diff --git a/CHANGELOG.md b/CHANGELOG.md index bb867af945e..e0e64087c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ ### Additions and Improvements - Remove privacy test classes support [#7569](https://github.com/hyperledger/besu/pull/7569) - Add Blob Transaction Metrics [#7622](https://github.com/hyperledger/besu/pull/7622) +- Implemented support for emptyBlockPeriodSeconds in QBFT [#6965](https://github.com/hyperledger/besu/pull/6965) + ### Bug fixes - Fix mounted data path directory permissions for besu user [#7575](https://github.com/hyperledger/besu/pull/7575) diff --git a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java index 3d3412d4860..60eac179965 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java @@ -288,12 +288,18 @@ protected MiningCoordinator createMiningCoordinator( protocolContext .getBlockchain() .observeBlockAdded( - o -> - miningParameters.setBlockPeriodSeconds( - qbftForksSchedule - .getFork(o.getBlock().getHeader().getNumber() + 1) - .getValue() - .getBlockPeriodSeconds())); + o -> { + miningParameters.setBlockPeriodSeconds( + qbftForksSchedule + .getFork(o.getBlock().getHeader().getNumber() + 1) + .getValue() + .getBlockPeriodSeconds()); + miningParameters.setEmptyBlockPeriodSeconds( + qbftForksSchedule + .getFork(o.getBlock().getHeader().getNumber() + 1) + .getValue() + .getEmptyBlockPeriodSeconds()); + }); if (syncState.isInitialSyncPhaseDone()) { miningCoordinator.enable(); @@ -422,8 +428,9 @@ private static MinedBlockObserver blockLogger( return block -> LOG.info( String.format( - "%s #%,d / %d tx / %d pending / %,d (%01.1f%%) gas / (%s)", + "%s %s #%,d / %d tx / %d pending / %,d (%01.1f%%) gas / (%s)", block.getHeader().getCoinbase().equals(localAddress) ? "Produced" : "Imported", + block.getBody().getTransactions().size() == 0 ? "empty block" : "block", block.getHeader().getNumber(), block.getBody().getTransactions().size(), transactionPool.count(), diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index 5b1274389d6..65e51c92139 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -138,15 +138,18 @@ public abstract class CommandTestAbstract { private static final Logger TEST_LOGGER = LoggerFactory.getLogger(CommandTestAbstract.class); protected static final int POA_BLOCK_PERIOD_SECONDS = 5; + protected static final int POA_EMPTY_BLOCK_PERIOD_SECONDS = 50; protected static final JsonObject VALID_GENESIS_QBFT_POST_LONDON = (new JsonObject()) .put( "config", new JsonObject() .put("londonBlock", 0) + .put("qbft", new JsonObject().put("blockperiodseconds", POA_BLOCK_PERIOD_SECONDS)) .put( "qbft", - new JsonObject().put("blockperiodseconds", POA_BLOCK_PERIOD_SECONDS))); + new JsonObject() + .put("xemptyblockperiodseconds", POA_EMPTY_BLOCK_PERIOD_SECONDS))); protected static final JsonObject VALID_GENESIS_IBFT2_POST_LONDON = (new JsonObject()) .put( diff --git a/config/src/main/java/org/hyperledger/besu/config/BftConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/BftConfigOptions.java index 58df7be8490..8ad35bf2d33 100644 --- a/config/src/main/java/org/hyperledger/besu/config/BftConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/BftConfigOptions.java @@ -37,6 +37,13 @@ public interface BftConfigOptions { */ int getBlockPeriodSeconds(); + /** + * Gets empty block period seconds. + * + * @return the empty block period seconds + */ + int getEmptyBlockPeriodSeconds(); + /** * Gets block period milliseconds. For TESTING only. If set then blockperiodseconds is ignored. * diff --git a/config/src/main/java/org/hyperledger/besu/config/BftFork.java b/config/src/main/java/org/hyperledger/besu/config/BftFork.java index fdaa8f2fa9d..9083bf339ec 100644 --- a/config/src/main/java/org/hyperledger/besu/config/BftFork.java +++ b/config/src/main/java/org/hyperledger/besu/config/BftFork.java @@ -41,6 +41,9 @@ public class BftFork implements Fork { /** The constant BLOCK_PERIOD_SECONDS_KEY. */ public static final String BLOCK_PERIOD_SECONDS_KEY = "blockperiodseconds"; + /** The constant EMPTY_BLOCK_PERIOD_SECONDS_KEY. */ + public static final String EMPTY_BLOCK_PERIOD_SECONDS_KEY = "xemptyblockperiodseconds"; + /** The constant BLOCK_PERIOD_MILLISECONDS_KEY. */ public static final String BLOCK_PERIOD_MILLISECONDS_KEY = "xblockperiodmilliseconds"; @@ -86,6 +89,16 @@ public OptionalInt getBlockPeriodSeconds() { return JsonUtil.getPositiveInt(forkConfigRoot, BLOCK_PERIOD_SECONDS_KEY); } + /** + * Gets empty block period seconds. + * + * @return the empty block period seconds + */ + public OptionalInt getEmptyBlockPeriodSeconds() { + // It can be 0 to disable custom empty block periods + return JsonUtil.getInt(forkConfigRoot, EMPTY_BLOCK_PERIOD_SECONDS_KEY); + } + /** * Gets block period milliseconds. Experimental for test scenarios only. * diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonBftConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonBftConfigOptions.java index b1c8630b399..fb9c8d6c796 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonBftConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonBftConfigOptions.java @@ -34,6 +34,8 @@ public class JsonBftConfigOptions implements BftConfigOptions { private static final long DEFAULT_EPOCH_LENGTH = 30_000; private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 1; + // 0 keeps working as before, increase to activate it + private static final int DEFAULT_EMPTY_BLOCK_PERIOD_SECONDS = 0; private static final int DEFAULT_BLOCK_PERIOD_MILLISECONDS = 0; // Experimental for test only private static final int DEFAULT_ROUND_EXPIRY_SECONDS = 1; // In a healthy network this can be very small. This default limit will allow for suitable @@ -67,6 +69,12 @@ public int getBlockPeriodSeconds() { bftConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS); } + @Override + public int getEmptyBlockPeriodSeconds() { + return JsonUtil.getInt( + bftConfigRoot, "xemptyblockperiodseconds", DEFAULT_EMPTY_BLOCK_PERIOD_SECONDS); + } + @Override public long getBlockPeriodMilliseconds() { return JsonUtil.getLong( diff --git a/config/src/test/java/org/hyperledger/besu/config/JsonBftConfigOptionsTest.java b/config/src/test/java/org/hyperledger/besu/config/JsonBftConfigOptionsTest.java index 5f530cebb5b..bbb4c4a6ceb 100644 --- a/config/src/test/java/org/hyperledger/besu/config/JsonBftConfigOptionsTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/JsonBftConfigOptionsTest.java @@ -17,6 +17,7 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.hyperledger.besu.datatypes.Address; @@ -30,6 +31,7 @@ public class JsonBftConfigOptionsTest { private static final int EXPECTED_DEFAULT_EPOCH_LENGTH = 30_000; private static final int EXPECTED_DEFAULT_BLOCK_PERIOD = 1; + private static final int EXPECTED_EMPTY_DEFAULT_BLOCK_PERIOD = 0; private static final int EXPECTED_DEFAULT_REQUEST_TIMEOUT = 1; private static final int EXPECTED_DEFAULT_GOSSIPED_HISTORY_LIMIT = 1000; private static final int EXPECTED_DEFAULT_MESSAGE_QUEUE_LIMIT = 1000; @@ -61,18 +63,37 @@ public void shouldGetBlockPeriodFromConfig() { assertThat(config.getBlockPeriodSeconds()).isEqualTo(5); } + @Test + public void shouldGetEmptyBlockPeriodFromConfig() { + final BftConfigOptions config = fromConfigOptions(singletonMap("xemptyblockperiodseconds", 60)); + assertThat(config.getEmptyBlockPeriodSeconds()).isEqualTo(60); + } + @Test public void shouldFallbackToDefaultBlockPeriod() { final BftConfigOptions config = fromConfigOptions(emptyMap()); assertThat(config.getBlockPeriodSeconds()).isEqualTo(EXPECTED_DEFAULT_BLOCK_PERIOD); } + @Test + public void shouldFallbackToEmptyDefaultBlockPeriod() { + final BftConfigOptions config = fromConfigOptions(emptyMap()); + assertThat(config.getEmptyBlockPeriodSeconds()).isEqualTo(EXPECTED_EMPTY_DEFAULT_BLOCK_PERIOD); + } + @Test public void shouldGetDefaultBlockPeriodFromDefaultConfig() { assertThat(JsonBftConfigOptions.DEFAULT.getBlockPeriodSeconds()) .isEqualTo(EXPECTED_DEFAULT_BLOCK_PERIOD); } + @Test + public void shouldGetDefaultEmptyBlockPeriodFromDefaultConfig() { + + assertThat(JsonBftConfigOptions.DEFAULT.getEmptyBlockPeriodSeconds()) + .isEqualTo(EXPECTED_EMPTY_DEFAULT_BLOCK_PERIOD); + } + @Test public void shouldThrowOnNonPositiveBlockPeriod() { final BftConfigOptions config = fromConfigOptions(singletonMap("blockperiodseconds", -1)); @@ -80,6 +101,13 @@ public void shouldThrowOnNonPositiveBlockPeriod() { .isInstanceOf(IllegalArgumentException.class); } + @Test + public void shouldNotThrowOnNonPositiveEmptyBlockPeriod() { + // can be 0 to be compatible with older versions + final BftConfigOptions config = fromConfigOptions(singletonMap("xemptyblockperiodseconds", 0)); + assertThatCode(() -> config.getEmptyBlockPeriodSeconds()).doesNotThrowAnyException(); + } + @Test public void shouldGetRequestTimeoutFromConfig() { final BftConfigOptions config = fromConfigOptions(singletonMap("requesttimeoutseconds", 5)); diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BlockTimer.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BlockTimer.java index 49ef94e008c..8b98a2ddec5 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BlockTimer.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BlockTimer.java @@ -37,6 +37,8 @@ public class BlockTimer { private Optional> currentTimerTask; private final BftEventQueue queue; private final Clock clock; + private long blockPeriodSeconds; + private long emptyBlockPeriodSeconds; /** * Construct a BlockTimer with primed executor service ready to start timers @@ -56,6 +58,8 @@ public BlockTimer( this.bftExecutors = bftExecutors; this.currentTimerTask = Optional.empty(); this.clock = clock; + this.blockPeriodSeconds = 0; + this.emptyBlockPeriodSeconds = 0; } /** Cancels the current running round timer if there is one */ @@ -83,13 +87,11 @@ public synchronized void startTimer( final ConsensusRoundIdentifier round, final BlockHeader chainHeadHeader) { cancelTimer(); - final long now = clock.millis(); final long expiryTime; // Experimental option for test scenarios only. Not for production use. final long blockPeriodMilliseconds = forksSchedule.getFork(round.getSequenceNumber()).getValue().getBlockPeriodMilliseconds(); - if (blockPeriodMilliseconds > 0) { // Experimental mode for setting < 1 second block periods e.g. for CI/CD pipelines // running tests against Besu @@ -99,12 +101,60 @@ public synchronized void startTimer( blockPeriodMilliseconds); } else { // absolute time when the timer is supposed to expire - final int blockPeriodSeconds = + final int currentBlockPeriodSeconds = forksSchedule.getFork(round.getSequenceNumber()).getValue().getBlockPeriodSeconds(); - final long minimumTimeBetweenBlocksMillis = blockPeriodSeconds * 1000L; + final long minimumTimeBetweenBlocksMillis = currentBlockPeriodSeconds * 1000L; expiryTime = chainHeadHeader.getTimestamp() * 1_000 + minimumTimeBetweenBlocksMillis; } + setBlockTimes(round); + + startTimer(round, expiryTime); + } + + /** + * Checks if the empty block timer is expired + * + * @param chainHeadHeader The header of the chain head + * @param currentTimeInMillis The current time + * @return a boolean value + */ + public synchronized boolean checkEmptyBlockExpired( + final BlockHeader chainHeadHeader, final long currentTimeInMillis) { + final long emptyBlockPeriodExpiryTime = + (chainHeadHeader.getTimestamp() + emptyBlockPeriodSeconds) * 1000; + + if (currentTimeInMillis > emptyBlockPeriodExpiryTime) { + LOG.debug("Empty Block expired"); + return true; + } + LOG.debug("Empty Block NOT expired"); + return false; + } + + /** + * Resets the empty block timer + * + * @param roundIdentifier The current round identifier + * @param chainHeadHeader The header of the chain head + * @param currentTimeInMillis The current time + */ + public void resetTimerForEmptyBlock( + final ConsensusRoundIdentifier roundIdentifier, + final BlockHeader chainHeadHeader, + final long currentTimeInMillis) { + final long emptyBlockPeriodExpiryTime = + (chainHeadHeader.getTimestamp() + emptyBlockPeriodSeconds) * 1000; + final long nextBlockPeriodExpiryTime = currentTimeInMillis + blockPeriodSeconds * 1000; + + startTimer(roundIdentifier, Math.min(emptyBlockPeriodExpiryTime, nextBlockPeriodExpiryTime)); + } + + private synchronized void startTimer( + final ConsensusRoundIdentifier round, final long expiryTime) { + cancelTimer(); + final long now = clock.millis(); + if (expiryTime > now) { final long delay = expiryTime - now; @@ -117,4 +167,29 @@ public synchronized void startTimer( queue.add(new BlockTimerExpiry(round)); } } + + private synchronized void setBlockTimes(final ConsensusRoundIdentifier round) { + final BftConfigOptions currentConfigOptions = + forksSchedule.getFork(round.getSequenceNumber()).getValue(); + this.blockPeriodSeconds = currentConfigOptions.getBlockPeriodSeconds(); + this.emptyBlockPeriodSeconds = currentConfigOptions.getEmptyBlockPeriodSeconds(); + } + + /** + * Retrieves the Block Period Seconds + * + * @return the Block Period Seconds + */ + public synchronized long getBlockPeriodSeconds() { + return blockPeriodSeconds; + } + + /** + * Retrieves the Empty Block Period Seconds + * + * @return the Empty Block Period Seconds + */ + public synchronized long getEmptyBlockPeriodSeconds() { + return emptyBlockPeriodSeconds; + } } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/MutableBftConfigOptions.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/MutableBftConfigOptions.java index 7b27c7b4c44..4cf00acd3b3 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/MutableBftConfigOptions.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/MutableBftConfigOptions.java @@ -31,6 +31,7 @@ public class MutableBftConfigOptions implements BftConfigOptions { private long epochLength; private int blockPeriodSeconds; + private int emptyBlockPeriodSeconds; private long blockPeriodMilliseconds; private int requestTimeoutSeconds; private int gossipedHistoryLimit; @@ -49,6 +50,7 @@ public class MutableBftConfigOptions implements BftConfigOptions { public MutableBftConfigOptions(final BftConfigOptions bftConfigOptions) { this.epochLength = bftConfigOptions.getEpochLength(); this.blockPeriodSeconds = bftConfigOptions.getBlockPeriodSeconds(); + this.emptyBlockPeriodSeconds = bftConfigOptions.getEmptyBlockPeriodSeconds(); this.blockPeriodMilliseconds = bftConfigOptions.getBlockPeriodMilliseconds(); this.requestTimeoutSeconds = bftConfigOptions.getRequestTimeoutSeconds(); this.gossipedHistoryLimit = bftConfigOptions.getGossipedHistoryLimit(); @@ -70,6 +72,11 @@ public int getBlockPeriodSeconds() { return blockPeriodSeconds; } + @Override + public int getEmptyBlockPeriodSeconds() { + return emptyBlockPeriodSeconds; + } + @Override public long getBlockPeriodMilliseconds() { return blockPeriodMilliseconds; @@ -138,6 +145,15 @@ public void setBlockPeriodSeconds(final int blockPeriodSeconds) { this.blockPeriodSeconds = blockPeriodSeconds; } + /** + * Sets empty block period seconds. + * + * @param emptyBlockPeriodSeconds the empty block period seconds + */ + public void setEmptyBlockPeriodSeconds(final int emptyBlockPeriodSeconds) { + this.emptyBlockPeriodSeconds = emptyBlockPeriodSeconds; + } + /** * Sets block period milliseconds. Experimental for test scenarios. Not for use on production * systems. diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleFactoryTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleFactoryTest.java index 0cc9c129451..89e7330c4c4 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleFactoryTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleFactoryTest.java @@ -37,7 +37,7 @@ public class ForksScheduleFactoryTest { @SuppressWarnings("unchecked") public void throwsErrorIfHasForkForGenesisBlock() { final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT; - final BftFork fork = createFork(0, 10); + final BftFork fork = createFork(0, 10, 30); final SpecCreator specCreator = Mockito.mock(SpecCreator.class); assertThatThrownBy( @@ -49,9 +49,9 @@ public void throwsErrorIfHasForkForGenesisBlock() { @SuppressWarnings("unchecked") public void throwsErrorIfHasForksWithDuplicateBlock() { final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT; - final BftFork fork1 = createFork(1, 10); - final BftFork fork2 = createFork(1, 20); - final BftFork fork3 = createFork(2, 30); + final BftFork fork1 = createFork(1, 10, 30); + final BftFork fork2 = createFork(1, 20, 60); + final BftFork fork3 = createFork(2, 30, 90); final SpecCreator specCreator = Mockito.mock(SpecCreator.class); assertThatThrownBy( @@ -66,12 +66,12 @@ public void throwsErrorIfHasForksWithDuplicateBlock() { public void createsScheduleUsingSpecCreator() { final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT; final ForkSpec genesisForkSpec = new ForkSpec<>(0, genesisConfigOptions); - final BftFork fork1 = createFork(1, 10); - final BftFork fork2 = createFork(2, 20); + final BftFork fork1 = createFork(1, 10, 20); + final BftFork fork2 = createFork(2, 20, 40); final SpecCreator specCreator = Mockito.mock(SpecCreator.class); - final BftConfigOptions configOptions1 = createBftConfigOptions(10); - final BftConfigOptions configOptions2 = createBftConfigOptions(20); + final BftConfigOptions configOptions1 = createBftConfigOptions(10, 30); + final BftConfigOptions configOptions2 = createBftConfigOptions(20, 60); when(specCreator.create(genesisForkSpec, fork1)).thenReturn(configOptions1); when(specCreator.create(new ForkSpec<>(1, configOptions1), fork2)).thenReturn(configOptions2); @@ -82,18 +82,25 @@ public void createsScheduleUsingSpecCreator() { assertThat(schedule.getFork(2)).isEqualTo(new ForkSpec<>(2, configOptions2)); } - private MutableBftConfigOptions createBftConfigOptions(final int blockPeriodSeconds) { + private MutableBftConfigOptions createBftConfigOptions( + final int blockPeriodSeconds, final int emptyBlockPeriodSeconds) { final MutableBftConfigOptions bftConfigOptions = new MutableBftConfigOptions(JsonBftConfigOptions.DEFAULT); bftConfigOptions.setBlockPeriodSeconds(blockPeriodSeconds); + bftConfigOptions.setEmptyBlockPeriodSeconds(emptyBlockPeriodSeconds); return bftConfigOptions; } - private BftFork createFork(final long block, final long blockPeriodSeconds) { + private BftFork createFork( + final long block, final long blockPeriodSeconds, final long emptyBlockPeriodSeconds) { return new BftFork( JsonUtil.objectNodeFromMap( Map.of( - BftFork.FORK_BLOCK_KEY, block, - BftFork.BLOCK_PERIOD_SECONDS_KEY, blockPeriodSeconds))); + BftFork.FORK_BLOCK_KEY, + block, + BftFork.BLOCK_PERIOD_SECONDS_KEY, + blockPeriodSeconds, + BftFork.EMPTY_BLOCK_PERIOD_SECONDS_KEY, + emptyBlockPeriodSeconds))); } } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BlockTimerTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BlockTimerTest.java index 70187a4d40d..7521e5eb301 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BlockTimerTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BlockTimerTest.java @@ -75,12 +75,18 @@ public void cancelTimerCancelsWhenNoTimer() { @Test public void startTimerSchedulesCorrectlyWhenExpiryIsInTheFuture() { final int MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS = 15; + final int MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS = 60; final long NOW_MILLIS = 505_000L; final long BLOCK_TIME_STAMP = 500L; final long EXPECTED_DELAY = 10_000L; when(mockForksSchedule.getFork(anyLong())) - .thenReturn(new ForkSpec<>(0, createBftFork(MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS))); + .thenReturn( + new ForkSpec<>( + 0, + createBftFork( + MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS, + MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS))); final BlockTimer timer = new BlockTimer(mockQueue, mockForksSchedule, bftExecutors, mockClock); @@ -104,12 +110,18 @@ public void startTimerSchedulesCorrectlyWhenExpiryIsInTheFuture() { @Test public void aBlockTimerExpiryEventIsAddedToTheQueueOnExpiry() throws InterruptedException { final int MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS = 1; + final int MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS = 10; final long NOW_MILLIS = 300_500L; final long BLOCK_TIME_STAMP = 300; final long EXPECTED_DELAY = 500; when(mockForksSchedule.getFork(anyLong())) - .thenReturn(new ForkSpec<>(0, createBftFork(MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS))); + .thenReturn( + new ForkSpec<>( + 0, + createBftFork( + MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS, + MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS))); when(mockClock.millis()).thenReturn(NOW_MILLIS); final BlockHeader header = @@ -149,11 +161,17 @@ public void aBlockTimerExpiryEventIsAddedToTheQueueOnExpiry() throws Interrupted @Test public void eventIsImmediatelyAddedToTheQueueIfAbsoluteExpiryIsEqualToNow() { final int MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS = 15; + final int MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS = 60; final long NOW_MILLIS = 515_000L; final long BLOCK_TIME_STAMP = 500; when(mockForksSchedule.getFork(anyLong())) - .thenReturn(new ForkSpec<>(0, createBftFork(MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS))); + .thenReturn( + new ForkSpec<>( + 0, + createBftFork( + MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS, + MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS))); final BlockTimer timer = new BlockTimer(mockQueue, mockForksSchedule, bftExecutors, mockClock); @@ -179,11 +197,17 @@ public void eventIsImmediatelyAddedToTheQueueIfAbsoluteExpiryIsEqualToNow() { @Test public void eventIsImmediatelyAddedToTheQueueIfAbsoluteExpiryIsInThePast() { final int MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS = 15; + final int MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS = 60; final long NOW_MILLIS = 520_000L; final long BLOCK_TIME_STAMP = 500L; when(mockForksSchedule.getFork(anyLong())) - .thenReturn(new ForkSpec<>(0, createBftFork(MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS))); + .thenReturn( + new ForkSpec<>( + 0, + createBftFork( + MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS, + MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS))); final BlockTimer timer = new BlockTimer(mockQueue, mockForksSchedule, bftExecutors, mockClock); @@ -209,11 +233,17 @@ public void eventIsImmediatelyAddedToTheQueueIfAbsoluteExpiryIsInThePast() { @Test public void startTimerCancelsExistingTimer() { final int MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS = 15; + final int MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS = 60; final long NOW_MILLIS = 500_000L; final long BLOCK_TIME_STAMP = 500L; when(mockForksSchedule.getFork(anyLong())) - .thenReturn(new ForkSpec<>(0, createBftFork(MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS))); + .thenReturn( + new ForkSpec<>( + 0, + createBftFork( + MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS, + MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS))); final BlockTimer timer = new BlockTimer(mockQueue, mockForksSchedule, bftExecutors, mockClock); @@ -237,11 +267,17 @@ public void startTimerCancelsExistingTimer() { @Test public void runningFollowsTheStateOfTheTimer() { final int MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS = 15; + final int MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS = 60; final long NOW_MILLIS = 500_000L; final long BLOCK_TIME_STAMP = 500L; when(mockForksSchedule.getFork(anyLong())) - .thenReturn(new ForkSpec<>(0, createBftFork(MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS))); + .thenReturn( + new ForkSpec<>( + 0, + createBftFork( + MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS, + MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS))); final BlockTimer timer = new BlockTimer(mockQueue, mockForksSchedule, bftExecutors, mockClock); @@ -263,10 +299,42 @@ public void runningFollowsTheStateOfTheTimer() { assertThat(timer.isRunning()).isFalse(); } - private BftConfigOptions createBftFork(final int blockPeriodSeconds) { + @Test + public void checkBlockTimerEmptyAndNonEmptyPeriodSecods() { + final int MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS = 15; + final int MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS = 60; + final long BLOCK_TIME_STAMP = 500L; + final ConsensusRoundIdentifier round = + new ConsensusRoundIdentifier(0xFEDBCA9876543210L, 0x12345678); + final BlockHeader header = + new BlockHeaderTestFixture().timestamp(BLOCK_TIME_STAMP).buildHeader(); + final ScheduledFuture mockedFuture = mock(ScheduledFuture.class); + Mockito.>when( + bftExecutors.scheduleTask(any(Runnable.class), anyLong(), any())) + .thenReturn(mockedFuture); + + when(mockForksSchedule.getFork(anyLong())) + .thenReturn( + new ForkSpec<>( + 0, + createBftFork( + MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS, + MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS))); + + final BlockTimer timer = new BlockTimer(mockQueue, mockForksSchedule, bftExecutors, mockClock); + timer.startTimer(round, header); + + assertThat(timer.getBlockPeriodSeconds()).isEqualTo(MINIMAL_TIME_BETWEEN_BLOCKS_SECONDS); + assertThat(timer.getEmptyBlockPeriodSeconds()) + .isEqualTo(MINIMAL_TIME_BETWEEN_EMPTY_BLOCKS_SECONDS); + } + + private BftConfigOptions createBftFork( + final int blockPeriodSeconds, final int emptyBlockPeriodSeconds) { final MutableBftConfigOptions bftConfigOptions = new MutableBftConfigOptions(JsonBftConfigOptions.DEFAULT); bftConfigOptions.setBlockPeriodSeconds(blockPeriodSeconds); + bftConfigOptions.setEmptyBlockPeriodSeconds(emptyBlockPeriodSeconds); return bftConfigOptions; } } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftForksSchedulesFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftForksSchedulesFactory.java index 0f0d467de37..be8e2ed5c46 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftForksSchedulesFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftForksSchedulesFactory.java @@ -49,6 +49,7 @@ private static QbftConfigOptions createQbftConfigOptions( new MutableQbftConfigOptions(lastSpec.getValue()); fork.getBlockPeriodSeconds().ifPresent(bftConfigOptions::setBlockPeriodSeconds); + fork.getEmptyBlockPeriodSeconds().ifPresent(bftConfigOptions::setEmptyBlockPeriodSeconds); fork.getBlockPeriodMilliseconds().ifPresent(bftConfigOptions::setBlockPeriodMilliseconds); fork.getBlockRewardWei().ifPresent(bftConfigOptions::setBlockRewardWei); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManager.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManager.java index f79537cc0f9..757998b5ca6 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManager.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManager.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.consensus.qbft.validation.FutureRoundProposalMessageValidator; import org.hyperledger.besu.consensus.qbft.validation.MessageValidatorFactory; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; @@ -130,19 +131,57 @@ public void handleBlockTimerExpiry(final ConsensusRoundIdentifier roundIdentifie logValidatorChanges(qbftRound); + if (roundIdentifier.equals(qbftRound.getRoundIdentifier())) { + buildBlockAndMaybePropose(roundIdentifier, qbftRound); + } else { + LOG.trace( + "Block timer expired for a round ({}) other than current ({})", + roundIdentifier, + qbftRound.getRoundIdentifier()); + } + } + + private void buildBlockAndMaybePropose( + final ConsensusRoundIdentifier roundIdentifier, final QbftRound qbftRound) { + // mining will be checked against round 0 as the current round is initialised to 0 above final boolean isProposer = finalState.isLocalNodeProposerForRound(qbftRound.getRoundIdentifier()); - if (isProposer) { - if (roundIdentifier.equals(qbftRound.getRoundIdentifier())) { - final long headerTimeStampSeconds = Math.round(clock.millis() / 1000D); - qbftRound.createAndSendProposalMessage(headerTimeStampSeconds); + if (!isProposer) { + // nothing to do here... + LOG.trace("This node is not a proposer so it will not send a proposal: " + roundIdentifier); + return; + } + + final long headerTimeStampSeconds = Math.round(clock.millis() / 1000D); + final Block block = qbftRound.createBlock(headerTimeStampSeconds); + final boolean blockHasTransactions = !block.getBody().getTransactions().isEmpty(); + if (blockHasTransactions) { + LOG.trace( + "Block has transactions and this node is a proposer so it will send a proposal: " + + roundIdentifier); + qbftRound.updateStateWithProposalAndTransmit(block); + + } else { + // handle the block times period + final long currentTimeInMillis = finalState.getClock().millis(); + boolean emptyBlockExpired = + finalState.getBlockTimer().checkEmptyBlockExpired(parentHeader, currentTimeInMillis); + if (emptyBlockExpired) { + LOG.trace( + "Block has no transactions and this node is a proposer so it will send a proposal: " + + roundIdentifier); + qbftRound.updateStateWithProposalAndTransmit(block); } else { LOG.trace( - "Block timer expired for a round ({}) other than current ({})", - roundIdentifier, - qbftRound.getRoundIdentifier()); + "Block has no transactions but emptyBlockPeriodSeconds did not expired yet: " + + roundIdentifier); + finalState + .getBlockTimer() + .resetTimerForEmptyBlock(roundIdentifier, parentHeader, currentTimeInMillis); + finalState.getRoundTimer().cancelTimer(); + currentRound = Optional.empty(); } } } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java index f7c43ef43dd..e0a8cb1c726 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java @@ -132,17 +132,14 @@ public ConsensusRoundIdentifier getRoundIdentifier() { } /** - * Create and send proposal message. + * Create a block * - * @param headerTimeStampSeconds the header time stamp seconds + * @param headerTimeStampSeconds of the block + * @return a Block */ - public void createAndSendProposalMessage(final long headerTimeStampSeconds) { + public Block createBlock(final long headerTimeStampSeconds) { LOG.debug("Creating proposed block. round={}", roundState.getRoundIdentifier()); - final Block block = - blockCreator.createBlock(headerTimeStampSeconds, this.parentHeader).getBlock(); - - LOG.trace("Creating proposed block blockHeader={}", block.getHeader()); - updateStateWithProposalAndTransmit(block, emptyList(), emptyList()); + return blockCreator.createBlock(headerTimeStampSeconds, this.parentHeader).getBlock(); } /** @@ -172,6 +169,15 @@ public void startRoundWith( bestPreparedCertificate.map(PreparedCertificate::getPrepares).orElse(emptyList())); } + /** + * Update state with proposal and transmit. + * + * @param block the block + */ + protected void updateStateWithProposalAndTransmit(final Block block) { + updateStateWithProposalAndTransmit(block, emptyList(), emptyList()); + } + /** * Update state with proposal and transmit. * diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptionsTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptionsTest.java index 8ad39adc5b5..2b04796b1a2 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptionsTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptionsTest.java @@ -37,4 +37,24 @@ public void getValidatorContractAddressNormalization() { assertThat(mutableQbftConfigOptions.getValidatorContractAddress()).hasValue("0xabc"); } + + @Test + public void checkBlockPeriodSeconds() { + when(qbftConfigOptions.getBlockPeriodSeconds()).thenReturn(2); + + final MutableQbftConfigOptions mutableQbftConfigOptions = + new MutableQbftConfigOptions(qbftConfigOptions); + + assertThat(mutableQbftConfigOptions.getBlockPeriodSeconds()).isEqualTo(2); + } + + @Test + public void checkEmptyBlockPeriodSeconds() { + when(qbftConfigOptions.getEmptyBlockPeriodSeconds()).thenReturn(60); + + final MutableQbftConfigOptions mutableQbftConfigOptions = + new MutableQbftConfigOptions(qbftConfigOptions); + + assertThat(mutableQbftConfigOptions.getEmptyBlockPeriodSeconds()).isEqualTo(60); + } } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java index 171b1607f39..190c87e020f 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java @@ -157,8 +157,10 @@ public void setup() { when(messageValidator.validateCommit(any())).thenReturn(true); when(messageValidator.validatePrepare(any())).thenReturn(true); when(finalState.getBlockTimer()).thenReturn(blockTimer); + when(finalState.getRoundTimer()).thenReturn(roundTimer); when(finalState.getQuorum()).thenReturn(3); when(finalState.getValidatorMulticaster()).thenReturn(validatorMulticaster); + when(finalState.getClock()).thenReturn(clock); when(blockCreator.createBlock(anyLong(), any())) .thenReturn( new BlockCreationResult( @@ -267,6 +269,7 @@ public void doesNotStartRoundTimerOnStart() { @Test public void onBlockTimerExpiryRoundTimerIsStartedAndProposalMessageIsTransmitted() { when(finalState.isLocalNodeProposerForRound(roundIdentifier)).thenReturn(true); + when(blockTimer.checkEmptyBlockExpired(any(), eq(0l))).thenReturn(true); final QbftBlockHeightManager manager = new QbftBlockHeightManager( @@ -290,6 +293,7 @@ public void onBlockTimerExpiryRoundTimerIsStartedAndProposalMessageIsTransmitted public void onBlockTimerExpiryForNonProposerRoundTimerIsStartedAndNoProposalMessageIsTransmitted() { when(finalState.isLocalNodeProposerForRound(roundIdentifier)).thenReturn(false); + when(blockTimer.checkEmptyBlockExpired(any(), eq(0l))).thenReturn(true); final QbftBlockHeightManager manager = new QbftBlockHeightManager( @@ -463,6 +467,7 @@ public void messagesForFutureRoundsAreBufferedAndUsedToPreloadNewRoundWhenItIsSt public void messagesForCurrentRoundAreBufferedAndUsedToPreloadRoundWhenItIsStarted() { when(finalState.getQuorum()).thenReturn(1); when(finalState.isLocalNodeProposerForRound(roundIdentifier)).thenReturn(true); + when(blockTimer.checkEmptyBlockExpired(any(), eq(0l))).thenReturn(true); final QbftBlockHeightManager manager = new QbftBlockHeightManager( @@ -500,6 +505,7 @@ public void messagesForCurrentRoundAreBufferedAndUsedToPreloadRoundWhenItIsStart @Test public void preparedCertificateIncludedInRoundChangeMessageOnRoundTimeoutExpired() { when(finalState.isLocalNodeProposerForRound(any())).thenReturn(true); + when(blockTimer.checkEmptyBlockExpired(any(), eq(0l))).thenReturn(true); final QbftBlockHeightManager manager = new QbftBlockHeightManager( @@ -577,4 +583,24 @@ public void illegalFutureRoundProposalDoesNotTriggerNewRound() { manager.handleProposalPayload(futureRoundProposal); verify(roundFactory, never()).createNewRound(any(), anyInt()); } + + @Test + public void checkOnlyEmptyBlockPeriodSecondsIsInvokedForBlocksWithNoTransactions() { + when(finalState.isLocalNodeProposerForRound(roundIdentifier)).thenReturn(true); + + final QbftBlockHeightManager manager = + new QbftBlockHeightManager( + headerTestFixture.buildHeader(), + finalState, + roundChangeManager, + roundFactory, + clock, + messageValidatorFactory, + messageFactory); + + manager.handleBlockTimerExpiry(roundIdentifier); + + verify(blockTimer, times(0)).getEmptyBlockPeriodSeconds(); + verify(blockTimer, times(0)).getBlockPeriodSeconds(); + } } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java index 514fd653a1d..fb84c1c38fa 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import org.hyperledger.besu.consensus.common.bft.BftBlockHashing; import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; @@ -48,7 +47,6 @@ import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.BlockCreationTiming; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; @@ -196,90 +194,6 @@ public void onReceptionOfValidProposalSendsAPrepareToNetworkPeers() { verify(transmitter, never()).multicastCommit(any(), any(), any()); } - @Test - public void sendsAProposalAndPrepareWhenSendProposalRequested() { - final RoundState roundState = new RoundState(roundIdentifier, 3, messageValidator); - final QbftRound round = - new QbftRound( - roundState, - blockCreator, - protocolContext, - protocolSchedule, - subscribers, - nodeKey, - messageFactory, - transmitter, - roundTimer, - bftExtraDataCodec, - parentHeader); - - round.createAndSendProposalMessage(15); - verify(transmitter, times(1)) - .multicastProposal( - roundIdentifier, proposedBlock, Collections.emptyList(), Collections.emptyList()); - verify(transmitter, times(1)).multicastPrepare(roundIdentifier, proposedBlock.getHash()); - verify(transmitter, never()).multicastCommit(any(), any(), any()); - } - - @Test - public void singleValidatorImportBlocksImmediatelyOnProposalCreation() { - final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator); - final QbftRound round = - new QbftRound( - roundState, - blockCreator, - protocolContext, - protocolSchedule, - subscribers, - nodeKey, - messageFactory, - transmitter, - roundTimer, - bftExtraDataCodec, - parentHeader); - round.createAndSendProposalMessage(15); - verify(transmitter, times(1)) - .multicastProposal( - roundIdentifier, proposedBlock, Collections.emptyList(), Collections.emptyList()); - verify(transmitter, times(1)).multicastPrepare(roundIdentifier, proposedBlock.getHash()); - verify(transmitter, times(1)).multicastCommit(any(), any(), any()); - } - - @Test - public void localNodeProposesToNetworkOfTwoValidatorsImportsOnReceptionOfCommitFromPeer() { - final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator); - final QbftRound round = - new QbftRound( - roundState, - blockCreator, - protocolContext, - protocolSchedule, - subscribers, - nodeKey, - messageFactory, - transmitter, - roundTimer, - bftExtraDataCodec, - parentHeader); - - final Hash commitSealHash = - new BftBlockHashing(new QbftExtraDataCodec()) - .calculateDataHashForCommittedSeal(proposedBlock.getHeader(), proposedExtraData); - final SECPSignature localCommitSeal = nodeKey.sign(commitSealHash); - - round.createAndSendProposalMessage(15); - verify(transmitter, never()).multicastCommit(any(), any(), any()); - - round.handlePrepareMessage( - messageFactory2.createPrepare(roundIdentifier, proposedBlock.getHash())); - - verify(transmitter, times(1)) - .multicastCommit(roundIdentifier, proposedBlock.getHash(), localCommitSeal); - - round.handleCommitMessage( - messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); - } - @Test public void aProposalWithAnewBlockIsSentUponReceptionOfARoundChangeWithNoCertificate() { final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator); @@ -393,26 +307,6 @@ public void creatingNewBlockFromEmptyPreparedCertificateUpdatesInternalState() { assertThat(roundState.isPrepared()).isTrue(); } - @Test - public void creatingNewBlockNotifiesBlockMiningObservers() { - final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator); - final QbftRound round = - new QbftRound( - roundState, - blockCreator, - protocolContext, - protocolSchedule, - subscribers, - nodeKey, - messageFactory, - transmitter, - roundTimer, - bftExtraDataCodec, - parentHeader); - round.createAndSendProposalMessage(15); - verify(minedBlockObserver).blockMined(any()); - } - @Test public void blockIsOnlyImportedOnceWhenCommitsAreReceivedBeforeProposal() { final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 0); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/QbftForksSchedulesFactoryTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/QbftForksSchedulesFactoryTest.java index d57ad8dd9fa..9622f98074a 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/QbftForksSchedulesFactoryTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/QbftForksSchedulesFactoryTest.java @@ -63,6 +63,8 @@ public void createsScheduleWithForkThatOverridesGenesisValues() { List.of("1", "2", "3"), BftFork.BLOCK_PERIOD_SECONDS_KEY, 10, + BftFork.EMPTY_BLOCK_PERIOD_SECONDS_KEY, + 60, BftFork.BLOCK_REWARD_KEY, "5", QbftFork.VALIDATOR_SELECTION_MODE_KEY, @@ -78,6 +80,7 @@ public void createsScheduleWithForkThatOverridesGenesisValues() { final Map forkOptions = new HashMap<>(configOptions.asMap()); forkOptions.put(BftFork.BLOCK_PERIOD_SECONDS_KEY, 10); + forkOptions.put(BftFork.EMPTY_BLOCK_PERIOD_SECONDS_KEY, 60); forkOptions.put(BftFork.BLOCK_REWARD_KEY, "5"); forkOptions.put(QbftFork.VALIDATOR_SELECTION_MODE_KEY, "5"); forkOptions.put(QbftFork.VALIDATOR_CONTRACT_ADDRESS_KEY, "10"); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java index 20a8a31084d..1921b3568b1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java @@ -131,6 +131,11 @@ public MiningParameters setBlockPeriodSeconds(final int blockPeriodSeconds) { return this; } + public MiningParameters setEmptyBlockPeriodSeconds(final int emptyBlockPeriodSeconds) { + getMutableRuntimeValues().emptyBlockPeriodSeconds = OptionalInt.of(emptyBlockPeriodSeconds); + return this; + } + @Value.Default public boolean isStratumMiningEnabled() { return false; @@ -231,6 +236,8 @@ default double getMinBlockOccupancyRatio() { OptionalInt getBlockPeriodSeconds(); + OptionalInt getEmptyBlockPeriodSeconds(); + Optional
getCoinbase(); OptionalLong getTargetGasLimit(); @@ -248,6 +255,7 @@ static class MutableRuntimeValues { private volatile OptionalLong targetGasLimit; private volatile Optional> nonceGenerator; private volatile OptionalInt blockPeriodSeconds; + private volatile OptionalInt emptyBlockPeriodSeconds; private MutableRuntimeValues(final MutableInitValues initValues) { miningEnabled = initValues.isMiningEnabled(); @@ -259,6 +267,7 @@ private MutableRuntimeValues(final MutableInitValues initValues) { targetGasLimit = initValues.getTargetGasLimit(); nonceGenerator = initValues.nonceGenerator(); blockPeriodSeconds = initValues.getBlockPeriodSeconds(); + emptyBlockPeriodSeconds = initValues.getEmptyBlockPeriodSeconds(); } @Override @@ -274,7 +283,8 @@ public boolean equals(final Object o) { && Objects.equals(minPriorityFeePerGas, that.minPriorityFeePerGas) && Objects.equals(targetGasLimit, that.targetGasLimit) && Objects.equals(nonceGenerator, that.nonceGenerator) - && Objects.equals(blockPeriodSeconds, that.blockPeriodSeconds); + && Objects.equals(blockPeriodSeconds, that.blockPeriodSeconds) + && Objects.equals(emptyBlockPeriodSeconds, that.emptyBlockPeriodSeconds); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTask.java index 85a478c9aaf..6333f0d1913 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTask.java @@ -218,7 +218,8 @@ protected void cleanup() { case IMPORTED: LOG.info( String.format( - "Imported #%,d / %d tx / %d om / %,d (%01.1f%%) gas / (%s) in %01.3fs. Peers: %d", + "Imported %s #%,d / %d tx / %d om / %,d (%01.1f%%) gas / (%s) in %01.3fs. Peers: %d", + block.getBody().getTransactions().size() == 0 ? "empty block" : "block", block.getHeader().getNumber(), block.getBody().getTransactions().size(), block.getBody().getOmmers().size(),