Skip to content

Commit

Permalink
Create a BFT-specific pivot block handler
Browse files Browse the repository at this point in the history
Signed-off-by: Matthew Whitehead <[email protected]>
  • Loading branch information
matthew1001 committed May 24, 2024
1 parent 4016447 commit 4002539
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.consensus.merge.MergeContext;
import org.hyperledger.besu.consensus.merge.UnverifiedForkchoiceSupplier;
import org.hyperledger.besu.consensus.qbft.BFTPivotSelectorFromPeers;
import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration;
import org.hyperledger.besu.cryptoservices.NodeKey;
import org.hyperledger.besu.datatypes.Hash;
Expand Down Expand Up @@ -835,7 +836,11 @@ private PivotBlockSelector createPivotSelector(
final SyncState syncState,
final MetricsSystem metricsSystem) {

if (genesisConfigOptions.getTerminalTotalDifficulty().isPresent()) {
if (genesisConfigOptions.isQbft() || genesisConfigOptions.isIbft2()) {
LOG.info("QBFT is configured, creating initial sync for BFT");
return new BFTPivotSelectorFromPeers(
ethContext, syncConfig, syncState, metricsSystem, protocolContext, nodeKey);
} else if (genesisConfigOptions.getTerminalTotalDifficulty().isPresent()) {
LOG.info("TTD difficulty is present, creating initial sync for PoS");

final MergeContext mergeContext = protocolContext.getConsensusContext(MergeContext.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* 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.consensus.qbft;

import org.hyperledger.besu.consensus.common.bft.BftContext;
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.cryptoservices.NodeKey;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.Util;
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
import org.hyperledger.besu.ethereum.eth.manager.EthPeer;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState;
import org.hyperledger.besu.ethereum.eth.sync.fastsync.NoSyncRequiredException;
import org.hyperledger.besu.ethereum.eth.sync.fastsync.PivotSelectorFromPeers;
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
import org.hyperledger.besu.plugin.services.MetricsSystem;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BFTPivotSelectorFromPeers extends PivotSelectorFromPeers {

private static final Logger LOG = LoggerFactory.getLogger(BFTPivotSelectorFromPeers.class);

private final ProtocolContext protocolContext;
private final NodeKey nodeKey;

public BFTPivotSelectorFromPeers(
final EthContext ethContext,
final SynchronizerConfiguration syncConfig,
final SyncState syncState,
final MetricsSystem metricsSystem,
final ProtocolContext protocolContext,
final NodeKey nodeKey) {
super(ethContext, syncConfig, syncState, metricsSystem);
this.protocolContext = protocolContext;
this.nodeKey = nodeKey;
LOG.info("Creating BFTPivotSelectorFromPeers");
}

@Override
public Optional<FastSyncState> selectNewPivotBlock() {

final BftContext bftContext = protocolContext.getConsensusContext(BftContext.class);
final ValidatorProvider validatorProvider = bftContext.getValidatorProvider();
// See if we have a best peer
Optional<EthPeer> bestPeer = selectBestPeer();

if (bestPeer.isPresent()) {
// For a recently created permissioned chain we can skip snap sync until we're past the
// pivot distance
if (bestPeer.get().chainState().getEstimatedHeight()
<= syncConfig.getFastSyncPivotDistance()) {
throw new NoSyncRequiredException();
}

return bestPeer.flatMap(this::fromBestPeer);
} else {
// Treat single-validator as a special case. We are the only node that can produce
// blocks so we won't wait to sync with a non-validator node that may or may not exist
if (validatorProvider.getValidatorsAtHead().size() == 1
&& validatorProvider
.getValidatorsAtHead()
.contains(Util.publicKeyToAddress(nodeKey.getPublicKey()))) {
throw new NoSyncRequiredException();
}

// Treat the case where we have min-peer-count peers who don't have a chain-head estimate but who are all validators as not needing to sync
// This is effectively handling the "new chain with N validators" case, but speaks more generally to the BFT case where a BFT chain
// prioritises information from other validators over waiting for non-validator peers to respond.
AtomicInteger peerValidatorCount = new AtomicInteger();
EthPeers theList = ethContext.getEthPeers();
theList.getAllActiveConnections().forEach(peer -> {
if (validatorProvider
.getValidatorsAtHead().contains(peer.getPeerInfo().getAddress())) {
peerValidatorCount.getAndIncrement();
}
});
if (peerValidatorCount.get() >= syncConfig.getFastSyncMinimumPeerCount()) {
// We have sync-min-peers x validators connected, all of whom have no head estimate. We'll assume this is a new chain
// and skip waiting for any more peers to sync with.
throw new NoSyncRequiredException();
}
}

return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.hyperledger.besu.ethereum.eth.sync.checkpointsync.CheckpointDownloaderFactory;
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncDownloader;
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState;
import org.hyperledger.besu.ethereum.eth.sync.fastsync.NoSyncRequiredState;
import org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate.FastDownloaderFactory;
import org.hyperledger.besu.ethereum.eth.sync.fullsync.FullSyncDownloader;
import org.hyperledger.besu.ethereum.eth.sync.fullsync.SyncTerminationCondition;
Expand Down Expand Up @@ -242,16 +243,24 @@ private CompletableFuture<Void> handleSyncResult(final FastSyncState result) {
// We've been shutdown which will have triggered the fast sync future to complete
return CompletableFuture.completedFuture(null);
}
fastSyncDownloader.ifPresent(FastSyncDownloader::deleteFastSyncState);
result
.getPivotBlockHeader()
.ifPresent(
blockHeader -> protocolContext.getWorldStateArchive().resetArchiveStateTo(blockHeader));
LOG.info(
"Sync completed successfully with pivot block {}",
result.getPivotBlockNumber().getAsLong());
pivotBlockSelector.close();
syncState.markInitialSyncPhaseAsDone();

if (result instanceof NoSyncRequiredState) {
LOG.info("Sync completed (no sync required)");
syncState.markInitialSyncPhaseAsDone();
} else {
fastSyncDownloader.ifPresent(FastSyncDownloader::deleteFastSyncState);
result
.getPivotBlockHeader()
.ifPresent(
blockHeader ->
protocolContext.getWorldStateArchive().resetArchiveStateTo(blockHeader));
if (result.hasPivotBlockHash())
LOG.info(
"Sync completed successfully with pivot block {}",
result.getPivotBlockNumber().getAsLong());
pivotBlockSelector.close();
syncState.markInitialSyncPhaseAsDone();
}

if (terminationCondition.shouldContinueDownload()) {
return startFullSync();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright ConsenSys AG.
* 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
Expand Down Expand Up @@ -80,7 +80,7 @@ public CompletableFuture<FastSyncState> start() {
if (!running.compareAndSet(false, true)) {
throw new IllegalStateException("SyncDownloader already running");
}
LOG.info("Starting sync");
LOG.info("Starting fast sync");
return start(initialFastSyncState);
}

Expand All @@ -94,7 +94,7 @@ protected CompletableFuture<FastSyncState> start(final FastSyncState fastSyncSta
onBonsai.clearFlatDatabase();
onBonsai.clearTrieLog();
});
LOG.debug("Start sync with initial sync state {}", fastSyncState);
LOG.info("Start fast sync with initial sync state {}", fastSyncState);
return findPivotBlock(fastSyncState, fss -> downloadChainAndWorldState(fastSyncActions, fss));
}

Expand All @@ -114,15 +114,17 @@ public CompletableFuture<FastSyncState> findPivotBlock(
protected CompletableFuture<FastSyncState> handleFailure(final Throwable error) {
trailingPeerRequirements = Optional.empty();
Throwable rootCause = ExceptionUtils.rootCause(error);
if (rootCause instanceof SyncException) {
if (rootCause instanceof NoSyncRequiredException) {
return CompletableFuture.completedFuture(new NoSyncRequiredState());
} else if (rootCause instanceof SyncException) {
return CompletableFuture.failedFuture(error);
} else if (rootCause instanceof StalledDownloadException) {
LOG.debug("Stalled sync re-pivoting to newer block.");
LOG.info("Stalled sync re-pivoting to newer block.");
return start(FastSyncState.EMPTY_SYNC_STATE);
} else if (rootCause instanceof CancellationException) {
return CompletableFuture.failedFuture(error);
} else if (rootCause instanceof MaxRetriesReachedException) {
LOG.debug(
LOG.info(
"A download operation reached the max number of retries, re-pivoting to newer block");
return start(FastSyncState.EMPTY_SYNC_STATE);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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.ethereum.eth.sync.fastsync;

public class NoSyncRequiredException extends RuntimeException {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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.ethereum.eth.sync.fastsync;

public class NoSyncRequiredState extends FastSyncState {}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class PivotSelectorFromPeers implements PivotBlockSelector {
private static final Logger LOG = LoggerFactory.getLogger(PivotSelectorFromPeers.class);

private final EthContext ethContext;
private final SynchronizerConfiguration syncConfig;
protected final SynchronizerConfiguration syncConfig;
private final SyncState syncState;
private final MetricsSystem metricsSystem;

Expand Down Expand Up @@ -74,7 +74,7 @@ public long getBestChainHeight() {
return syncState.bestChainHeight();
}

private Optional<FastSyncState> fromBestPeer(final EthPeer peer) {
protected Optional<FastSyncState> fromBestPeer(final EthPeer peer) {
final long pivotBlockNumber =
peer.chainState().getEstimatedHeight() - syncConfig.getFastSyncPivotDistance();
if (pivotBlockNumber <= BlockHeader.GENESIS_BLOCK_NUMBER) {
Expand All @@ -86,7 +86,7 @@ private Optional<FastSyncState> fromBestPeer(final EthPeer peer) {
return Optional.of(new FastSyncState(pivotBlockNumber));
}

private Optional<EthPeer> selectBestPeer() {
protected Optional<EthPeer> selectBestPeer() {
return ethContext
.getEthPeers()
.bestPeerMatchingCriteria(this::canPeerDeterminePivotBlock)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public SnapSyncDownloader(

@Override
protected CompletableFuture<FastSyncState> start(final FastSyncState fastSyncState) {
LOG.debug("Start snap sync with initial sync state {}", fastSyncState);
LOG.info("Start snap sync with initial sync state {}", fastSyncState);
return findPivotBlock(fastSyncState, fss -> downloadChainAndWorldState(fastSyncActions, fss));
}

Expand Down

0 comments on commit 4002539

Please sign in to comment.