Implementing support for emptyBlockPeriodSeconds in QBFT (Issue #3810) (#6965)

Implemented support for emptyBlockPeriodSeconds in QBFT (Issue #3810)

Introduces experimental xemptyblockperiodseconds genesis config option for producing empty blocks at a specific interval independently of the value of the existing blockperiodseconds setting.

https://github.com/hyperledger/besu/issues/3810

---------

Signed-off-by: Antonio Mota <antonio.mota@citi.com>
Signed-off-by: amsmota <amsmota@gmail.com>
This commit is contained in:
amsmota
2024-09-24 12:35:08 +01:00
committed by GitHub
parent e0518c6d94
commit aed6bb0044
20 changed files with 388 additions and 154 deletions

View File

@@ -37,6 +37,8 @@ public class BlockTimer {
private Optional<ScheduledFuture<?>> 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 class 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 class BlockTimer {
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 class BlockTimer {
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 class BlockTimer {
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;
}
}

View File

@@ -31,6 +31,7 @@ import java.util.Optional;
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 class MutableBftConfigOptions implements BftConfigOptions {
return blockPeriodSeconds;
}
@Override
public int getEmptyBlockPeriodSeconds() {
return emptyBlockPeriodSeconds;
}
@Override
public long getBlockPeriodMilliseconds() {
return blockPeriodMilliseconds;
@@ -138,6 +145,15 @@ public class MutableBftConfigOptions implements BftConfigOptions {
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.

View File

@@ -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<BftConfigOptions, BftFork> specCreator = Mockito.mock(SpecCreator.class);
assertThatThrownBy(
@@ -49,9 +49,9 @@ public class ForksScheduleFactoryTest {
@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<BftConfigOptions, BftFork> specCreator = Mockito.mock(SpecCreator.class);
assertThatThrownBy(
@@ -66,12 +66,12 @@ public class ForksScheduleFactoryTest {
public void createsScheduleUsingSpecCreator() {
final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT;
final ForkSpec<BftConfigOptions> 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<BftConfigOptions, BftFork> 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 class ForksScheduleFactoryTest {
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)));
}
}

View File

@@ -75,12 +75,18 @@ public class BlockTimerTest {
@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 class BlockTimerTest {
@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 class BlockTimerTest {
@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 class BlockTimerTest {
@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 class BlockTimerTest {
@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 class BlockTimerTest {
@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 class BlockTimerTest {
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.<ScheduledFuture<?>>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;
}
}

View File

@@ -49,6 +49,7 @@ public class QbftForksSchedulesFactory {
new MutableQbftConfigOptions(lastSpec.getValue());
fork.getBlockPeriodSeconds().ifPresent(bftConfigOptions::setBlockPeriodSeconds);
fork.getEmptyBlockPeriodSeconds().ifPresent(bftConfigOptions::setEmptyBlockPeriodSeconds);
fork.getBlockPeriodMilliseconds().ifPresent(bftConfigOptions::setBlockPeriodMilliseconds);
fork.getBlockRewardWei().ifPresent(bftConfigOptions::setBlockRewardWei);

View File

@@ -28,6 +28,7 @@ import org.hyperledger.besu.consensus.qbft.payload.MessageFactory;
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 class QbftBlockHeightManager implements BaseQbftBlockHeightManager {
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();
}
}
}

View File

@@ -132,17 +132,14 @@ public class QbftRound {
}
/**
* 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 class QbftRound {
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.
*

View File

@@ -37,4 +37,24 @@ public class MutableQbftConfigOptionsTest {
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);
}
}

View File

@@ -157,8 +157,10 @@ public class QbftBlockHeightManagerTest {
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 class QbftBlockHeightManagerTest {
@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 class QbftBlockHeightManagerTest {
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 class QbftBlockHeightManagerTest {
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 class QbftBlockHeightManagerTest {
@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 class QbftBlockHeightManagerTest {
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();
}
}

View File

@@ -29,7 +29,6 @@ import static org.mockito.Mockito.verify;
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.SECPSignature;
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 class QbftRoundTest {
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 class QbftRoundTest {
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);

View File

@@ -63,6 +63,8 @@ public class QbftForksSchedulesFactoryTest
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 class QbftForksSchedulesFactoryTest
final Map<String, Object> 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");