diff --git a/CHANGELOG.md b/CHANGELOG.md index fffb59408..629d328e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ ### Bug fixes - Add missing RPC method `debug_accountRange` to `RpcMethod.java` so this method can be used with `--rpc-http-api-method-no-auth` [#8153](https://github.com/hyperledger/besu/issues/8153) +- Add a fallback pivot strategy when the safe block does not change for a long time, to make possible to complete the initial sync in case the chain is not finalizing [#8395](https://github.com/hyperledger/besu/pull/8395) ## 25.2.2 hotfix - Pectra - Sepolia: Fix for deposit contract log decoding [#8383](https://github.com/hyperledger/besu/pull/8383) diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromSafeBlock.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromSafeBlock.java index f30fb54e2..61a34f226 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromSafeBlock.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromSafeBlock.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.eth.sync.tasks.RetryingGetHeaderFromPeerByH import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.plugin.services.MetricsSystem; +import java.time.Duration; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -41,6 +42,9 @@ import org.slf4j.LoggerFactory; public class PivotSelectorFromSafeBlock implements PivotBlockSelector { private static final Logger LOG = LoggerFactory.getLogger(PivotSelectorFromSafeBlock.class); + private static final long NO_FCU_RECEIVED_LOGGING_THRESHOLD = Duration.ofMinutes(1).toMillis(); + private static final long UNCHANGED_PIVOT_BLOCK_FALLBACK_INTERVAL = + Duration.ofMinutes(7).toMillis(); private final ProtocolContext protocolContext; private final ProtocolSchedule protocolSchedule; private final EthContext ethContext; @@ -50,8 +54,12 @@ public class PivotSelectorFromSafeBlock implements PivotBlockSelector { private final Supplier> forkchoiceStateSupplier; private final Runnable cleanupAction; - private long lastNoFcuReceivedInfoLog = System.currentTimeMillis(); - private static final long NO_FCU_RECEIVED_LOGGING_THRESHOLD = 60000L; + private volatile long lastNoFcuReceivedInfoLog = System.currentTimeMillis(); + private volatile long lastPivotBlockChange = System.currentTimeMillis(); + private volatile Hash lastSafeBlockHash = Hash.ZERO; + private volatile Hash fallbackBlockHash; + private volatile Hash lastFallbackBlockHash; + private volatile boolean inFallbackMode = false; private volatile Optional maybeCachedHeadBlockHeader = Optional.empty(); public PivotSelectorFromSafeBlock( @@ -76,11 +84,38 @@ public class PivotSelectorFromSafeBlock implements PivotBlockSelector { @Override public Optional selectNewPivotBlock() { final Optional maybeForkchoice = forkchoiceStateSupplier.get(); + final var now = System.currentTimeMillis(); + if (maybeForkchoice.isPresent() && maybeForkchoice.get().hasValidSafeBlockHash()) { - return Optional.of(selectLastSafeBlockAsPivot(maybeForkchoice.get().getSafeBlockHash())); + final var safeBlockHash = maybeForkchoice.get().getSafeBlockHash(); + + // if the safe has changed just return it and reset the timer and save the current head as + // fallback + if (!safeBlockHash.equals(lastSafeBlockHash)) { + lastSafeBlockHash = safeBlockHash; + lastPivotBlockChange = now; + inFallbackMode = false; + fallbackBlockHash = maybeForkchoice.get().getHeadBlockHash(); + return Optional.of(selectLastSafeBlockAsPivot(safeBlockHash)); + } + + // otherwise verify if we need to fallback to a previous head block + if (lastPivotBlockChange + UNCHANGED_PIVOT_BLOCK_FALLBACK_INTERVAL < now) { + lastPivotBlockChange = now; + inFallbackMode = true; + lastFallbackBlockHash = fallbackBlockHash; + final var fallbackPivot = selectFallbackBlockAsPivot(fallbackBlockHash); + fallbackBlockHash = maybeForkchoice.get().getHeadBlockHash(); + return Optional.of(fallbackPivot); + } + + // if not enough time has passed the return again the previous value + return Optional.of( + selectLastSafeBlockAsPivot(inFallbackMode ? lastFallbackBlockHash : lastSafeBlockHash)); } - if (lastNoFcuReceivedInfoLog + NO_FCU_RECEIVED_LOGGING_THRESHOLD < System.currentTimeMillis()) { - lastNoFcuReceivedInfoLog = System.currentTimeMillis(); + + if (lastNoFcuReceivedInfoLog + NO_FCU_RECEIVED_LOGGING_THRESHOLD < now) { + lastNoFcuReceivedInfoLog = now; LOG.info( "Waiting for consensus client, this may be because your consensus client is still syncing"); } @@ -99,6 +134,14 @@ public class PivotSelectorFromSafeBlock implements PivotBlockSelector { return new FastSyncState(safeHash); } + private FastSyncState selectFallbackBlockAsPivot(final Hash fallbackBlockHash) { + LOG.debug( + "Safe block not changed in the last {} min, using a previous head block {} as fallback", + UNCHANGED_PIVOT_BLOCK_FALLBACK_INTERVAL / 60, + fallbackBlockHash); + return new FastSyncState(fallbackBlockHash); + } + @Override public void close() { cleanupAction.run(); @@ -188,7 +231,7 @@ public class PivotSelectorFromSafeBlock implements PivotBlockSelector { LOG.debug("Error downloading block header by hash {}", hash); } else { LOG.atDebug() - .setMessage("Successfully downloaded pivot block header by hash {}") + .setMessage("Successfully downloaded block header by hash {}") .addArgument(blockHeader::toLogString) .log(); }