diff --git a/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java index b8d4d2645e0..58412029fc3 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java @@ -74,6 +74,7 @@ import org.hyperledger.besu.plugin.services.BesuEvents; import org.hyperledger.besu.util.Subscribers; +import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -182,7 +183,10 @@ protected MiningCoordinator createMiningCoordinator( Util.publicKeyToAddress(nodeKey.getPublicKey()), proposerSelector, uniqueMessageMulticaster, - new RoundTimer(bftEventQueue, bftConfig.getRequestTimeoutSeconds(), bftExecutors), + new RoundTimer( + bftEventQueue, + Duration.ofSeconds(bftConfig.getRequestTimeoutSeconds()), + bftExecutors), new BlockTimer(bftEventQueue, forksSchedule, bftExecutors, clock), blockCreatorFactory, clock); 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 7961305c48e..3d3412d4860 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java @@ -84,6 +84,7 @@ import org.hyperledger.besu.plugin.services.BesuEvents; import org.hyperledger.besu.util.Subscribers; +import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -222,7 +223,10 @@ protected MiningCoordinator createMiningCoordinator( Util.publicKeyToAddress(nodeKey.getPublicKey()), proposerSelector, uniqueMessageMulticaster, - new RoundTimer(bftEventQueue, qbftConfig.getRequestTimeoutSeconds(), bftExecutors), + new RoundTimer( + bftEventQueue, + Duration.ofSeconds(qbftConfig.getRequestTimeoutSeconds()), + bftExecutors), new BlockTimer(bftEventQueue, qbftForksSchedule, bftExecutors, clock), blockCreatorFactory, clock); 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 c94752b598d..58df7be8490 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 block period milliseconds. For TESTING only. If set then blockperiodseconds is ignored. + * + * @return the block period milliseconds + */ + long getBlockPeriodMilliseconds(); + /** * Gets request timeout seconds. * 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 30f8e1c5d5b..fdaa8f2fa9d 100644 --- a/config/src/main/java/org/hyperledger/besu/config/BftFork.java +++ b/config/src/main/java/org/hyperledger/besu/config/BftFork.java @@ -21,6 +21,7 @@ import java.util.Locale; import java.util.Optional; import java.util.OptionalInt; +import java.util.OptionalLong; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -40,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 BLOCK_PERIOD_MILLISECONDS_KEY. */ + public static final String BLOCK_PERIOD_MILLISECONDS_KEY = "xblockperiodmilliseconds"; + /** The constant BLOCK_REWARD_KEY. */ public static final String BLOCK_REWARD_KEY = "blockreward"; @@ -82,6 +86,15 @@ public OptionalInt getBlockPeriodSeconds() { return JsonUtil.getPositiveInt(forkConfigRoot, BLOCK_PERIOD_SECONDS_KEY); } + /** + * Gets block period milliseconds. Experimental for test scenarios only. + * + * @return the block period milliseconds + */ + public OptionalLong getBlockPeriodMilliseconds() { + return JsonUtil.getLong(forkConfigRoot, BLOCK_PERIOD_MILLISECONDS_KEY); + } + /** * Gets block reward wei. * 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 95f2d9f7ce7..b1c8630b399 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,7 @@ public class JsonBftConfigOptions implements BftConfigOptions { private static final long DEFAULT_EPOCH_LENGTH = 30_000; private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 1; + 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 // protection for on a typical 20 node validator network with multiple rounds @@ -66,6 +67,12 @@ public int getBlockPeriodSeconds() { bftConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS); } + @Override + public long getBlockPeriodMilliseconds() { + return JsonUtil.getLong( + bftConfigRoot, "xblockperiodmilliseconds", DEFAULT_BLOCK_PERIOD_MILLISECONDS); + } + @Override public int getRequestTimeoutSeconds() { return JsonUtil.getInt(bftConfigRoot, "requesttimeoutseconds", DEFAULT_ROUND_EXPIRY_SECONDS); @@ -133,6 +140,9 @@ public Map asMap() { if (bftConfigRoot.has("blockperiodseconds")) { builder.put("blockPeriodSeconds", getBlockPeriodSeconds()); } + if (bftConfigRoot.has("xblockperiodmilliseconds")) { + builder.put("xBlockPeriodMilliSeconds", getBlockPeriodMilliseconds()); + } if (bftConfigRoot.has("requesttimeoutseconds")) { builder.put("requestTimeoutSeconds", getRequestTimeoutSeconds()); } 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 5649b69e8f1..49ef94e008c 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 @@ -24,9 +24,14 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** Class for starting and keeping organised block timers */ public class BlockTimer { + private static final Logger LOG = LoggerFactory.getLogger(BlockTimer.class); + private final ForksSchedule forksSchedule; private final BftExecutors bftExecutors; private Optional> currentTimerTask; @@ -79,12 +84,26 @@ public synchronized void startTimer( 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(); - // absolute time when the timer is supposed to expire - final int blockPeriodSeconds = - forksSchedule.getFork(round.getSequenceNumber()).getValue().getBlockPeriodSeconds(); - final long minimumTimeBetweenBlocksMillis = blockPeriodSeconds * 1000L; - final long expiryTime = chainHeadHeader.getTimestamp() * 1_000 + minimumTimeBetweenBlocksMillis; + if (blockPeriodMilliseconds > 0) { + // Experimental mode for setting < 1 second block periods e.g. for CI/CD pipelines + // running tests against Besu + expiryTime = clock.millis() + blockPeriodMilliseconds; + LOG.warn( + "Test-mode only xblockperiodmilliseconds has been set to {} millisecond blocks. Do not use in a production system.", + blockPeriodMilliseconds); + } else { + // absolute time when the timer is supposed to expire + final int blockPeriodSeconds = + forksSchedule.getFork(round.getSequenceNumber()).getValue().getBlockPeriodSeconds(); + final long minimumTimeBetweenBlocksMillis = blockPeriodSeconds * 1000L; + expiryTime = chainHeadHeader.getTimestamp() * 1_000 + minimumTimeBetweenBlocksMillis; + } if (expiryTime > now) { final long delay = expiryTime - now; 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 fd406ea5e12..7b27c7b4c44 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 long blockPeriodMilliseconds; private int requestTimeoutSeconds; private int gossipedHistoryLimit; private int messageQueueLimit; @@ -48,6 +49,7 @@ public class MutableBftConfigOptions implements BftConfigOptions { public MutableBftConfigOptions(final BftConfigOptions bftConfigOptions) { this.epochLength = bftConfigOptions.getEpochLength(); this.blockPeriodSeconds = bftConfigOptions.getBlockPeriodSeconds(); + this.blockPeriodMilliseconds = bftConfigOptions.getBlockPeriodMilliseconds(); this.requestTimeoutSeconds = bftConfigOptions.getRequestTimeoutSeconds(); this.gossipedHistoryLimit = bftConfigOptions.getGossipedHistoryLimit(); this.messageQueueLimit = bftConfigOptions.getMessageQueueLimit(); @@ -68,6 +70,11 @@ public int getBlockPeriodSeconds() { return blockPeriodSeconds; } + @Override + public long getBlockPeriodMilliseconds() { + return blockPeriodMilliseconds; + } + @Override public int getRequestTimeoutSeconds() { return requestTimeoutSeconds; @@ -131,6 +138,16 @@ public void setBlockPeriodSeconds(final int blockPeriodSeconds) { this.blockPeriodSeconds = blockPeriodSeconds; } + /** + * Sets block period milliseconds. Experimental for test scenarios. Not for use on production + * systems. + * + * @param blockPeriodMilliseconds the block period milliseconds + */ + public void setBlockPeriodMilliseconds(final long blockPeriodMilliseconds) { + this.blockPeriodMilliseconds = blockPeriodMilliseconds; + } + /** * Sets request timeout seconds. * diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/RoundTimer.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/RoundTimer.java index a01dc652cff..0302943fc12 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/RoundTimer.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/RoundTimer.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.consensus.common.bft.events.RoundExpiry; +import java.time.Duration; import java.util.Optional; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -31,21 +32,21 @@ public class RoundTimer { private final BftExecutors bftExecutors; private Optional> currentTimerTask; private final BftEventQueue queue; - private final long baseExpiryMillis; + private final Duration baseExpiryPeriod; /** * Construct a RoundTimer with primed executor service ready to start timers * * @param queue The queue in which to put round expiry events - * @param baseExpirySeconds The initial round length for round 0 + * @param baseExpiryPeriod The initial round length for round 0 * @param bftExecutors executor service that timers can be scheduled with */ public RoundTimer( - final BftEventQueue queue, final long baseExpirySeconds, final BftExecutors bftExecutors) { + final BftEventQueue queue, final Duration baseExpiryPeriod, final BftExecutors bftExecutors) { this.queue = queue; this.bftExecutors = bftExecutors; this.currentTimerTask = Optional.empty(); - this.baseExpiryMillis = baseExpirySeconds * 1000; + this.baseExpiryPeriod = baseExpiryPeriod; } /** Cancels the current running round timer if there is one */ @@ -71,7 +72,8 @@ public synchronized boolean isRunning() { public synchronized void startTimer(final ConsensusRoundIdentifier round) { cancelTimer(); - final long expiryTime = baseExpiryMillis * (long) Math.pow(2, round.getRoundNumber()); + final long expiryTime = + baseExpiryPeriod.toMillis() * (long) Math.pow(2, round.getRoundNumber()); final Runnable newTimerRunnable = () -> queue.add(new RoundExpiry(round)); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundTimerTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundTimerTest.java index 0ebca51c9e2..8f437395471 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundTimerTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundTimerTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.consensus.common.bft.events.RoundExpiry; +import java.time.Duration; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -46,7 +47,7 @@ public void initialise() { bftExecutors = mock(BftExecutors.class); queue = new BftEventQueue(1000); queue.start(); - timer = new RoundTimer(queue, 1, bftExecutors); + timer = new RoundTimer(queue, Duration.ofSeconds(1), bftExecutors); } @Test diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java index 5d2b02b1a7c..8896733548d 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java @@ -100,6 +100,7 @@ import org.hyperledger.besu.util.Subscribers; import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; @@ -403,7 +404,7 @@ private static ControllerAndState createControllerAndFinalState( Util.publicKeyToAddress(nodeKey.getPublicKey()), proposerSelector, multicaster, - new RoundTimer(bftEventQueue, ROUND_TIMER_SEC, bftExecutors), + new RoundTimer(bftEventQueue, Duration.ofSeconds(ROUND_TIMER_SEC), bftExecutors), new BlockTimer(bftEventQueue, forksSchedule, bftExecutors, TestClock.fixed()), blockCreatorFactory, clock); diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactory.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactory.java index 0e71fe81216..2367f90e2be 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactory.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactory.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.TimestampBoundedByFutureParameter; import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.TimestampMoreRecentThanParent; +import java.time.Duration; import java.util.Optional; import org.apache.tuweni.units.bigints.UInt256; @@ -45,32 +46,43 @@ private IbftBlockHeaderValidationRulesetFactory() {} * Produces a BlockHeaderValidator configured for assessing bft block headers which are to form * part of the BlockChain (i.e. not proposed blocks, which do not contain commit seals) * - * @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks. + * @param minimumTimeBetweenBlocks the minimum time which must elapse between blocks. * @param baseFeeMarket an {@link Optional} wrapping {@link BaseFeeMarket} class if appropriate. * @return BlockHeaderValidator configured for assessing bft block headers */ public static BlockHeaderValidator.Builder blockHeaderValidator( - final long secondsBetweenBlocks, final Optional baseFeeMarket) { - return new BlockHeaderValidator.Builder() - .addRule(new AncestryValidationRule()) - .addRule(new GasUsageValidationRule()) - .addRule( - new GasLimitRangeAndDeltaValidationRule( - DEFAULT_MIN_GAS_LIMIT, DEFAULT_MAX_GAS_LIMIT, baseFeeMarket)) - .addRule(new TimestampBoundedByFutureParameter(1)) - .addRule(new TimestampMoreRecentThanParent(secondsBetweenBlocks)) - .addRule( - new ConstantFieldValidationRule<>( - "MixHash", BlockHeader::getMixHash, BftHelpers.EXPECTED_MIX_HASH)) - .addRule( - new ConstantFieldValidationRule<>( - "OmmersHash", BlockHeader::getOmmersHash, Hash.EMPTY_LIST_HASH)) - .addRule( - new ConstantFieldValidationRule<>( - "Difficulty", BlockHeader::getDifficulty, UInt256.ONE)) - .addRule(new ConstantFieldValidationRule<>("Nonce", BlockHeader::getNonce, 0L)) - .addRule(new BftValidatorsValidationRule()) - .addRule(new BftCoinbaseValidationRule()) - .addRule(new BftCommitSealsValidationRule()); + final Duration minimumTimeBetweenBlocks, final Optional baseFeeMarket) { + final BlockHeaderValidator.Builder ruleBuilder = + new BlockHeaderValidator.Builder() + .addRule(new AncestryValidationRule()) + .addRule(new GasUsageValidationRule()) + .addRule( + new GasLimitRangeAndDeltaValidationRule( + DEFAULT_MIN_GAS_LIMIT, DEFAULT_MAX_GAS_LIMIT, baseFeeMarket)) + .addRule(new TimestampBoundedByFutureParameter(1)) + .addRule( + new ConstantFieldValidationRule<>( + "MixHash", BlockHeader::getMixHash, BftHelpers.EXPECTED_MIX_HASH)) + .addRule( + new ConstantFieldValidationRule<>( + "OmmersHash", BlockHeader::getOmmersHash, Hash.EMPTY_LIST_HASH)) + .addRule( + new ConstantFieldValidationRule<>( + "Difficulty", BlockHeader::getDifficulty, UInt256.ONE)) + .addRule(new ConstantFieldValidationRule<>("Nonce", BlockHeader::getNonce, 0L)) + .addRule(new BftValidatorsValidationRule()) + .addRule(new BftCoinbaseValidationRule()) + .addRule(new BftCommitSealsValidationRule()); + + // Currently the minimum acceptable time between blocks is 1 second. The timestamp of an + // Ethereum header is stored as seconds since Unix epoch so blocks being produced more + // frequently than once a second cannot pass this validator. For non-production scenarios + // (e.g. for testing block production much more frequently than once a second) Besu has + // an experimental 'xblockperiodmilliseconds' option for BFT chains. If this is enabled + // we cannot apply the TimestampMoreRecentThanParent validation rule so we do not add it + if (minimumTimeBetweenBlocks.compareTo(Duration.ofSeconds(1)) >= 0) { + ruleBuilder.addRule(new TimestampMoreRecentThanParent(minimumTimeBetweenBlocks.getSeconds())); + } + return ruleBuilder; } } diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleBuilder.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleBuilder.java index 0789f2e8981..3adf5718955 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleBuilder.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleBuilder.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.plugin.services.MetricsSystem; +import java.time.Duration; import java.util.Optional; /** Defines the protocol behaviours for a blockchain using a BFT consensus mechanism. */ @@ -120,6 +121,9 @@ protected BlockHeaderValidator.Builder createBlockHeaderRuleset( Optional.of(feeMarket).filter(FeeMarket::implementsBaseFee).map(BaseFeeMarket.class::cast); return IbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( - config.getBlockPeriodSeconds(), baseFeeMarket); + config.getBlockPeriodMilliseconds() > 0 + ? Duration.ofMillis(config.getBlockPeriodMilliseconds()) + : Duration.ofSeconds(config.getBlockPeriodSeconds()), + baseFeeMarket); } } diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactoryTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactoryTest.java index a7deb9e1973..160fc2a2f39 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactoryTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactoryTest.java @@ -38,6 +38,7 @@ import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -93,7 +94,7 @@ public void bftValidateHeaderPassesWithBaseFee() { final BlockHeaderValidator validator = IbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( - 5, Optional.of(FeeMarket.london(1))) + Duration.ofSeconds(5), Optional.of(FeeMarket.london(1))) .build(); assertThat( @@ -372,7 +373,8 @@ private BlockHeaderTestFixture getPresetHeaderBuilder( } public BlockHeaderValidator getBlockHeaderValidator() { - return IbftBlockHeaderValidationRulesetFactory.blockHeaderValidator(5, Optional.empty()) + return IbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( + Duration.ofSeconds(5), Optional.empty()) .build(); } } diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java index 8b8406b6385..5469717b13f 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java @@ -64,6 +64,7 @@ import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; +import java.time.Duration; import java.time.ZoneId; import java.util.Collections; import java.util.List; @@ -105,7 +106,7 @@ public void createdBlockPassesValidationRulesAndHasAppropriateHashAndMixHash() { public BlockHeaderValidator.Builder createBlockHeaderRuleset( final BftConfigOptions config, final FeeMarket feeMarket) { return IbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( - 5, Optional.empty()); + Duration.ofSeconds(5), Optional.empty()); } }; final GenesisConfigOptions configOptions = @@ -200,7 +201,7 @@ public BlockHeaderValidator.Builder createBlockHeaderRuleset( final BlockHeaderValidator rules = IbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( - secondsBetweenBlocks, Optional.empty()) + Duration.ofSeconds(secondsBetweenBlocks), Optional.empty()) .build(); // NOTE: The header will not contain commit seals, so can only do light validation on header. diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java index 8906f0de7f6..d90d5a15277 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java @@ -118,6 +118,7 @@ import java.io.IOException; import java.nio.file.Path; import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; @@ -512,7 +513,7 @@ private static ControllerAndState createControllerAndFinalState( Util.publicKeyToAddress(nodeKey.getPublicKey()), proposerSelector, multicaster, - new RoundTimer(bftEventQueue, ROUND_TIMER_SEC, bftExecutors), + new RoundTimer(bftEventQueue, Duration.ofSeconds(ROUND_TIMER_SEC), bftExecutors), new BlockTimer(bftEventQueue, forksSchedule, bftExecutors, TestClock.fixed()), blockCreatorFactory, clock); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java index 7320dfaaf7a..ac5ba3ac23a 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.TimestampBoundedByFutureParameter; import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.TimestampMoreRecentThanParent; +import java.time.Duration; import java.util.Optional; import org.apache.tuweni.units.bigints.UInt256; @@ -44,31 +45,42 @@ private QbftBlockHeaderValidationRulesetFactory() {} * Produces a BlockHeaderValidator configured for assessing bft block headers which are to form * part of the BlockChain (i.e. not proposed blocks, which do not contain commit seals) * - * @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks. + * @param minimumTimeBetweenBlocks the minimum amount of time that must elapse between blocks. * @param useValidatorContract whether validator selection is using a validator contract * @param baseFeeMarket an {@link Optional} wrapping {@link BaseFeeMarket} class if appropriate. * @return BlockHeaderValidator configured for assessing bft block headers */ public static BlockHeaderValidator.Builder blockHeaderValidator( - final long secondsBetweenBlocks, + final Duration minimumTimeBetweenBlocks, final boolean useValidatorContract, final Optional baseFeeMarket) { - return new BlockHeaderValidator.Builder() - .addRule(new AncestryValidationRule()) - .addRule(new GasUsageValidationRule()) - .addRule( - new GasLimitRangeAndDeltaValidationRule( - DEFAULT_MIN_GAS_LIMIT, DEFAULT_MAX_GAS_LIMIT, baseFeeMarket)) - .addRule(new TimestampBoundedByFutureParameter(1)) - .addRule(new TimestampMoreRecentThanParent(secondsBetweenBlocks)) - .addRule( - new ConstantFieldValidationRule<>( - "MixHash", BlockHeader::getMixHash, BftHelpers.EXPECTED_MIX_HASH)) - .addRule( - new ConstantFieldValidationRule<>( - "Difficulty", BlockHeader::getDifficulty, UInt256.ONE)) - .addRule(new QbftValidatorsValidationRule(useValidatorContract)) - .addRule(new BftCoinbaseValidationRule()) - .addRule(new BftCommitSealsValidationRule()); + BlockHeaderValidator.Builder ruleBuilder = + new BlockHeaderValidator.Builder() + .addRule(new AncestryValidationRule()) + .addRule(new GasUsageValidationRule()) + .addRule( + new GasLimitRangeAndDeltaValidationRule( + DEFAULT_MIN_GAS_LIMIT, DEFAULT_MAX_GAS_LIMIT, baseFeeMarket)) + .addRule(new TimestampBoundedByFutureParameter(1)) + .addRule( + new ConstantFieldValidationRule<>( + "MixHash", BlockHeader::getMixHash, BftHelpers.EXPECTED_MIX_HASH)) + .addRule( + new ConstantFieldValidationRule<>( + "Difficulty", BlockHeader::getDifficulty, UInt256.ONE)) + .addRule(new QbftValidatorsValidationRule(useValidatorContract)) + .addRule(new BftCoinbaseValidationRule()) + .addRule(new BftCommitSealsValidationRule()); + + // Currently the minimum acceptable time between blocks is 1 second. The timestamp of an + // Ethereum header is stored as seconds since Unix epoch so blocks being produced more + // frequently than once a second cannot pass this validator. For non-production scenarios + // (e.g. for testing block production much more frequently than once a second) Besu has + // an experimental 'xblockperiodmilliseconds' option for BFT chains. If this is enabled + // we cannot apply the TimestampMoreRecentThanParent validation rule so we do not add it + if (minimumTimeBetweenBlocks.compareTo(Duration.ofSeconds(1)) >= 0) { + ruleBuilder.addRule(new TimestampMoreRecentThanParent(minimumTimeBetweenBlocks.getSeconds())); + } + return ruleBuilder; } } 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 90448f62015..0f0d467de37 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.getBlockPeriodMilliseconds().ifPresent(bftConfigOptions::setBlockPeriodMilliseconds); fork.getBlockRewardWei().ifPresent(bftConfigOptions::setBlockRewardWei); if (fork.isMiningBeneficiaryConfigured()) { diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleBuilder.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleBuilder.java index 44c7ddfba8c..e1cbc134b6f 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleBuilder.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleBuilder.java @@ -33,6 +33,7 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.plugin.services.MetricsSystem; +import java.time.Duration; import java.util.Optional; /** Defines the protocol behaviours for a blockchain using a QBFT consensus mechanism. */ @@ -164,7 +165,9 @@ protected BlockHeaderValidator.Builder createBlockHeaderRuleset( Optional.of(feeMarket).filter(FeeMarket::implementsBaseFee).map(BaseFeeMarket.class::cast); return QbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( - qbftConfigOptions.getBlockPeriodSeconds(), + qbftConfigOptions.getBlockPeriodMilliseconds() > 0 + ? Duration.ofMillis(qbftConfigOptions.getBlockPeriodMilliseconds()) + : Duration.ofSeconds(qbftConfigOptions.getBlockPeriodSeconds()), qbftConfigOptions.isValidatorContractMode(), baseFeeMarket); } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java index 4771cf91cbd..ae1fbc0f19b 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java @@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -92,7 +93,7 @@ public void bftValidateHeaderPassesWithBaseFee() { final BlockHeaderValidator validator = QbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( - 5, false, Optional.of(FeeMarket.london(1))) + Duration.ofSeconds(5), false, Optional.of(FeeMarket.london(1))) .build(); assertThat( @@ -366,7 +367,8 @@ blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.FUL } public BlockHeaderValidator getBlockHeaderValidator() { - return QbftBlockHeaderValidationRulesetFactory.blockHeaderValidator(5, false, Optional.empty()) + return QbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( + Duration.ofSeconds(5), false, Optional.empty()) .build(); } }