Skip to content

Commit

Permalink
Dev/test option for short BFT block periods (#7588)
Browse files Browse the repository at this point in the history
* Dev mode for short BFT block periods

Signed-off-by: Matthew Whitehead <[email protected]>

* Refactoring

Signed-off-by: Matthew Whitehead <[email protected]>

* Fix comment

Signed-off-by: Matthew Whitehead <[email protected]>

* Refactor to make BFT block milliseconds an experimental QBFT config option

Signed-off-by: Matthew Whitehead <[email protected]>

* Update Json BFT config options

Signed-off-by: Matthew Whitehead <[email protected]>

---------

Signed-off-by: Matthew Whitehead <[email protected]>
  • Loading branch information
matthew1001 authored Sep 20, 2024
1 parent e721237 commit 19d3ca8
Show file tree
Hide file tree
Showing 19 changed files with 181 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
13 changes: 13 additions & 0 deletions config/src/main/java/org/hyperledger/besu/config/BftFork.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

Expand Down Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -133,6 +140,9 @@ public Map<String, Object> 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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<? extends BftConfigOptions> forksSchedule;
private final BftExecutors bftExecutors;
private Optional<ScheduledFuture<?>> currentTimerTask;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -68,6 +70,11 @@ public int getBlockPeriodSeconds() {
return blockPeriodSeconds;
}

@Override
public long getBlockPeriodMilliseconds() {
return blockPeriodMilliseconds;
}

@Override
public int getRequestTimeoutSeconds() {
return requestTimeoutSeconds;
Expand Down Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,21 +32,21 @@ public class RoundTimer {
private final BftExecutors bftExecutors;
private Optional<ScheduledFuture<?>> 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 */
Expand All @@ -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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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> 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> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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);
}
}
Loading

0 comments on commit 19d3ca8

Please sign in to comment.