Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Round change upon f+1 RC messages (experimental option) #7838

Merged
merged 23 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a9dc833
Fix incorrect duration for THREE_MINUTES from 1 minute to 3 minutes
Oct 17, 2024
1ab1526
Round change upon f+1 RC messages (experimental option)
Oct 31, 2024
468e194
Update besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
pullurib Dec 3, 2024
7eda43f
revert an unrelated fix already merged to main
Dec 3, 2024
bfff0be
update return value description
Dec 3, 2024
d1a2572
fix logging level for couple of logs
Dec 3, 2024
6c6743b
Review changes , added tests
Dec 6, 2024
ab91ca5
Merge branch 'main' into qbft-rc-quorum
Dec 6, 2024
9934488
Merge branch 'main' into qbft-rc-quorum
Dec 6, 2024
7138df1
Merge and fix controller builder test context
Dec 6, 2024
42fc935
Merge branch 'main' into qbft-rc-quorum
pullurib Dec 11, 2024
931e12c
Merge branch 'main' into qbft-rc-quorum
macfarla Dec 17, 2024
14073c9
Merge branch 'main' into qbft-rc-quorum
Jan 23, 2025
4e8d151
Merge branch 'qbft-rc-quorum' of github.com:pullurib/besu into qbft-r…
Jan 23, 2025
802c6d9
minor fix to import missing class after merging main
Jan 23, 2025
4759b67
Merge branch 'main' into qbft-rc-quorum
pullurib Jan 24, 2025
dfde803
Add missing function header comments
Jan 27, 2025
4062bea
Merge branch 'qbft-rc-quorum' of github.com:pullurib/besu into qbft-r…
Jan 27, 2025
5c8d3ab
Merge branch 'main' into qbft-rc-quorum
jframe Jan 28, 2025
9005220
Merge branch 'main' into qbft-rc-quorum
jframe Jan 29, 2025
897bc6e
Merge branch 'main' into qbft-rc-quorum
matthew1001 Jan 30, 2025
8fd9bab
Merge branch 'main' into qbft-rc-quorum
matthew1001 Jan 31, 2025
ecd3d7a
Merge branch 'main' into qbft-rc-quorum
matthew1001 Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import org.hyperledger.besu.cli.options.unstable.NetworkingOptions;
import org.hyperledger.besu.cli.options.unstable.P2PTLSConfigOptions;
import org.hyperledger.besu.cli.options.unstable.PrivacyPluginOptions;
import org.hyperledger.besu.cli.options.unstable.QBFTOptions;
import org.hyperledger.besu.cli.options.unstable.RPCOptions;
import org.hyperledger.besu.cli.options.unstable.SynchronizerOptions;
import org.hyperledger.besu.cli.presynctasks.PreSynchronizationTaskRunner;
Expand Down Expand Up @@ -303,6 +304,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
private final EvmOptions unstableEvmOptions = EvmOptions.create();
private final IpcOptions unstableIpcOptions = IpcOptions.create();
private final ChainPruningOptions unstableChainPruningOptions = ChainPruningOptions.create();
private final QBFTOptions unstableQbftOptions = QBFTOptions.create();

