Skip to content

Commit

Permalink
Round change upon f+1 RC messages (experimental option)
Browse files Browse the repository at this point in the history
Signed-off-by: Bhanu Pulluri <[email protected]>
  • Loading branch information
Bhanu Pulluri committed Oct 31, 2024
1 parent a9dc833 commit 1ab1526
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 46 deletions.
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 the int
*/
public static int calculateRequiredFutureRCQuorum(final int validatorCount) {
return validatorCount - Util.fastDivCeiling(2 * validatorCount, 3) + 1;
}

/**
* 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 @@ -213,37 +214,50 @@ private void logValidatorChanges(final QbftRound qbftRound) {
@Override
public void roundExpired(final RoundExpiry expire) {
if (currentRound.isEmpty()) {
LOG.error(
LOG.info(
"Received Round timer expiry before round is created timerRound={}", expire.getView());
return;
}

QbftRound qbftRound = currentRound.get();
if (!expire.getView().equals(qbftRound.getRoundIdentifier())) {
LOG.trace(
LOG.info(
"Ignoring Round timer expired which does not match current round. round={}, timerRound={}",
qbftRound.getRoundIdentifier(),
expire.getView());
return;
}

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

private synchronized void doRoundChange(final int newRoundNumber) {

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(
BftHelpers.calculateRequiredFutureRCQuorum(finalState.getValidators().size()));
qbftBlockHeightManager.isEarlyRoundChangeEnabled(true);
}

return qbftBlockHeightManager;
}
}
Loading

0 comments on commit 1ab1526

Please sign in to comment.