From 40025396eae9db2d4162722d211a5ac4aa01b4e7 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Fri, 24 May 2024 16:10:21 +0100 Subject: [PATCH] Create a BFT-specific pivot block handler Signed-off-by: Matthew Whitehead --- .../controller/BesuControllerBuilder.java | 7 +- .../qbft/BFTPivotSelectorFromPeers.java | 105 ++++++++++++++++++ .../eth/sync/DefaultSynchronizer.java | 29 +++-- .../eth/sync/fastsync/FastSyncDownloader.java | 14 ++- .../fastsync/NoSyncRequiredException.java | 17 +++ .../sync/fastsync/NoSyncRequiredState.java | 17 +++ .../sync/fastsync/PivotSelectorFromPeers.java | 6 +- .../eth/sync/snapsync/SnapSyncDownloader.java | 2 +- 8 files changed, 176 insertions(+), 21 deletions(-) create mode 100644 consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/BFTPivotSelectorFromPeers.java create mode 100644 ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredException.java create mode 100644 ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredState.java 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 86a3db25c3ae..a6a3555a9f57 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -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; @@ -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); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/BFTPivotSelectorFromPeers.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/BFTPivotSelectorFromPeers.java new file mode 100644 index 000000000000..05d08d20ba16 --- /dev/null +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/BFTPivotSelectorFromPeers.java @@ -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 selectNewPivotBlock() { + + final BftContext bftContext = protocolContext.getConsensusContext(BftContext.class); + final ValidatorProvider validatorProvider = bftContext.getValidatorProvider(); + // See if we have a best peer + Optional 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(); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java index af35898ac221..277e07035e35 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java @@ -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; @@ -242,16 +243,24 @@ private CompletableFuture 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(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java index 7b64f885c299..d4cd0cf6c02d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -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 @@ -80,7 +80,7 @@ public CompletableFuture start() { if (!running.compareAndSet(false, true)) { throw new IllegalStateException("SyncDownloader already running"); } - LOG.info("Starting sync"); + LOG.info("Starting fast sync"); return start(initialFastSyncState); } @@ -94,7 +94,7 @@ protected CompletableFuture 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)); } @@ -114,15 +114,17 @@ public CompletableFuture findPivotBlock( protected CompletableFuture 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 { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredException.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredException.java new file mode 100644 index 000000000000..caa45abdc196 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredException.java @@ -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 {} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredState.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredState.java new file mode 100644 index 000000000000..9cefe78ed15a --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredState.java @@ -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 {} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromPeers.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromPeers.java index 6402b58d1fd6..3410ea3be121 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromPeers.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromPeers.java @@ -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; @@ -74,7 +74,7 @@ public long getBestChainHeight() { return syncState.bestChainHeight(); } - private Optional fromBestPeer(final EthPeer peer) { + protected Optional fromBestPeer(final EthPeer peer) { final long pivotBlockNumber = peer.chainState().getEstimatedHeight() - syncConfig.getFastSyncPivotDistance(); if (pivotBlockNumber <= BlockHeader.GENESIS_BLOCK_NUMBER) { @@ -86,7 +86,7 @@ private Optional fromBestPeer(final EthPeer peer) { return Optional.of(new FastSyncState(pivotBlockNumber)); } - private Optional selectBestPeer() { + protected Optional selectBestPeer() { return ethContext .getEthPeers() .bestPeerMatchingCriteria(this::canPeerDeterminePivotBlock) diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncDownloader.java index 00c571f9840a..88ea0114b91c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncDownloader.java @@ -48,7 +48,7 @@ public SnapSyncDownloader( @Override protected CompletableFuture 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)); }