// stable CLI options
final DataStorageOptions dataStorageOptions = DataStorageOptions.create();
Expand Down Expand Up @@ -1162,6 +1164,7 @@ private void handleUnstableOptions() {
.put("EVM Options", unstableEvmOptions)
.put("IPC Options", unstableIpcOptions)
.put("Chain Data Pruning Options", unstableChainPruningOptions)
.put("QBFT Options", unstableQbftOptions)
.build();

UnstableOptionsSubCommand.createUnstableOptions(commandLine, unstableOptions);
Expand Down Expand Up @@ -1806,6 +1809,7 @@ public BesuControllerBuilder setupControllerBuilder() {
.isRevertReasonEnabled(isRevertReasonEnabled)
.isParallelTxProcessingEnabled(
dataStorageConfiguration.getUnstable().isParallelTxProcessingEnabled())
.isEarlyRoundChangeEnabled(unstableQbftOptions.isEarlyRoundChangeEnabled())
.storageProvider(storageProvider)
.gasLimitCalculator(
miningParametersSupplier.get().getTargetGasLimit().isPresent()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.cli.options.unstable;

import picocli.CommandLine;

public class QBFTOptions {

public static QBFTOptions create() {
return new QBFTOptions();
}

@CommandLine.Option(
names = {"--Xqbft-enable-early-round-change"},
description =
"Enable early round change upon receiving f+1 valid future Round Change messages from different validators (experimental)",
hidden = true)
private boolean enableEarlyRoundChange = false;

public boolean isEarlyRoundChangeEnabled() {
return enableEarlyRoundChange;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
/** whether parallel transaction processing is enabled or not */
protected boolean isParallelTxProcessingEnabled;

protected boolean isEarlyRoundChangeEnabled = false;

/** Instantiates a new Besu controller builder. */
protected BesuControllerBuilder() {}

Expand Down Expand Up @@ -529,6 +531,11 @@ public BesuControllerBuilder isParallelTxProcessingEnabled(
return this;
}

public BesuControllerBuilder isEarlyRoundChangeEnabled(final boolean isEarlyRoundChangeEnabled) {
this.isEarlyRoundChangeEnabled = isEarlyRoundChangeEnabled;
return this;
}

/**
* Build besu controller.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,23 +249,28 @@ protected MiningCoordinator createMiningCoordinator(

final MessageFactory messageFactory = new MessageFactory(nodeKey);

final BftEventHandler qbftController =
new QbftController(
blockchain,
QbftBlockHeightManagerFactory qbftBlockHeightManagerFactory =
new QbftBlockHeightManagerFactory(
finalState,
new QbftBlockHeightManagerFactory(
new QbftRoundFactory(
finalState,
new QbftRoundFactory(
finalState,
protocolContext,
bftProtocolSchedule,
minedBlockObservers,
messageValidatorFactory,
messageFactory,
bftExtraDataCodec().get()),
protocolContext,
bftProtocolSchedule,
minedBlockObservers,
messageValidatorFactory,
messageFactory,
new ValidatorModeTransitionLogger(qbftForksSchedule)),
bftExtraDataCodec().get()),
messageValidatorFactory,
messageFactory,
new ValidatorModeTransitionLogger(qbftForksSchedule));

qbftBlockHeightManagerFactory.isEarlyRoundChangeEnabled(isEarlyRoundChangeEnabled);

final BftEventHandler qbftController =
new QbftController(
blockchain,
finalState,
qbftBlockHeightManagerFactory,
gossiper,
duplicateMessageTracker,
futureMessageBuffer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ public static int calculateRequiredValidatorQuorum(final int validatorCount) {
return Util.fastDivCeiling(2 * validatorCount, 3);
}

/**
* Calculate required future RC messages count quorum for a round change.
*
* @param validatorCount the validator count
* @return Required number of future round change messages to reach quorum for a round change.
*/
public static int calculateRequiredFutureRCQuorum(final int validatorCount) {
return validatorCount - Util.fastDivCeiling(2 * validatorCount, 3) + 1;
pullurib marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Prepare message count for quorum.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ 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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public class QbftBlockHeightManager implements BaseQbftBlockHeightManager {

private Optional<PreparedCertificate> latestPreparedCertificate = Optional.empty();
private Optional<QbftRound> currentRound = Optional.empty();
private boolean isEarlyRoundChangeEnabled = false;

/**
* Instantiates a new Qbft block height manager.
Expand Down Expand Up @@ -227,23 +228,36 @@ public void roundExpired(final RoundExpiry expire) {
return;
}

doRoundChange(qbftRound.getRoundIdentifier().getRoundNumber() + 1);
}

private synchronized void doRoundChange(final int newRoundNumber) {
jframe marked this conversation as resolved.
Show resolved Hide resolved

if (currentRound.isPresent()
&& currentRound.get().getRoundIdentifier().getRoundNumber() >= newRoundNumber) {
return;
}
LOG.debug(
"Round has expired, creating PreparedCertificate and notifying peers. round={}",
qbftRound.getRoundIdentifier());
"Round has expired or changing based on RC quorum, creating PreparedCertificate and notifying peers. round={}",
currentRound.get().getRoundIdentifier());
final Optional<PreparedCertificate> preparedCertificate =
qbftRound.constructPreparedCertificate();
currentRound.get().constructPreparedCertificate();

if (preparedCertificate.isPresent()) {
latestPreparedCertificate = preparedCertificate;
}

startNewRound(qbftRound.getRoundIdentifier().getRoundNumber() + 1);
qbftRound = currentRound.get();
startNewRound(newRoundNumber);
if (currentRound.isEmpty()) {
LOG.info("Failed to start round ");
return;
}
QbftRound qbftRoundNew = currentRound.get();

try {
final RoundChange localRoundChange =
messageFactory.createRoundChange(
qbftRound.getRoundIdentifier(), latestPreparedCertificate);
qbftRoundNew.getRoundIdentifier(), latestPreparedCertificate);

// Its possible the locally created RoundChange triggers the transmission of a NewRound
// message - so it must be handled accordingly.
Expand All @@ -252,7 +266,7 @@ public void roundExpired(final RoundExpiry expire) {
LOG.warn("Failed to create signed RoundChange message.", e);
}

transmitter.multicastRoundChange(qbftRound.getRoundIdentifier(), latestPreparedCertificate);
transmitter.multicastRoundChange(qbftRoundNew.getRoundIdentifier(), latestPreparedCertificate);
}

@Override
Expand Down Expand Up @@ -333,24 +347,55 @@ public void handleRoundChangePayload(final RoundChange message) {
final Optional<Collection<RoundChange>> result =
roundChangeManager.appendRoundChangeMessage(message);

if (result.isPresent()) {
LOG.debug(
"Received sufficient RoundChange messages to change round to targetRound={}",
targetRound);
if (messageAge == MessageAge.FUTURE_ROUND) {
startNewRound(targetRound.getRoundNumber());
}
if (!isEarlyRoundChangeEnabled) {
if (result.isPresent()) {
LOG.debug(
"Received sufficient RoundChange messages to change round to targetRound={}",
targetRound);
if (messageAge == MessageAge.FUTURE_ROUND) {
startNewRound(targetRound.getRoundNumber());
}

final RoundChangeArtifacts roundChangeMetadata = RoundChangeArtifacts.create(result.get());
final RoundChangeArtifacts roundChangeMetadata = RoundChangeArtifacts.create(result.get());

if (finalState.isLocalNodeProposerForRound(targetRound)) {
if (currentRound.isEmpty()) {
startNewRound(0);
if (finalState.isLocalNodeProposerForRound(targetRound)) {
if (currentRound.isEmpty()) {
startNewRound(0);
}
currentRound
.get()
.startRoundWith(roundChangeMetadata, TimeUnit.MILLISECONDS.toSeconds(clock.millis()));
}
}
} else {

if (currentRound.isEmpty()) {
startNewRound(0);
}
int currentRoundNumber = currentRound.get().getRoundIdentifier().getRoundNumber();
// If this node is proposer for the current round, check if quorum is achieved for RC messages
// aiming this round
if (targetRound.getRoundNumber() == currentRoundNumber
&& finalState.isLocalNodeProposerForRound(targetRound)
&& result.isPresent()) {

final RoundChangeArtifacts roundChangeMetadata = RoundChangeArtifacts.create(result.get());

currentRound
.get()
.startRoundWith(roundChangeMetadata, TimeUnit.MILLISECONDS.toSeconds(clock.millis()));
}

// check if f+1 RC messages for future rounds are received
QbftRound qbftRound = currentRound.get();
Optional<Integer> nextHigherRound =
roundChangeManager.futureRCQuorumReceived(qbftRound.getRoundIdentifier());
if (nextHigherRound.isPresent()) {
LOG.info(
"Received sufficient RoundChange messages to change round to targetRound={}",
nextHigherRound.get());
doRoundChange(nextHigherRound.get());
}
}
}

Expand Down Expand Up @@ -391,6 +436,10 @@ private MessageAge determineAgeOfPayload(final int messageRoundNumber) {
return MessageAge.PRIOR_ROUND;
}

public void isEarlyRoundChangeEnabled(final boolean isEarlyRoundChangeEnabled) {
this.isEarlyRoundChangeEnabled = isEarlyRoundChangeEnabled;
}

/** The enum Message age. */
public enum MessageAge {
/** Prior round message age. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class QbftBlockHeightManagerFactory {
private final MessageValidatorFactory messageValidatorFactory;
private final MessageFactory messageFactory;
private final ValidatorModeTransitionLogger validatorModeTransitionLogger;
private boolean isEarlyRoundChangeEnabled = false;

/**
* Instantiates a new Qbft block height manager factory.
Expand Down Expand Up @@ -75,22 +76,39 @@ public BaseQbftBlockHeightManager create(final BlockHeader parentHeader) {
}
}

public void isEarlyRoundChangeEnabled(final boolean isEarlyRoundChangeEnabled) {
this.isEarlyRoundChangeEnabled = isEarlyRoundChangeEnabled;
}

private BaseQbftBlockHeightManager createNoOpBlockHeightManager(final BlockHeader parentHeader) {
return new NoOpBlockHeightManager(parentHeader);
}

private BaseQbftBlockHeightManager createFullBlockHeightManager(final BlockHeader parentHeader) {
return new QbftBlockHeightManager(
parentHeader,
finalState,

RoundChangeManager roundChangeManager =
new RoundChangeManager(
BftHelpers.calculateRequiredValidatorQuorum(finalState.getValidators().size()),
messageValidatorFactory.createRoundChangeMessageValidator(
parentHeader.getNumber() + 1L, parentHeader),
finalState.getLocalAddress()),
roundFactory,
finalState.getClock(),
messageValidatorFactory,
messageFactory);
finalState.getLocalAddress());

QbftBlockHeightManager qbftBlockHeightManager =
new QbftBlockHeightManager(
parentHeader,
finalState,
roundChangeManager,
roundFactory,
finalState.getClock(),
messageValidatorFactory,
messageFactory);

if (isEarlyRoundChangeEnabled) {
roundChangeManager.setRCQuorum(
pullurib marked this conversation as resolved.
Show resolved Hide resolved
BftHelpers.calculateRequiredFutureRCQuorum(finalState.getValidators().size()));
qbftBlockHeightManager.isEarlyRoundChangeEnabled(true);
}

return qbftBlockHeightManager;
}
}
Loading