From 10e4c3ceb5cac1120886baeb77cd1e2cf28b5e89 Mon Sep 17 00:00:00 2001 From: Bhanu Pulluri Date: Fri, 7 Mar 2025 16:56:11 -0500 Subject: [PATCH 1/4] multicast RC message every 5 seconds Signed-off-by: Bhanu Pulluri --- .../besu/consensus/common/bft/RoundTimer.java | 18 ++++++++-- .../qbft/network/QbftMessageTransmitter.java | 36 +++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) 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 0302943fc12..3cd8f5e244d 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 @@ -34,6 +34,8 @@ public class RoundTimer { private final BftEventQueue queue; private final Duration baseExpiryPeriod; + private Optional> frequentRCMulticastTask = Optional.empty(); + /** * Construct a RoundTimer with primed executor service ready to start timers * @@ -53,6 +55,8 @@ public RoundTimer( public synchronized void cancelTimer() { currentTimerTask.ifPresent(t -> t.cancel(false)); currentTimerTask = Optional.empty(); + + cancelRCMulticastTask(); } /** @@ -83,12 +87,22 @@ public synchronized void startTimer(final ConsensusRoundIdentifier round) { // Once we are up to round 2 start logging round expiries if (round.getRoundNumber() >= 2) { LOG.info( - "BFT round {} expired. Moved to round {} which will expire in {} seconds", - round.getRoundNumber() - 1, + "Moved to round {} which will expire in {} seconds", round.getRoundNumber(), (expiryTime / 1000)); } currentTimerTask = Optional.of(newTimerTask); } + + public synchronized void setRCMulticastTask(final ScheduledFuture rcMulticastTask) { + // Cancel any existing multicast task before setting a new one + cancelRCMulticastTask(); + frequentRCMulticastTask = Optional.of(rcMulticastTask); + } + + private synchronized void cancelRCMulticastTask() { + frequentRCMulticastTask.ifPresent(t -> t.cancel(false)); + frequentRCMulticastTask = Optional.empty(); + } } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/network/QbftMessageTransmitter.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/network/QbftMessageTransmitter.java index 7c5b93c24a7..5b6042d21f0 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/network/QbftMessageTransmitter.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/network/QbftMessageTransmitter.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.consensus.qbft.network; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; +import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.common.bft.network.ValidatorMulticaster; import org.hyperledger.besu.consensus.common.bft.payload.SignedData; import org.hyperledger.besu.consensus.qbft.messagedata.CommitMessageData; @@ -36,6 +37,10 @@ import java.util.List; import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +52,7 @@ public class QbftMessageTransmitter { private final MessageFactory messageFactory; private final ValidatorMulticaster multicaster; + private final ScheduledExecutorService scheduledExecutorService; /** * Instantiates a new Qbft message transmitter. @@ -58,6 +64,7 @@ public QbftMessageTransmitter( final MessageFactory messageFactory, final ValidatorMulticaster multicaster) { this.messageFactory = messageFactory; this.multicaster = multicaster; + scheduledExecutorService = Executors.newScheduledThreadPool(1); } /** @@ -133,14 +140,39 @@ public void multicastCommit( */ public void multicastRoundChange( final ConsensusRoundIdentifier roundIdentifier, - final Optional preparedRoundCertificate) { + final Optional preparedRoundCertificate, + final RoundTimer roundTimer) { + try { + + // Schedule periodic multicasting every 5 seconds + final long interval = 5000; // 5 seconds in milliseconds + + final ScheduledFuture[] scheduledTask = new ScheduledFuture[1]; + + // Create the RoundChange message data final RoundChange data = messageFactory.createRoundChange(roundIdentifier, preparedRoundCertificate); final RoundChangeMessageData message = RoundChangeMessageData.create(data); - multicaster.send(message); + // Define the periodic multicast task + final Runnable periodicMulticast = + new Runnable() { + @Override + public void run() { + LOG.info("Broadcasting round change every 5 seconds for round {}", roundIdentifier); + + multicaster.send(message); + } + }; + + // Schedule the periodic task + scheduledTask[0] = + scheduledExecutorService.scheduleAtFixedRate( + periodicMulticast, 0, interval, TimeUnit.MILLISECONDS); + roundTimer.setRCMulticastTask(scheduledTask[0]); + } catch (final SecurityModuleException e) { LOG.warn("Failed to generate signature for RoundChange (not sent): {} ", e.getMessage()); } From 4e7ae8abbec03d878f775817ed559ae30fd13af0 Mon Sep 17 00:00:00 2001 From: Bhanu Pulluri Date: Wed, 12 Mar 2025 00:57:14 -0400 Subject: [PATCH 2/4] update RC message frequent multicast and tests Signed-off-by: Bhanu Pulluri --- .../node/configuration/BesuNodeFactory.java | 57 ++++++++++ .../bft/BftRecoveryAcceptanceTest.java | 88 +++++++++++++++ .../org/hyperledger/besu/cli/BesuCommand.java | 3 +- .../besu/cli/options/QBFTOptions.java | 17 ++- .../controller/BesuControllerBuilder.java | 14 +++ .../controller/QbftBesuControllerBuilder.java | 10 +- .../bft/FrequentMessageMulticaster.java | 102 ++++++++++++++++++ .../besu/consensus/common/bft/RoundTimer.java | 15 --- .../bft/FrequentMessageMulticasterTest.java | 90 ++++++++++++++++ .../qbft/core/support/TestContextBuilder.java | 5 +- .../core/network/QbftMessageTransmitter.java | 34 +----- .../statemachine/QbftBlockHeightManager.java | 42 ++------ .../QbftBlockHeightManagerFactory.java | 40 ++----- .../core/statemachine/QbftController.java | 6 ++ .../qbft/core/types/QbftFinalState.java | 21 ++++ .../qbft/adaptor/QbftFinalStateImpl.java | 41 ++++++- 16 files changed, 465 insertions(+), 120 deletions(-) create mode 100644 acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftRecoveryAcceptanceTest.java create mode 100644 consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticaster.java create mode 100644 consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticasterTest.java diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index 7418dcb043e..7ed6dfcd25e 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -490,6 +490,42 @@ public BesuNode createQbftNode( return create(builder.build()); } + public BesuNode createQbftNodeWithExtraCliOptions( + final String name, + final boolean fixedPort, + final DataStorageFormat storageFormat, + final List extraCliOptions) + throws IOException { + JsonRpcConfiguration rpcConfig = node.createJsonRpcWithQbftEnabledConfig(false); + rpcConfig.addRpcApi("ADMIN,TXPOOL"); + if (fixedPort) { + rpcConfig.setPort( + Math.abs(name.hashCode() % 60000) + + 1024); // Generate a consistent port for p2p based on node name + } + BesuNodeConfigurationBuilder builder = + new BesuNodeConfigurationBuilder() + .name(name) + .miningEnabled() + .devMode(false) + .jsonRpcConfiguration(rpcConfig) + .webSocketConfiguration(node.createWebSocketEnabledConfig()) + .dataStorageConfiguration( + storageFormat == DataStorageFormat.FOREST + ? DataStorageConfiguration.DEFAULT_FOREST_CONFIG + : DataStorageConfiguration.DEFAULT_BONSAI_CONFIG) + .genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig) + .extraCLIOptions(extraCliOptions); + if (fixedPort) { + builder.p2pPort( + Math.abs(name.hashCode() % 60000) + + 1024 + + 500); // Generate a consistent port for p2p based on node name (+ 500 to avoid + // clashing with RPC port or other nodes with a similar name) + } + return create(builder.build()); + } + public BesuNode createCustomGenesisNode( final String name, final String genesisPath, final boolean canBeBootnode) throws IOException { return createCustomGenesisNode(name, genesisPath, canBeBootnode, false); @@ -622,6 +658,27 @@ public BesuNode createQbftNodeWithValidators(final String name, final String... .build()); } + public BesuNode createQbftNodeWithValidatorsAndExtraCLIOptions( + final String name, final List extraCLIOptions, final String... validators) + throws IOException { + + return create( + new BesuNodeConfigurationBuilder() + .name(name) + .miningEnabled() + .jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false)) + .webSocketConfiguration(node.createWebSocketEnabledConfig()) + .devMode(false) + .genesisConfigProvider( + nodes -> + node.createGenesisConfigForValidators( + asList(validators), + nodes, + GenesisConfigurationFactory::createQbftGenesisConfig)) + .extraCLIOptions(extraCLIOptions) + .build()); + } + public BesuNode createQbftNodeWithContractBasedValidators( final String name, final String... validators) throws IOException { return create( diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftRecoveryAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftRecoveryAcceptanceTest.java new file mode 100644 index 00000000000..465b9d58156 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftRecoveryAcceptanceTest.java @@ -0,0 +1,88 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.bft; + +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class BftRecoveryAcceptanceTest extends AcceptanceTestBase { + + private static final long THREE_MINUTES = Duration.of(3, ChronoUnit.MINUTES).toMillis(); + private static final long TEN_SECONDS = Duration.of(10, ChronoUnit.SECONDS).toMillis(); + + @Test + public void shouldBeRecoverFast() throws Exception { + + List extraCLIOptions = + List.of("--Xqbft-fast-recovery", "--Xqbft-enable-early-round-change"); + + // Create a mix of Bonsai and Forest DB nodes + final BesuNode minerNode1 = + besu.createQbftNodeWithExtraCliOptions( + "miner1", true, DataStorageFormat.BONSAI, extraCLIOptions); + final BesuNode minerNode2 = + besu.createQbftNodeWithExtraCliOptions( + "miner2", true, DataStorageFormat.BONSAI, extraCLIOptions); + final BesuNode minerNode3 = + besu.createQbftNodeWithExtraCliOptions( + "miner3", true, DataStorageFormat.BONSAI, extraCLIOptions); + final BesuNode minerNode4 = + besu.createQbftNodeWithExtraCliOptions( + "miner4", true, DataStorageFormat.BONSAI, extraCLIOptions); + + cluster.start(minerNode1, minerNode2, minerNode3, minerNode4); + + cluster.verify(blockchain.reachesHeight(minerNode1, 20, 85)); + + stopNode(minerNode3); + stopNode(minerNode4); + + /* Let the other two nodes go to round 5 */ + Thread.sleep(THREE_MINUTES); + + startNode(minerNode3); + startNode(minerNode4); + + /* Mining should start in few seconds */ + cluster.verify(blockchain.reachesHeight(minerNode3, 1, 20)); + } + + // Start a node with a delay before returning to give it time to start + private void startNode(final BesuNode node) throws InterruptedException { + cluster.startNode(node); + Thread.sleep(TEN_SECONDS); + } + + // Stop a node with a delay before returning to give it time to stop + private void stopNode(final BesuNode node) throws InterruptedException { + cluster.stopNode(node); + Thread.sleep(TEN_SECONDS); + } + + @AfterEach + @Override + public void tearDownAcceptanceTestBase() { + cluster.stop(); + super.tearDownAcceptanceTestBase(); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 9fd6a53cdb6..785838771c6 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -62,13 +62,13 @@ import org.hyperledger.besu.cli.options.PermissionsOptions; import org.hyperledger.besu.cli.options.PluginsConfigurationOptions; import org.hyperledger.besu.cli.options.PrivacyPluginOptions; +import org.hyperledger.besu.cli.options.QBFTOptions; import org.hyperledger.besu.cli.options.RPCOptions; import org.hyperledger.besu.cli.options.RpcWebsocketOptions; import org.hyperledger.besu.cli.options.SynchronizerOptions; import org.hyperledger.besu.cli.options.TransactionPoolOptions; import org.hyperledger.besu.cli.options.storage.DataStorageOptions; import org.hyperledger.besu.cli.options.storage.DiffBasedSubStorageOptions; -import org.hyperledger.besu.cli.options.unstable.QBFTOptions; import org.hyperledger.besu.cli.presynctasks.PreSynchronizationTaskRunner; import org.hyperledger.besu.cli.presynctasks.PrivateDatabaseMigrationPreSyncTask; import org.hyperledger.besu.cli.subcommands.PasswordSubCommand; @@ -1802,6 +1802,7 @@ public BesuControllerBuilder setupControllerBuilder() { .isRevertReasonEnabled(isRevertReasonEnabled) .storageProvider(storageProvider) .isEarlyRoundChangeEnabled(unstableQbftOptions.isEarlyRoundChangeEnabled()) + .isFastRecoveryEnabled(unstableQbftOptions.isFastRecoveryEnabled()) .requiredBlocks(requiredBlocks) .reorgLoggingThreshold(reorgLoggingThreshold) .evmConfiguration(unstableEvmOptions.toDomainObject()) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/QBFTOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/QBFTOptions.java index bb2954b5981..650cb172777 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/QBFTOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/QBFTOptions.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.cli.options.unstable; +package org.hyperledger.besu.cli.options; import picocli.CommandLine; @@ -38,6 +38,12 @@ public static QBFTOptions create() { hidden = true) private boolean enableEarlyRoundChange = false; + @CommandLine.Option( + names = {"--Xqbft-fast-recovery"}, + description = "Enable fast recovery mode for QBFT consensus (experimental)", + hidden = true) + private boolean enableFastRecovery = false; + /** * Is early round change enabled boolean. * @@ -46,4 +52,13 @@ public static QBFTOptions create() { public boolean isEarlyRoundChangeEnabled() { return enableEarlyRoundChange; } + + /** + * Is fast recovery enabled boolean. + * + * @return true if fast recovery is enabled + */ + public boolean isFastRecoveryEnabled() { + return enableFastRecovery; + } } diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 3af1210345e..49092f852cd 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -218,6 +218,9 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides /** When enabled, round changes on f+1 RC messages from higher rounds */ protected boolean isEarlyRoundChangeEnabled = false; + /** When enabled, qbft recovery for nodes coming up is faster */ + protected boolean isFastRecoveryEnabled = false; + /** Instantiates a new Besu controller builder. */ protected BesuControllerBuilder() {} @@ -552,6 +555,17 @@ public BesuControllerBuilder isEarlyRoundChangeEnabled(final boolean isEarlyRoun return this; } + /** + * set fast recovery enabled for QBFT consensus + * + * @param fastRecoveryEnabled whether to enable fast recovery + * @return the besu controller + */ + public BesuControllerBuilder isFastRecoveryEnabled(final boolean fastRecoveryEnabled) { + this.isFastRecoveryEnabled = fastRecoveryEnabled; + return this; + } + /** * Build besu controller. * 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 4e631dc0e88..5de6a2ac641 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java @@ -32,11 +32,13 @@ import org.hyperledger.besu.consensus.common.bft.BlockTimer; import org.hyperledger.besu.consensus.common.bft.EthSynchronizerUpdater; import org.hyperledger.besu.consensus.common.bft.EventMultiplexer; +import org.hyperledger.besu.consensus.common.bft.FrequentMessageMulticaster; import org.hyperledger.besu.consensus.common.bft.MessageTracker; import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.common.bft.UniqueMessageMulticaster; import org.hyperledger.besu.consensus.common.bft.blockcreation.BftMiningCoordinator; import org.hyperledger.besu.consensus.common.bft.blockcreation.ProposerSelector; +import org.hyperledger.besu.consensus.common.bft.network.ValidatorMulticaster; import org.hyperledger.besu.consensus.common.bft.network.ValidatorPeers; import org.hyperledger.besu.consensus.common.bft.protocol.BftProtocolManager; import org.hyperledger.besu.consensus.common.bft.statemachine.BftEventHandler; @@ -233,6 +235,7 @@ protected MiningCoordinator createMiningCoordinator( // "only send once" filter applied by the UniqueMessageMulticaster. peers = new ValidatorPeers(validatorProvider, Istanbul100SubProtocol.NAME); + final ValidatorMulticaster frequentRCMulticaster = new FrequentMessageMulticaster(peers, 5000); final UniqueMessageMulticaster uniqueMessageMulticaster = new UniqueMessageMulticaster(peers, qbftConfig.getGossipedHistoryLimit()); @@ -251,7 +254,10 @@ protected MiningCoordinator createMiningCoordinator( bftExecutors), new BlockTimer(bftEventQueue, qbftForksSchedule, bftExecutors, clock), new QbftBlockCreatorFactoryAdaptor(blockCreatorFactory, qbftExtraDataCodec), - clock); + clock, + isEarlyRoundChangeEnabled, + isFastRecoveryEnabled, + frequentRCMulticaster); final MessageValidatorFactory messageValidatorFactory = new MessageValidatorFactory(proposerSelector, qbftProtocolSchedule, qbftProtocolContext); @@ -292,8 +298,6 @@ protected MiningCoordinator createMiningCoordinator( new QbftValidatorModeTransitionLoggerAdaptor( new ValidatorModeTransitionLogger(qbftForksSchedule))); - qbftBlockHeightManagerFactory.isEarlyRoundChangeEnabled(isEarlyRoundChangeEnabled); - final QbftEventHandler qbftController = new QbftController( new QbftBlockchainAdaptor(blockchain), diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticaster.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticaster.java new file mode 100644 index 00000000000..fe1278be05e --- /dev/null +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticaster.java @@ -0,0 +1,102 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.common.bft; + +import org.hyperledger.besu.consensus.common.bft.network.ValidatorMulticaster; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** The Frequent message multicaster. */ +public class FrequentMessageMulticaster implements ValidatorMulticaster { + private final ValidatorMulticaster multicaster; + private final long interval; + private final ScheduledExecutorService scheduledExecutorService; + private volatile ScheduledFuture scheduledTask; + private static final Logger LOG = LoggerFactory.getLogger(FrequentMessageMulticaster.class); + + /** + * Constructor that specifies the interval at which to multicast messages + * + * @param multicaster Network connections to the remote validators + * @param multicastInterval Interval at which to multicast messages + */ + public FrequentMessageMulticaster( + final ValidatorMulticaster multicaster, final long multicastInterval) { + this.multicaster = multicaster; + this.interval = multicastInterval; + this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + } + + /** + * Instantiates a new Frequent message multicaster. + * + * @param multicaster the multicaster + */ + @VisibleForTesting + public FrequentMessageMulticaster(final ValidatorMulticaster multicaster) { + this(multicaster, 5000); + } + + private Runnable createPeriodicMulticastTask( + final MessageData message, final Collection
denylist) { + return () -> { + LOG.debug( + "Broadcasting round change every {} ms on thread {}", + interval, + Thread.currentThread().threadId()); + multicaster.send(message, denylist); + }; + } + + @Override + public synchronized void send(final MessageData message) { + send(message, Collections.emptyList()); + } + + @Override + public synchronized void send(final MessageData message, final Collection
denylist) { + // Cancel the existing task before scheduling a new one + stopFrequentMulticasting(); + + // Define the periodic multicast task + final Runnable periodicMulticast = createPeriodicMulticastTask(message, denylist); + + // Schedule the periodic task + scheduledTask = + scheduledExecutorService.scheduleAtFixedRate( + periodicMulticast, 0, interval, TimeUnit.MILLISECONDS); + + LOG.debug("Scheduled new frequent multicast task for message."); + } + + public synchronized void stopFrequentMulticasting() { + if (scheduledTask != null) { + LOG.debug("Cancelling existing frequent multicast task."); + scheduledTask.cancel(true); + scheduledTask = null; + } + } +} 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 3cd8f5e244d..78cfedfa98b 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 @@ -34,8 +34,6 @@ public class RoundTimer { private final BftEventQueue queue; private final Duration baseExpiryPeriod; - private Optional> frequentRCMulticastTask = Optional.empty(); - /** * Construct a RoundTimer with primed executor service ready to start timers * @@ -55,8 +53,6 @@ public RoundTimer( public synchronized void cancelTimer() { currentTimerTask.ifPresent(t -> t.cancel(false)); currentTimerTask = Optional.empty(); - - cancelRCMulticastTask(); } /** @@ -94,15 +90,4 @@ public synchronized void startTimer(final ConsensusRoundIdentifier round) { currentTimerTask = Optional.of(newTimerTask); } - - public synchronized void setRCMulticastTask(final ScheduledFuture rcMulticastTask) { - // Cancel any existing multicast task before setting a new one - cancelRCMulticastTask(); - frequentRCMulticastTask = Optional.of(rcMulticastTask); - } - - private synchronized void cancelRCMulticastTask() { - frequentRCMulticastTask.ifPresent(t -> t.cancel(false)); - frequentRCMulticastTask = Optional.empty(); - } } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticasterTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticasterTest.java new file mode 100644 index 00000000000..fe13c32cd6d --- /dev/null +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticasterTest.java @@ -0,0 +1,90 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.common.bft; + +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.consensus.common.bft.network.ValidatorMulticaster; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class FrequentMessageMulticasterTest { + private FrequentMessageMulticaster multicaster; + private ValidatorMulticaster mockValidatorMulticaster; + + @BeforeEach + void setUp() { + mockValidatorMulticaster = mock(ValidatorMulticaster.class); + multicaster = new FrequentMessageMulticaster(mockValidatorMulticaster, 5000); + } + + @AfterEach + void tearDown() { + multicaster.stopFrequentMulticasting(); + } + + @Test + void testMessageIsSentPeriodically() throws InterruptedException { + MessageData message = mock(MessageData.class); + + multicaster.send(message); + + // Allow time for multiple executions + TimeUnit.MILLISECONDS.sleep(12000); + + // The message should have been sent multiple times + verify(mockValidatorMulticaster, atLeast(2)).send(message, Collections.emptyList()); + } + + @Test + void testNewMessageReplacesOldTask() throws InterruptedException { + MessageData message1 = mock(MessageData.class); + MessageData message2 = mock(MessageData.class); + + multicaster.send(message1); + TimeUnit.MILLISECONDS.sleep(2000); // Allow initial message to start + + multicaster.send(message2); // This should replace the old task + + // Wait and check if message1 was stopped + TimeUnit.MILLISECONDS.sleep(7000); + + verify(mockValidatorMulticaster, atMost(2)).send(message1, Collections.emptyList()); + verify(mockValidatorMulticaster, atLeast(1)).send(message2, Collections.emptyList()); + } + + @Test + void testCancelStopsFrequentMulticasting() throws InterruptedException { + MessageData message = mock(MessageData.class); + + multicaster.send(message); + TimeUnit.MILLISECONDS.sleep(7000); + + multicaster.stopFrequentMulticasting(); + + TimeUnit.MILLISECONDS.sleep(7000); + + verify(mockValidatorMulticaster, atMost(3)).send(message, Collections.emptyList()); + } +} diff --git a/consensus/qbft-core/src/integration-test/java/org/hyperledger/besu/consensus/qbft/core/support/TestContextBuilder.java b/consensus/qbft-core/src/integration-test/java/org/hyperledger/besu/consensus/qbft/core/support/TestContextBuilder.java index e55e467de13..bf27a414d72 100644 --- a/consensus/qbft-core/src/integration-test/java/org/hyperledger/besu/consensus/qbft/core/support/TestContextBuilder.java +++ b/consensus/qbft-core/src/integration-test/java/org/hyperledger/besu/consensus/qbft/core/support/TestContextBuilder.java @@ -541,7 +541,10 @@ private static ControllerAndState createControllerAndFinalState( new RoundTimer(bftEventQueue, Duration.ofSeconds(ROUND_TIMER_SEC), bftExecutors), new BlockTimer(bftEventQueue, forksSchedule, bftExecutors, TestClock.fixed()), new QbftBlockCreatorFactoryAdaptor(blockCreatorFactory, BFT_EXTRA_DATA_ENCODER), - clock); + clock, + false, + false, + multicaster); final MessageFactory messageFactory = new MessageFactory(nodeKey, blockEncoder); diff --git a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/network/QbftMessageTransmitter.java b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/network/QbftMessageTransmitter.java index 4d99f3f7cac..d2debe905d1 100644 --- a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/network/QbftMessageTransmitter.java +++ b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/network/QbftMessageTransmitter.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.consensus.qbft.core.network; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; -import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.common.bft.network.ValidatorMulticaster; import org.hyperledger.besu.consensus.common.bft.payload.SignedData; import org.hyperledger.besu.consensus.qbft.core.messagedata.CommitMessageData; @@ -37,10 +36,6 @@ import java.util.List; import java.util.Optional; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +47,6 @@ public class QbftMessageTransmitter { private final MessageFactory messageFactory; private final ValidatorMulticaster multicaster; - private final ScheduledExecutorService scheduledExecutorService; /** * Instantiates a new Qbft message transmitter. @@ -64,7 +58,6 @@ public QbftMessageTransmitter( final MessageFactory messageFactory, final ValidatorMulticaster multicaster) { this.messageFactory = messageFactory; this.multicaster = multicaster; - scheduledExecutorService = Executors.newScheduledThreadPool(1); } /** @@ -140,38 +133,15 @@ public void multicastCommit( */ public void multicastRoundChange( final ConsensusRoundIdentifier roundIdentifier, - final Optional preparedRoundCertificate, - final RoundTimer roundTimer) { + final Optional preparedRoundCertificate) { try { - - // Schedule periodic multicasting every 5 seconds - final long interval = 5000; // 5 seconds in milliseconds - - final ScheduledFuture[] scheduledTask = new ScheduledFuture[1]; - - // Create the RoundChange message data final RoundChange data = messageFactory.createRoundChange(roundIdentifier, preparedRoundCertificate); final RoundChangeMessageData message = RoundChangeMessageData.create(data); - // Define the periodic multicast task - final Runnable periodicMulticast = - new Runnable() { - @Override - public void run() { - LOG.info("Broadcasting round change every 5 seconds for round {}", roundIdentifier); - - multicaster.send(message); - } - }; - - // Schedule the periodic task - scheduledTask[0] = - scheduledExecutorService.scheduleAtFixedRate( - periodicMulticast, 0, interval, TimeUnit.MILLISECONDS); - roundTimer.setRCMulticastTask(scheduledTask[0]); + multicaster.send(message); } catch (final SecurityModuleException e) { LOG.warn("Failed to generate signature for RoundChange (not sent): {} ", e.getMessage()); diff --git a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManager.java b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManager.java index a74b9c6d3b8..b6051f45280 100644 --- a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManager.java +++ b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManager.java @@ -68,7 +68,6 @@ public class QbftBlockHeightManager implements BaseQbftBlockHeightManager { private Optional latestPreparedCertificate = Optional.empty(); private Optional currentRound = Optional.empty(); - private boolean isEarlyRoundChangeEnabled = false; /** * Instantiates a new Qbft block height manager. @@ -92,7 +91,11 @@ public QbftBlockHeightManager( this.parentHeader = parentHeader; this.roundFactory = qbftRoundFactory; this.transmitter = - new QbftMessageTransmitter(messageFactory, finalState.getValidatorMulticaster()); + new QbftMessageTransmitter( + messageFactory, + (finalState.isFastRecoveryEnabled() + ? finalState.getFrequentRCMulticaster() + : finalState.getValidatorMulticaster())); this.messageFactory = messageFactory; this.clock = clock; this.roundChangeManager = roundChangeManager; @@ -116,39 +119,6 @@ public QbftBlockHeightManager( finalState.getBlockTimer().startTimer(roundIdentifier, parentHeader::getTimestamp); } - /** - * Instantiates a new Qbft block height manager. Secondary constructor with early round change - * option. - * - * @param parentHeader the parent header - * @param finalState the final state - * @param roundChangeManager the round change manager - * @param qbftRoundFactory the qbft round factory - * @param clock the clock - * @param messageValidatorFactory the message validator factory - * @param messageFactory the message factory - * @param isEarlyRoundChangeEnabled enable round change when f+1 RC messages are received - */ - public QbftBlockHeightManager( - final QbftBlockHeader parentHeader, - final QbftFinalState finalState, - final RoundChangeManager roundChangeManager, - final QbftRoundFactory qbftRoundFactory, - final Clock clock, - final MessageValidatorFactory messageValidatorFactory, - final MessageFactory messageFactory, - final boolean isEarlyRoundChangeEnabled) { - this( - parentHeader, - finalState, - roundChangeManager, - qbftRoundFactory, - clock, - messageValidatorFactory, - messageFactory); - this.isEarlyRoundChangeEnabled = isEarlyRoundChangeEnabled; - } - @Override public void handleBlockTimerExpiry(final ConsensusRoundIdentifier roundIdentifier) { if (currentRound.isPresent()) { @@ -381,7 +351,7 @@ public void handleRoundChangePayload(final RoundChange message) { final Optional> result = roundChangeManager.appendRoundChangeMessage(message); - if (!isEarlyRoundChangeEnabled) { + if (!finalState.isEarlyRoundChangeEnabled()) { if (result.isPresent()) { LOG.debug( "Received sufficient RoundChange messages to change round to targetRound={}", diff --git a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManagerFactory.java b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManagerFactory.java index cc957513d91..4b1d0e26696 100644 --- a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManagerFactory.java +++ b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManagerFactory.java @@ -34,7 +34,6 @@ public class QbftBlockHeightManagerFactory { private final MessageValidatorFactory messageValidatorFactory; private final MessageFactory messageFactory; private final QbftValidatorModeTransitionLogger validatorModeTransitionLogger; - private boolean isEarlyRoundChangeEnabled = false; /** * Instantiates a new Qbft block height manager factory. @@ -76,15 +75,6 @@ public BaseQbftBlockHeightManager create(final QbftBlockHeader parentHeader) { } } - /** - * Sets early round change enabled. - * - * @param isEarlyRoundChangeEnabled the is early round change enabled - */ - public void isEarlyRoundChangeEnabled(final boolean isEarlyRoundChangeEnabled) { - this.isEarlyRoundChangeEnabled = isEarlyRoundChangeEnabled; - } - private BaseQbftBlockHeightManager createNoOpBlockHeightManager( final QbftBlockHeader parentHeader) { return new NoOpBlockHeightManager(parentHeader); @@ -96,7 +86,7 @@ private BaseQbftBlockHeightManager createFullBlockHeightManager( QbftBlockHeightManager qbftBlockHeightManager; RoundChangeManager roundChangeManager; - if (isEarlyRoundChangeEnabled) { + if (finalState.isEarlyRoundChangeEnabled()) { roundChangeManager = new RoundChangeManager( BftHelpers.calculateRequiredValidatorQuorum(finalState.getValidators().size()), @@ -104,16 +94,6 @@ private BaseQbftBlockHeightManager createFullBlockHeightManager( messageValidatorFactory.createRoundChangeMessageValidator( parentHeader.getNumber() + 1L, parentHeader), finalState.getLocalAddress()); - qbftBlockHeightManager = - new QbftBlockHeightManager( - parentHeader, - finalState, - roundChangeManager, - roundFactory, - finalState.getClock(), - messageValidatorFactory, - messageFactory, - true); } else { roundChangeManager = new RoundChangeManager( @@ -121,16 +101,16 @@ private BaseQbftBlockHeightManager createFullBlockHeightManager( messageValidatorFactory.createRoundChangeMessageValidator( parentHeader.getNumber() + 1L, parentHeader), finalState.getLocalAddress()); - qbftBlockHeightManager = - new QbftBlockHeightManager( - parentHeader, - finalState, - roundChangeManager, - roundFactory, - finalState.getClock(), - messageValidatorFactory, - messageFactory); } + qbftBlockHeightManager = + new QbftBlockHeightManager( + parentHeader, + finalState, + roundChangeManager, + roundFactory, + finalState.getClock(), + messageValidatorFactory, + messageFactory); return qbftBlockHeightManager; } diff --git a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftController.java b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftController.java index c2fdaaf70af..81741278503 100644 --- a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftController.java +++ b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftController.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.consensus.qbft.core.statemachine; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; +import org.hyperledger.besu.consensus.common.bft.FrequentMessageMulticaster; import org.hyperledger.besu.consensus.common.bft.Gossiper; import org.hyperledger.besu.consensus.common.bft.MessageTracker; import org.hyperledger.besu.consensus.common.bft.SynchronizerUpdater; @@ -188,6 +189,11 @@ protected

> void consumeMessage( @Override public void handleNewBlockEvent(final QbftNewChainHead newChainHead) { + if (finalState.isFastRecoveryEnabled()) { + FrequentMessageMulticaster frequentMessageMulticaster = + (FrequentMessageMulticaster) finalState.getFrequentRCMulticaster(); + frequentMessageMulticaster.stopFrequentMulticasting(); + } final QbftBlockHeader newBlockHeader = newChainHead.newChainHeadHeader(); final QbftBlockHeader currentMiningParent = getCurrentHeightManager().getParentBlockHeader(); LOG.debug( diff --git a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/types/QbftFinalState.java b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/types/QbftFinalState.java index 87fbf6decef..56aa30b59b8 100644 --- a/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/types/QbftFinalState.java +++ b/consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/types/QbftFinalState.java @@ -104,4 +104,25 @@ public interface QbftFinalState { * @return true if the local node is the proposer for the given round, false otherwise */ boolean isLocalNodeProposerForRound(ConsensusRoundIdentifier roundIdentifier); + + /** + * Gets the frequent round change multicaster. + * + * @return the frequent round change multicaster + */ + public ValidatorMulticaster getFrequentRCMulticaster(); + + /** + * Is early round change enabled boolean. + * + * @return the boolean + */ + public boolean isEarlyRoundChangeEnabled(); + + /** + * Is fast recovery enabled boolean. + * + * @return the boolean + */ + public boolean isFastRecoveryEnabled(); } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftFinalStateImpl.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftFinalStateImpl.java index 5a3ff17130b..669d2fd2e8c 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftFinalStateImpl.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftFinalStateImpl.java @@ -40,6 +40,9 @@ public class QbftFinalStateImpl implements QbftFinalState { private final BlockTimer blockTimer; private final QbftBlockCreatorFactory blockCreatorFactory; private final Clock clock; + private final boolean isEarlyRoundChangeEnabled; + private final boolean isFastRecoveryEnabled; + private final ValidatorMulticaster frequentRCMulticaster; /** * Constructs a new QBFT final state. @@ -63,7 +66,10 @@ public QbftFinalStateImpl( final RoundTimer roundTimer, final BlockTimer blockTimer, final QbftBlockCreatorFactory blockCreatorFactory, - final Clock clock) { + final Clock clock, + final boolean isEarlyRoundChangeEnabled, + final boolean isFastRecoveryEnabled, + final ValidatorMulticaster frequentRCMulticaster) { this.validatorProvider = validatorProvider; this.nodeKey = nodeKey; this.localAddress = localAddress; @@ -73,6 +79,9 @@ public QbftFinalStateImpl( this.blockTimer = blockTimer; this.blockCreatorFactory = blockCreatorFactory; this.clock = clock; + this.isEarlyRoundChangeEnabled = isEarlyRoundChangeEnabled; + this.isFastRecoveryEnabled = isFastRecoveryEnabled; + this.frequentRCMulticaster = frequentRCMulticaster; } /** @@ -95,6 +104,36 @@ public ValidatorMulticaster getValidatorMulticaster() { return validatorMulticaster; } + /** + * Gets the frequent round change multicaster. + * + * @return the frequent round change multicaster + */ + @Override + public ValidatorMulticaster getFrequentRCMulticaster() { + return frequentRCMulticaster; + } + + /** + * Is early round change enabled boolean. + * + * @return the boolean + */ + @Override + public boolean isEarlyRoundChangeEnabled() { + return isEarlyRoundChangeEnabled; + } + + /** + * Is fast recovery enabled boolean. + * + * @return the boolean + */ + @Override + public boolean isFastRecoveryEnabled() { + return isFastRecoveryEnabled; + } + /** * Gets node key. * From b0be33303a2c4cc6c7ea6d9e945718cd66823316 Mon Sep 17 00:00:00 2001 From: Bhanu Pulluri Date: Wed, 12 Mar 2025 09:32:16 -0400 Subject: [PATCH 3/4] fix compile errors Signed-off-by: Bhanu Pulluri --- .../consensus/common/bft/FrequentMessageMulticaster.java | 5 +++++ .../qbft/core/statemachine/QbftBlockHeightManagerTest.java | 4 ++-- .../besu/consensus/qbft/adaptor/QbftFinalStateImpl.java | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticaster.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticaster.java index fe1278be05e..cd382973fda 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticaster.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/FrequentMessageMulticaster.java @@ -92,6 +92,11 @@ public synchronized void send(final MessageData message, final CollectionStops the current scheduled task. + */ public synchronized void stopFrequentMulticasting() { if (scheduledTask != null) { LOG.debug("Cancelling existing frequent multicast task."); diff --git a/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManagerTest.java b/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManagerTest.java index 6c6155f187f..7fbcdcc09c1 100644 --- a/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManagerTest.java +++ b/consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/statemachine/QbftBlockHeightManagerTest.java @@ -599,6 +599,7 @@ public void roundChangeTriggeredUponReceivingFPlusOneRoundChanges() { new RoundChangeManager(3, 2, roundChangeMessageValidator, validators.get(2)); when(finalState.isLocalNodeProposerForRound(any())).thenReturn(false); + when(finalState.isEarlyRoundChangeEnabled()).thenReturn(true); final QbftBlockHeightManager manager = new QbftBlockHeightManager( @@ -608,8 +609,7 @@ public void roundChangeTriggeredUponReceivingFPlusOneRoundChanges() { roundFactory, clock, messageValidatorFactory, - validatorMessageFactory.get(2), - true); // Enable early round change + validatorMessageFactory.get(2)); manager.handleRoundChangePayload(roundChange1); manager.handleRoundChangePayload(roundChange2); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftFinalStateImpl.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftFinalStateImpl.java index 669d2fd2e8c..d81a35c4ffb 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftFinalStateImpl.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/adaptor/QbftFinalStateImpl.java @@ -56,6 +56,9 @@ public class QbftFinalStateImpl implements QbftFinalState { * @param blockTimer the block timer * @param blockCreatorFactory the block creator factory * @param clock the clock + * @param isEarlyRoundChangeEnabled the early round change enabled flag + * @param isFastRecoveryEnabled the fast recovery enabled flag + * @param frequentRCMulticaster the frequent round change multicaster */ public QbftFinalStateImpl( final ValidatorProvider validatorProvider, From b9734eee043b01245949e61982b86202b98d9623 Mon Sep 17 00:00:00 2001 From: Bhanu Pulluri Date: Wed, 12 Mar 2025 15:06:41 -0400 Subject: [PATCH 4/4] Add fastrecovery checking to mock testing Signed-off-by: Bhanu Pulluri --- .../test/java/org/hyperledger/besu/cli/CommandTestAbstract.java | 1 + 1 file changed, 1 insertion(+) diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index e04ec95ca1c..c86a149385a 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -283,6 +283,7 @@ public void initMocks() throws Exception { when(mockControllerBuilder.isParallelTxProcessingEnabled(false)) .thenReturn(mockControllerBuilder); when(mockControllerBuilder.isEarlyRoundChangeEnabled(false)).thenReturn(mockControllerBuilder); + when(mockControllerBuilder.isFastRecoveryEnabled(false)).thenReturn(mockControllerBuilder); when(mockControllerBuilder.storageProvider(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.requiredBlocks(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.reorgLoggingThreshold(anyLong())).thenReturn(mockControllerBuilder);