mirror of
https://github.com/vacp2p/status-linea-besu.git
synced 2026-01-08 23:08:15 -05:00
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:
@@ -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)
|
||||
|
||||
@@ -288,12 +288,18 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
|
||||
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 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
|
||||
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(),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 class BftFork implements Fork {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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 class JsonBftConfigOptions implements BftConfigOptions {
|
||||
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(
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.hyperledger.besu.config;
|
||||
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 class JsonBftConfigOptionsTest {
|
||||
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 class JsonBftConfigOptionsTest {
|
||||
.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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -131,6 +131,11 @@ public abstract class MiningParameters {
|
||||
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 @@ public abstract class MiningParameters {
|
||||
|
||||
OptionalInt getBlockPeriodSeconds();
|
||||
|
||||
OptionalInt getEmptyBlockPeriodSeconds();
|
||||
|
||||
Optional<Address> getCoinbase();
|
||||
|
||||
OptionalLong getTargetGasLimit();
|
||||
@@ -248,6 +255,7 @@ public abstract class MiningParameters {
|
||||
private volatile OptionalLong targetGasLimit;
|
||||
private volatile Optional<Iterable<Long>> nonceGenerator;
|
||||
private volatile OptionalInt blockPeriodSeconds;
|
||||
private volatile OptionalInt emptyBlockPeriodSeconds;
|
||||
|
||||
private MutableRuntimeValues(final MutableInitValues initValues) {
|
||||
miningEnabled = initValues.isMiningEnabled();
|
||||
@@ -259,6 +267,7 @@ public abstract class MiningParameters {
|
||||
targetGasLimit = initValues.getTargetGasLimit();
|
||||
nonceGenerator = initValues.nonceGenerator();
|
||||
blockPeriodSeconds = initValues.getBlockPeriodSeconds();
|
||||
emptyBlockPeriodSeconds = initValues.getEmptyBlockPeriodSeconds();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -274,7 +283,8 @@ public abstract class MiningParameters {
|
||||
&& 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
|
||||
|
||||
@@ -218,7 +218,8 @@ public class PersistBlockTask extends AbstractEthTask<Block> {
|
||||
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(),
|
||||
|
||||
Reference in New Issue
Block a user