From f2d5f6815cccd14051b00129d3183e90579150ee Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Sun, 20 Jan 2019 08:24:11 +1000 Subject: [PATCH 01/17] Add support for initiating fast sync to DefaultSynchronizer, starting full sync once that completes. Currently the FastSyncDownloader immediately fails with FAST_SYNC_UNSUPPORTED. --- .../eth/sync/DefaultSynchronizer.java | 43 ++++++++++++++++--- ...ownloader.java => FullSyncDownloader.java} | 4 +- .../eth/sync/fastsync/FastSyncDownloader.java | 36 ++++++++++++++++ .../eth/sync/fastsync/FastSyncResult.java | 18 ++++++++ ...rTest.java => FullSyncDownloaderTest.java} | 34 +++++++-------- .../pegasys/pantheon/cli/PantheonCommand.java | 27 ++++++------ 6 files changed, 122 insertions(+), 40 deletions(-) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/{Downloader.java => FullSyncDownloader.java} (99%) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/{DownloaderTest.java => FullSyncDownloaderTest.java} (95%) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java index 118a68ee75..d5347c41d0 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java @@ -16,6 +16,8 @@ import tech.pegasys.pantheon.ethereum.core.SyncStatus; import tech.pegasys.pantheon.ethereum.core.Synchronizer; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncDownloader; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult; import tech.pegasys.pantheon.ethereum.eth.sync.state.PendingBlocks; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; @@ -35,7 +37,8 @@ public class DefaultSynchronizer implements Synchronizer { private final SyncState syncState; private final AtomicBoolean started = new AtomicBoolean(false); private final BlockPropagationManager blockPropagationManager; - private final Downloader downloader; + private final FullSyncDownloader fullSyncDownloader; + private final Optional> fastSyncDownloader; public DefaultSynchronizer( final SynchronizerConfiguration syncConfig, @@ -54,28 +57,54 @@ public DefaultSynchronizer( syncState, new PendingBlocks(), ethTasksTimer); - this.downloader = - new Downloader<>( + this.fullSyncDownloader = + new FullSyncDownloader<>( syncConfig, protocolSchedule, protocolContext, ethContext, syncState, ethTasksTimer); ChainHeadTracker.trackChainHeadForPeers( ethContext, protocolSchedule, protocolContext.getBlockchain(), syncConfig, ethTasksTimer); - if (syncConfig.syncMode().equals(SyncMode.FAST)) { + if (syncConfig.syncMode() == SyncMode.FAST) { LOG.info("Fast sync enabled."); + this.fastSyncDownloader = + Optional.of( + new FastSyncDownloader<>( + syncConfig, protocolSchedule, protocolContext, ethContext, ethTasksTimer)); + } else { + this.fastSyncDownloader = Optional.empty(); } } @Override public void start() { if (started.compareAndSet(false, true)) { - LOG.info("Starting synchronizer."); - blockPropagationManager.start(); - downloader.start(); + if (fastSyncDownloader.isPresent()) { + fastSyncDownloader.get().start().whenComplete(this::handleFastSyncResult); + } else { + startFullSync(); + } } else { throw new IllegalStateException("Attempt to start an already started synchronizer."); } } + private void handleFastSyncResult(final FastSyncResult result, final Throwable error) { + if (error != null) { + LOG.error("Fast sync failed. Switching to full sync.", error); + } + if (result == FastSyncResult.FAST_SYNC_COMPLETE) { + LOG.info("Fast sync completed successfully."); + } else { + LOG.error("Fast sync failed: {}", result); + } + startFullSync(); + } + + private void startFullSync() { + LOG.info("Starting synchronizer."); + blockPropagationManager.start(); + fullSyncDownloader.start(); + } + @Override public Optional getSyncStatus() { if (!started.get()) { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/Downloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FullSyncDownloader.java similarity index 99% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/Downloader.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FullSyncDownloader.java index 754f0e7902..ac728d3b7e 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/Downloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FullSyncDownloader.java @@ -54,7 +54,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class Downloader { +public class FullSyncDownloader { private static final Logger LOG = LogManager.getLogger(); private final SynchronizerConfiguration config; @@ -73,7 +73,7 @@ public class Downloader { private long syncTargetDisconnectListenerId; protected CompletableFuture currentTask; - Downloader( + FullSyncDownloader( final SynchronizerConfiguration config, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java new file mode 100644 index 0000000000..b51ad40d21 --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; + +import tech.pegasys.pantheon.ethereum.ProtocolContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.metrics.LabelledMetric; +import tech.pegasys.pantheon.metrics.OperationTimer; + +import java.util.concurrent.CompletableFuture; + +public class FastSyncDownloader { + + public FastSyncDownloader( + final SynchronizerConfiguration syncConfig, + final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, + final EthContext ethContext, + final LabelledMetric ethTasksTimer) {} + + public CompletableFuture start() { + return CompletableFuture.completedFuture(FastSyncResult.FAST_SYNC_UNAVAILABLE); + } +} diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java new file mode 100644 index 0000000000..86c1613fab --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java @@ -0,0 +1,18 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; + +public enum FastSyncResult { + FAST_SYNC_COMPLETE, + FAST_SYNC_UNAVAILABLE +} diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/DownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/FullSyncDownloaderTest.java similarity index 95% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/DownloaderTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/FullSyncDownloaderTest.java index 0d4e169531..c596626a92 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/DownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/FullSyncDownloaderTest.java @@ -55,7 +55,7 @@ import org.junit.Before; import org.junit.Test; -public class DownloaderTest { +public class FullSyncDownloaderTest { protected ProtocolSchedule protocolSchedule; protected EthProtocolManager ethProtocolManager; @@ -88,12 +88,12 @@ public void setupTest() { ethTashsTimer = NoOpMetricsSystem.NO_OP_LABELLED_TIMER; } - private Downloader downloader(final SynchronizerConfiguration syncConfig) { - return new Downloader<>( + private FullSyncDownloader downloader(final SynchronizerConfiguration syncConfig) { + return new FullSyncDownloader<>( syncConfig, protocolSchedule, protocolContext, ethContext, syncState, ethTashsTimer); } - private Downloader downloader() { + private FullSyncDownloader downloader() { final SynchronizerConfiguration syncConfig = SynchronizerConfiguration.builder().build().validated(localBlockchain); return downloader(syncConfig); @@ -115,7 +115,7 @@ public void syncsToBetterChain_multipleSegments() { .downloaderChainSegmentSize(10) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); downloader.start(); while (!syncState.syncTarget().isPresent()) { @@ -147,7 +147,7 @@ public void syncsToBetterChain_singleSegment() { .downloaderChainSegmentSize(10) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); downloader.start(); while (!syncState.syncTarget().isPresent()) { @@ -179,7 +179,7 @@ public void syncsToBetterChain_singleSegmentOnBoundary() { .downloaderChainSegmentSize(4) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); downloader.start(); while (!syncState.syncTarget().isPresent()) { @@ -206,7 +206,7 @@ public void doesNotSyncToWorseChain() { EthProtocolManagerTestUtil.createPeer(ethProtocolManager, otherBlockchain); final Responder responder = RespondingEthPeer.blockchainResponder(otherBlockchain); - final Downloader downloader = downloader(); + final FullSyncDownloader downloader = downloader(); downloader.start(); peer.respond(responder); @@ -246,7 +246,7 @@ public void syncsToBetterChain_fromFork() { .downloaderChainSegmentSize(10) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); downloader.start(); while (localBlockchain.getChainHeadBlockNumber() < targetBlock) { @@ -268,7 +268,7 @@ public void choosesBestPeerAsSyncTarget_byTd() { final RespondingEthPeer peerB = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, localTd.plus(200)); - final Downloader downloader = downloader(); + final FullSyncDownloader downloader = downloader(); downloader.start(); // Process until the sync target is selected @@ -291,7 +291,7 @@ public void choosesBestPeerAsSyncTarget_byTdAndHeight() { EthProtocolManagerTestUtil.createPeer(ethProtocolManager, localTd.plus(200), 0); peerA.getEthPeer().chainState().update(gen.hash(), 50); - final Downloader downloader = downloader(); + final FullSyncDownloader downloader = downloader(); downloader.start(); // Process until the sync target is selected @@ -319,7 +319,7 @@ public void switchesSyncTarget_betterHeight() { .downloaderChangeTargetThresholdByHeight(10) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); downloader.start(); // Process until the sync target is selected @@ -359,7 +359,7 @@ public void doesNotSwitchSyncTarget_betterHeightUnderThreshold() { .downloaderChangeTargetThresholdByHeight(1000) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); downloader.start(); // Process until the sync target is selected @@ -399,7 +399,7 @@ public void switchesSyncTarget_betterTd() { .downloaderChangeTargetThresholdByTd(UInt256.of(10)) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); downloader.start(); // Process until the sync target is selected @@ -446,7 +446,7 @@ public void doesNotSwitchSyncTarget_betterTdUnderThreshold() { .downloaderChangeTargetThresholdByTd(UInt256.of(100_000_000L)) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); downloader.start(); // Process until the sync target is selected @@ -491,7 +491,7 @@ public void recoversFromSyncTargetDisconnect() { .downloaderHeadersRequestSize(3) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); final long bestPeerChainHead = otherBlockchain.getChainHeadBlockNumber(); final RespondingEthPeer bestPeer = @@ -565,7 +565,7 @@ public void requestsCheckpointsFromSyncTarget() { .downloaderHeadersRequestSize(3) .build() .validated(localBlockchain); - final Downloader downloader = downloader(syncConfig); + final FullSyncDownloader downloader = downloader(syncConfig); // Setup the best peer we should use as our sync target final long bestPeerChainHead = otherBlockchain.getChainHeadBlockNumber(); diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index e16b3fafb0..08debb311e 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -209,14 +209,13 @@ public static class RpcApisConversionException extends Exception { ) private final Collection bannedNodeIds = new ArrayList<>(); - // TODO: Re-enable as per NC-1057/NC-1681 - // @Option( - // names = {"--sync-mode"}, - // paramLabel = MANDATORY_MODE_FORMAT_HELP, - // description = - // "Synchronization mode (Value can be one of ${COMPLETION-CANDIDATES}, default: - // ${DEFAULT-VALUE})" - // ) + @Option( + hidden = true, + names = {"--sync-mode"}, + paramLabel = MANDATORY_MODE_FORMAT_HELP, + description = + "Synchronization mode (Value can be one of ${COMPLETION-CANDIDATES}, default: ${DEFAULT-VALUE})" + ) private final SyncMode syncMode = DEFAULT_SYNC_MODE; // Boolean option to indicate if the client have to sync against the ottoman test network @@ -511,7 +510,7 @@ public void run() { } final EthNetworkConfig ethNetworkConfig = ethNetworkConfig(); - PermissioningConfiguration permissioningConfiguration = permissioningConfiguration(); + final PermissioningConfiguration permissioningConfiguration = permissioningConfiguration(); ensureAllBootnodesAreInWhitelist(ethNetworkConfig, permissioningConfiguration); synchronize( @@ -529,17 +528,17 @@ public void run() { private void ensureAllBootnodesAreInWhitelist( final EthNetworkConfig ethNetworkConfig, final PermissioningConfiguration permissioningConfiguration) { - List bootnodes = + final List bootnodes = DiscoveryConfiguration.getBootstrapPeersFromGenericCollection( ethNetworkConfig.getBootNodes()); if (permissioningConfiguration.isNodeWhitelistSet() && bootnodes != null) { - List whitelist = + final List whitelist = permissioningConfiguration .getNodeWhitelist() .stream() .map(DefaultPeer::fromURI) .collect(Collectors.toList()); - for (Peer bootnode : bootnodes) { + for (final Peer bootnode : bootnodes) { if (!whitelist.contains(bootnode)) { throw new ParameterException( new CommandLine(this), @@ -638,7 +637,7 @@ private void synchronize( checkNotNull(runnerBuilder); - Runner runner = + final Runner runner = runnerBuilder .vertx(Vertx.vertx()) .pantheonController(controller) @@ -723,7 +722,7 @@ private EthNetworkConfig updateNetworkConfig(final EthNetworkConfig ethNetworkCo private String genesisConfig() { try { return Resources.toString(genesisFile().toURI().toURL(), UTF_8); - } catch (IOException e) { + } catch (final IOException e) { throw new ParameterException( new CommandLine(this), String.format("Unable to load genesis file %s.", genesisFile()), From cb8cfb280e757c1ef8e0cecb940df7537397f0ad Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 21 Jan 2019 08:27:10 +1000 Subject: [PATCH 02/17] Wait for a minimum number of peers to be available before starting fast sync. --- .../eth/sync/SynchronizerConfiguration.java | 10 +++++ .../eth/sync/fastsync/FastSyncDownloader.java | 40 ++++++++++++++++++- .../eth/sync/fastsync/FastSyncResult.java | 3 +- .../tech/pegasys/pantheon/RunnerTest.java | 2 + .../src/test/resources/everything_config.toml | 2 +- 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java index 0a7fddf092..6daf945d74 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java @@ -30,10 +30,12 @@ public class SynchronizerConfiguration { // TODO: Determine reasonable defaults here public static int DEFAULT_PIVOT_DISTANCE_FROM_HEAD = 500; public static float DEFAULT_FULL_VALIDATION_RATE = .1f; + public static int DEFAULT_FAST_SYNC_MINIMUM_PEERS = 5; // Fast sync config private final int fastSyncPivotDistance; private final float fastSyncFullValidationRate; + private final int fastSyncMinimumPeerCount; // Block propagation config private final Range blockPropagationRange; @@ -58,6 +60,7 @@ private SynchronizerConfiguration( final SyncMode requestedSyncMode, final int fastSyncPivotDistance, final float fastSyncFullValidationRate, + final int fastSyncMinimumPeerCount, final Range blockPropagationRange, final Optional syncMode, final long downloaderChangeTargetThresholdByHeight, @@ -73,6 +76,7 @@ private SynchronizerConfiguration( this.requestedSyncMode = requestedSyncMode; this.fastSyncPivotDistance = fastSyncPivotDistance; this.fastSyncFullValidationRate = fastSyncFullValidationRate; + this.fastSyncMinimumPeerCount = fastSyncMinimumPeerCount; this.blockPropagationRange = blockPropagationRange; this.syncMode = syncMode; this.downloaderChangeTargetThresholdByHeight = downloaderChangeTargetThresholdByHeight; @@ -115,6 +119,7 @@ public SynchronizerConfiguration validated(final Blockchain blockchain) { requestedSyncMode, fastSyncPivotDistance, fastSyncFullValidationRate, + fastSyncMinimumPeerCount, blockPropagationRange, Optional.of(actualSyncMode), downloaderChangeTargetThresholdByHeight, @@ -222,6 +227,10 @@ public float fastSyncFullValidationRate() { return fastSyncFullValidationRate; } + public int fastSyncMinimumPeerCount() { + return fastSyncMinimumPeerCount; + } + public static class Builder { private int fastSyncPivotDistance = DEFAULT_PIVOT_DISTANCE_FROM_HEAD; private float fastSyncFullValidationRate = DEFAULT_FULL_VALIDATION_RATE; @@ -318,6 +327,7 @@ public SynchronizerConfiguration build() { syncMode, fastSyncPivotDistance, fastSyncFullValidationRate, + DEFAULT_FAST_SYNC_MINIMUM_PEERS, blockPropagationRange, Optional.empty(), downloaderChangeTargetThresholdByHeight, diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java index b51ad40d21..3007a3e863 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -15,22 +15,58 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; +import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; +import tech.pegasys.pantheon.util.ExceptionUtils; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class FastSyncDownloader { + private static final Logger LOG = LogManager.getLogger(); + private final SynchronizerConfiguration syncConfig; + private final ProtocolSchedule protocolSchedule; + private final ProtocolContext protocolContext; + private final EthContext ethContext; + private final LabelledMetric ethTasksTimer; public FastSyncDownloader( final SynchronizerConfiguration syncConfig, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, final EthContext ethContext, - final LabelledMetric ethTasksTimer) {} + final LabelledMetric ethTasksTimer) { + this.syncConfig = syncConfig; + this.protocolSchedule = protocolSchedule; + this.protocolContext = protocolContext; + this.ethContext = ethContext; + this.ethTasksTimer = ethTasksTimer; + } public CompletableFuture start() { - return CompletableFuture.completedFuture(FastSyncResult.FAST_SYNC_UNAVAILABLE); + LOG.info("Fast sync enabled"); + return waitForSuitablePeers().handle((result, error) -> handleWaitForPeersResult(error)); + } + + private FastSyncResult handleWaitForPeersResult(final Throwable error) { + if (ExceptionUtils.rootCause(error) instanceof TimeoutException) { + LOG.warn( + "Fast sync timed out before minimum peer count was reached. Continuing with reduced peers."); + } else if (error != null) { + LOG.error("Failed to find peers for fast sync", error); + return FastSyncResult.UNEXPECTED_ERROR; + } + return FastSyncResult.FAST_SYNC_UNAVAILABLE; + } + + private CompletableFuture waitForSuitablePeers() { + final WaitForPeersTask waitForPeersTask = + WaitForPeersTask.create(ethContext, syncConfig.fastSyncMinimumPeerCount(), ethTasksTimer); + return ethContext.getScheduler().scheduleSyncWorkerTask(waitForPeersTask::run); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java index 86c1613fab..dfb5e2b140 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java @@ -14,5 +14,6 @@ public enum FastSyncResult { FAST_SYNC_COMPLETE, - FAST_SYNC_UNAVAILABLE + FAST_SYNC_UNAVAILABLE, + UNEXPECTED_ERROR } diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java index e81c4b4637..a8dad2c2bf 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java @@ -65,6 +65,7 @@ import okhttp3.Response; import org.assertj.core.api.Assertions; import org.awaitility.Awaitility; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -80,6 +81,7 @@ public void fullSyncFromGenesis() throws Exception { } @Test + @Ignore("Fast sync implementation in progress.") public void fastSyncFromGenesis() throws Exception { syncFromGenesis(SyncMode.FAST); } diff --git a/pantheon/src/test/resources/everything_config.toml b/pantheon/src/test/resources/everything_config.toml index 03e902f35c..1880eea03b 100644 --- a/pantheon/src/test/resources/everything_config.toml +++ b/pantheon/src/test/resources/everything_config.toml @@ -28,7 +28,7 @@ host-whitelist=["all"] # chain genesis="~/genesis.json" -#sync-mode="fast" +sync-mode="fast" ottoman=false ropsten=false goerli=false From 8b10aed9ef5e0df4721d549ced1b4936220ae112 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 21 Jan 2019 10:03:37 +1000 Subject: [PATCH 03/17] Add tests for fast sync waiting for peers and the overall coordination of fast sync actions. --- .../eth/sync/DefaultSynchronizer.java | 6 +- .../eth/sync/SynchronizerConfiguration.java | 13 ++- .../eth/sync/fastsync/FastSyncActions.java | 96 +++++++++++++++++++ .../eth/sync/fastsync/FastSyncDownloader.java | 52 ++-------- .../eth/sync/fastsync/FastSyncResult.java | 3 +- .../manager/EthProtocolManagerTestUtil.java | 8 +- .../sync/fastsync/FastSyncActionsTest.java | 88 +++++++++++++++++ .../sync/fastsync/FastSyncDownloaderTest.java | 50 ++++++++++ 8 files changed, 266 insertions(+), 50 deletions(-) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java create mode 100644 ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java create mode 100644 ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java index d5347c41d0..c62310754f 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java @@ -16,6 +16,7 @@ import tech.pegasys.pantheon.ethereum.core.SyncStatus; import tech.pegasys.pantheon.ethereum.core.Synchronizer; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncActions; import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncDownloader; import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult; import tech.pegasys.pantheon.ethereum.eth.sync.state.PendingBlocks; @@ -68,7 +69,8 @@ public DefaultSynchronizer( this.fastSyncDownloader = Optional.of( new FastSyncDownloader<>( - syncConfig, protocolSchedule, protocolContext, ethContext, ethTasksTimer)); + new FastSyncActions<>( + syncConfig, protocolSchedule, protocolContext, ethContext, ethTasksTimer))); } else { this.fastSyncDownloader = Optional.empty(); } @@ -91,7 +93,7 @@ private void handleFastSyncResult(final FastSyncResult result, final Throwable e if (error != null) { LOG.error("Fast sync failed. Switching to full sync.", error); } - if (result == FastSyncResult.FAST_SYNC_COMPLETE) { + if (result == FastSyncResult.SUCCESS) { LOG.info("Fast sync completed successfully."); } else { LOG.error("Fast sync failed: {}", result); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java index 6daf945d74..96e0598a5e 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java @@ -18,6 +18,7 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.util.uint.UInt256; +import java.time.Duration; import java.util.Optional; import com.google.common.collect.Range; @@ -31,11 +32,13 @@ public class SynchronizerConfiguration { public static int DEFAULT_PIVOT_DISTANCE_FROM_HEAD = 500; public static float DEFAULT_FULL_VALIDATION_RATE = .1f; public static int DEFAULT_FAST_SYNC_MINIMUM_PEERS = 5; + private static final Duration DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME = Duration.ofSeconds(30); // Fast sync config private final int fastSyncPivotDistance; private final float fastSyncFullValidationRate; private final int fastSyncMinimumPeerCount; + private final Duration fastSyncMaximumPeerWaitTime; // Block propagation config private final Range blockPropagationRange; @@ -61,6 +64,7 @@ private SynchronizerConfiguration( final int fastSyncPivotDistance, final float fastSyncFullValidationRate, final int fastSyncMinimumPeerCount, + final Duration fastSyncMaximumPeerWaitTime, final Range blockPropagationRange, final Optional syncMode, final long downloaderChangeTargetThresholdByHeight, @@ -77,6 +81,7 @@ private SynchronizerConfiguration( this.fastSyncPivotDistance = fastSyncPivotDistance; this.fastSyncFullValidationRate = fastSyncFullValidationRate; this.fastSyncMinimumPeerCount = fastSyncMinimumPeerCount; + this.fastSyncMaximumPeerWaitTime = fastSyncMaximumPeerWaitTime; this.blockPropagationRange = blockPropagationRange; this.syncMode = syncMode; this.downloaderChangeTargetThresholdByHeight = downloaderChangeTargetThresholdByHeight; @@ -120,6 +125,7 @@ public SynchronizerConfiguration validated(final Blockchain blockchain) { fastSyncPivotDistance, fastSyncFullValidationRate, fastSyncMinimumPeerCount, + fastSyncMaximumPeerWaitTime, blockPropagationRange, Optional.of(actualSyncMode), downloaderChangeTargetThresholdByHeight, @@ -227,10 +233,14 @@ public float fastSyncFullValidationRate() { return fastSyncFullValidationRate; } - public int fastSyncMinimumPeerCount() { + public int getFastSyncMinimumPeerCount() { return fastSyncMinimumPeerCount; } + public Duration getFastSyncMaximumPeerWaitTime() { + return fastSyncMaximumPeerWaitTime; + } + public static class Builder { private int fastSyncPivotDistance = DEFAULT_PIVOT_DISTANCE_FROM_HEAD; private float fastSyncFullValidationRate = DEFAULT_FULL_VALIDATION_RATE; @@ -328,6 +338,7 @@ public SynchronizerConfiguration build() { fastSyncPivotDistance, fastSyncFullValidationRate, DEFAULT_FAST_SYNC_MINIMUM_PEERS, + DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME, blockPropagationRange, Optional.empty(), downloaderChangeTargetThresholdByHeight, diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java new file mode 100644 index 0000000000..e86548a62c --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -0,0 +1,96 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import tech.pegasys.pantheon.ethereum.ProtocolContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; +import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; +import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; +import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.metrics.LabelledMetric; +import tech.pegasys.pantheon.metrics.OperationTimer; +import tech.pegasys.pantheon.util.ExceptionUtils; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeoutException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class FastSyncActions { + + private static final Logger LOG = LogManager.getLogger(); + private final SynchronizerConfiguration syncConfig; + private final ProtocolSchedule protocolSchedule; + private final ProtocolContext protocolContext; + private final EthContext ethContext; + private final LabelledMetric ethTasksTimer; + + public FastSyncActions( + final SynchronizerConfiguration syncConfig, + final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, + final EthContext ethContext, + final LabelledMetric ethTasksTimer) { + this.syncConfig = syncConfig; + this.protocolSchedule = protocolSchedule; + this.protocolContext = protocolContext; + this.ethContext = ethContext; + this.ethTasksTimer = ethTasksTimer; + } + + public CompletableFuture waitForSuitablePeers() { + final WaitForPeersTask waitForPeersTask = + WaitForPeersTask.create( + ethContext, syncConfig.getFastSyncMinimumPeerCount(), ethTasksTimer); + + final EthScheduler scheduler = ethContext.getScheduler(); + return scheduler + .timeout(waitForPeersTask, syncConfig.getFastSyncMaximumPeerWaitTime()) + .handle( + (result, error) -> { + if (ExceptionUtils.rootCause(error) instanceof TimeoutException) { + if (ethContext.getEthPeers().bestPeer().isPresent()) { + LOG.warn( + "Fast sync timed out before minimum peer count was reached. Continuing with reduced peers."); + } else { + return FastSyncResult.NO_PEERS_AVAILABLE; + } + } else if (error != null) { + LOG.error("Failed to find peers for fast sync", error); + return FastSyncResult.UNEXPECTED_ERROR; + } + return FastSyncResult.SUCCESS; + }) + .thenCompose( + result -> + result == FastSyncResult.NO_PEERS_AVAILABLE + ? waitForAnyPeer() + : completedFuture(result)); + } + + private CompletionStage waitForAnyPeer() { + LOG.warn( + "Maximum wait time for fast sync reached but no peers available. Continuing to wait for any available peer."); + final WaitForPeerTask waitForPeerTask = WaitForPeerTask.create(ethContext, ethTasksTimer); + return ethContext + .getScheduler() + .scheduleSyncWorkerTask(waitForPeerTask::run) + .thenApply(voidResult -> FastSyncResult.SUCCESS); + } +} diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java index 3007a3e863..6a9ad5f899 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -12,61 +12,25 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; -import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; -import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; -import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; -import tech.pegasys.pantheon.metrics.LabelledMetric; -import tech.pegasys.pantheon.metrics.OperationTimer; -import tech.pegasys.pantheon.util.ExceptionUtils; - import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class FastSyncDownloader { private static final Logger LOG = LogManager.getLogger(); - private final SynchronizerConfiguration syncConfig; - private final ProtocolSchedule protocolSchedule; - private final ProtocolContext protocolContext; - private final EthContext ethContext; - private final LabelledMetric ethTasksTimer; + private final FastSyncActions fastSyncActions; - public FastSyncDownloader( - final SynchronizerConfiguration syncConfig, - final ProtocolSchedule protocolSchedule, - final ProtocolContext protocolContext, - final EthContext ethContext, - final LabelledMetric ethTasksTimer) { - this.syncConfig = syncConfig; - this.protocolSchedule = protocolSchedule; - this.protocolContext = protocolContext; - this.ethContext = ethContext; - this.ethTasksTimer = ethTasksTimer; + public FastSyncDownloader(final FastSyncActions fastSyncActions) { + this.fastSyncActions = fastSyncActions; } public CompletableFuture start() { LOG.info("Fast sync enabled"); - return waitForSuitablePeers().handle((result, error) -> handleWaitForPeersResult(error)); - } - - private FastSyncResult handleWaitForPeersResult(final Throwable error) { - if (ExceptionUtils.rootCause(error) instanceof TimeoutException) { - LOG.warn( - "Fast sync timed out before minimum peer count was reached. Continuing with reduced peers."); - } else if (error != null) { - LOG.error("Failed to find peers for fast sync", error); - return FastSyncResult.UNEXPECTED_ERROR; - } - return FastSyncResult.FAST_SYNC_UNAVAILABLE; - } - - private CompletableFuture waitForSuitablePeers() { - final WaitForPeersTask waitForPeersTask = - WaitForPeersTask.create(ethContext, syncConfig.fastSyncMinimumPeerCount(), ethTasksTimer); - return ethContext.getScheduler().scheduleSyncWorkerTask(waitForPeersTask::run); + return fastSyncActions + .waitForSuitablePeers() + .thenApply( + result -> + result == FastSyncResult.SUCCESS ? FastSyncResult.FAST_SYNC_UNAVAILABLE : result); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java index dfb5e2b140..4224250e26 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java @@ -13,7 +13,8 @@ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; public enum FastSyncResult { - FAST_SYNC_COMPLETE, + SUCCESS, FAST_SYNC_UNAVAILABLE, + NO_PEERS_AVAILABLE, UNEXPECTED_ERROR } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java index a2ae437c19..b6fb16e705 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java @@ -50,13 +50,17 @@ public static EthProtocolManager create( return create(blockchain, worldStateArchive, () -> false); } - public static EthProtocolManager create() { + public static EthProtocolManager create(final TimeoutPolicy timeoutPolicy) { final ProtocolSchedule protocolSchedule = MainnetProtocolSchedule.create(); final GenesisConfigFile config = GenesisConfigFile.mainnet(); final GenesisState genesisState = GenesisState.fromConfig(config, protocolSchedule); final Blockchain blockchain = createInMemoryBlockchain(genesisState.getBlock()); final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); - return create(blockchain, worldStateArchive); + return create(blockchain, worldStateArchive, timeoutPolicy); + } + + public static EthProtocolManager create() { + return create(() -> false); } public static void broadcastMessage( diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java new file mode 100644 index 0000000000..41baf4a2f6 --- /dev/null +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem.NO_OP_LABELLED_TIMER; + +import tech.pegasys.pantheon.ethereum.ProtocolContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; +import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; +import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode; +import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.metrics.LabelledMetric; +import tech.pegasys.pantheon.metrics.OperationTimer; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Before; +import org.junit.Test; + +public class FastSyncActionsTest { + + private final SynchronizerConfiguration syncConfig = + new SynchronizerConfiguration.Builder().syncMode(SyncMode.FAST).build(); + + @SuppressWarnings("unchecked") + private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); + + @SuppressWarnings("unchecked") + private final ProtocolContext protocolContext = mock(ProtocolContext.class); + + private final LabelledMetric ethTasksTimer = NO_OP_LABELLED_TIMER; + private final AtomicBoolean timeout = new AtomicBoolean(false); + private FastSyncActions fastSyncActions; + private EthProtocolManager ethProtocolManager; + + @Before + public void setUp() { + ethProtocolManager = EthProtocolManagerTestUtil.create(timeout::get); + fastSyncActions = + new FastSyncActions<>( + syncConfig, + protocolSchedule, + protocolContext, + ethProtocolManager.ethContext(), + ethTasksTimer); + } + + @Test + public void waitForPeersShouldSucceedIfEnoughPeersAreFound() { + for (int i = 0; i < syncConfig.getFastSyncMinimumPeerCount(); i++) { + EthProtocolManagerTestUtil.createPeer(ethProtocolManager); + } + final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); + assertThat(result).isCompletedWithValue(FastSyncResult.SUCCESS); + } + + @Test + public void waitForPeersShouldReportSuccessWhenTimeLimitReachedAndAPeerIsAvailable() { + EthProtocolManagerTestUtil.createPeer(ethProtocolManager); + timeout.set(true); + final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); + assertThat(result).isCompletedWithValue(FastSyncResult.SUCCESS); + } + + @Test + public void waitForPeersShouldContinueWaitingUntilAtLeastOnePeerIsAvailable() { + timeout.set(true); + final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); + assertThat(result).isNotCompleted(); + + EthProtocolManagerTestUtil.createPeer(ethProtocolManager); + assertThat(result).isCompletedWithValue(FastSyncResult.SUCCESS); + } +} diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java new file mode 100644 index 0000000000..3ab1e82b6a --- /dev/null +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.concurrent.CompletableFuture; + +import org.junit.Test; + +public class FastSyncDownloaderTest { + + @SuppressWarnings("unchecked") + private final FastSyncActions fastSyncActions = mock(FastSyncActions.class); + + private final FastSyncDownloader downloader = new FastSyncDownloader<>(fastSyncActions); + + @Test + public void shouldWaitForSuitablePeersThenFail() { + when(fastSyncActions.waitForSuitablePeers()) + .thenReturn(completedFuture(FastSyncResult.SUCCESS)); + + final CompletableFuture result = downloader.start(); + + assertThat(result).isCompletedWithValue(FastSyncResult.FAST_SYNC_UNAVAILABLE); + } + + @Test + public void shouldAbortIfWaitForSuitablePeersFails() { + when(fastSyncActions.waitForSuitablePeers()) + .thenReturn(completedFuture(FastSyncResult.UNEXPECTED_ERROR)); + + final CompletableFuture result = downloader.start(); + + assertThat(result).isCompletedWithValue(FastSyncResult.UNEXPECTED_ERROR); + } +} From 6f3abb0e2ab34b0f94adf8196f946d687184583a Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 21 Jan 2019 11:30:16 +1000 Subject: [PATCH 04/17] Select pivot block. --- .../eth/sync/SynchronizerConfiguration.java | 2 +- .../eth/sync/fastsync/FastSyncActions.java | 38 ++++++++++--- .../eth/sync/fastsync/FastSyncDownloader.java | 21 ++++++- .../eth/sync/fastsync/FastSyncResult.java | 1 + .../eth/sync/fastsync/FastSyncState.java | 57 +++++++++++++++++++ .../sync/fastsync/FastSyncActionsTest.java | 53 ++++++++++++++--- .../sync/fastsync/FastSyncDownloaderTest.java | 18 ++++-- 7 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java index 96e0598a5e..805c9a45c4 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java @@ -32,7 +32,7 @@ public class SynchronizerConfiguration { public static int DEFAULT_PIVOT_DISTANCE_FROM_HEAD = 500; public static float DEFAULT_FULL_VALIDATION_RATE = .1f; public static int DEFAULT_FAST_SYNC_MINIMUM_PEERS = 5; - private static final Duration DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME = Duration.ofSeconds(30); + private static final Duration DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME = Duration.ofMinutes(5); // Fast sync config private final int fastSyncPivotDistance; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index e86548a62c..5a04fb3940 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -13,8 +13,13 @@ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; import static java.util.concurrent.CompletableFuture.completedFuture; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.CHAIN_TOO_SHORT; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.NO_PEERS_AVAILABLE; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.SUCCESS; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.UNEXPECTED_ERROR; import tech.pegasys.pantheon.ethereum.ProtocolContext; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; @@ -25,6 +30,7 @@ import tech.pegasys.pantheon.metrics.OperationTimer; import tech.pegasys.pantheon.util.ExceptionUtils; +import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeoutException; @@ -54,7 +60,7 @@ public FastSyncActions( this.ethTasksTimer = ethTasksTimer; } - public CompletableFuture waitForSuitablePeers() { + public CompletableFuture waitForSuitablePeers() { final WaitForPeersTask waitForPeersTask = WaitForPeersTask.create( ethContext, syncConfig.getFastSyncMinimumPeerCount(), ethTasksTimer); @@ -69,28 +75,46 @@ public CompletableFuture waitForSuitablePeers() { LOG.warn( "Fast sync timed out before minimum peer count was reached. Continuing with reduced peers."); } else { - return FastSyncResult.NO_PEERS_AVAILABLE; + return FastSyncState.withResult(NO_PEERS_AVAILABLE); } } else if (error != null) { LOG.error("Failed to find peers for fast sync", error); - return FastSyncResult.UNEXPECTED_ERROR; + return FastSyncState.withResult(UNEXPECTED_ERROR); } - return FastSyncResult.SUCCESS; + return FastSyncState.withResult(SUCCESS); }) .thenCompose( result -> - result == FastSyncResult.NO_PEERS_AVAILABLE + result.getLastActionResult() == NO_PEERS_AVAILABLE ? waitForAnyPeer() : completedFuture(result)); } - private CompletionStage waitForAnyPeer() { + private CompletionStage waitForAnyPeer() { LOG.warn( "Maximum wait time for fast sync reached but no peers available. Continuing to wait for any available peer."); final WaitForPeerTask waitForPeerTask = WaitForPeerTask.create(ethContext, ethTasksTimer); return ethContext .getScheduler() .scheduleSyncWorkerTask(waitForPeerTask::run) - .thenApply(voidResult -> FastSyncResult.SUCCESS); + .thenApply(voidResult -> FastSyncState.withResult(SUCCESS)); + } + + public FastSyncState selectPivotBlock() { + return ethContext + .getEthPeers() + .bestPeer() + .map( + peer -> { + final long pivotBlockNumber = + peer.chainState().getEstimatedHeight() - syncConfig.fastSyncPivotDistance(); + if (pivotBlockNumber <= BlockHeader.GENESIS_BLOCK_NUMBER) { + return FastSyncState.withResult(CHAIN_TOO_SHORT); + } else { + LOG.info("Selecting block number {} as fast sync pivot block.", pivotBlockNumber); + return new FastSyncState(SUCCESS, OptionalLong.of(pivotBlockNumber)); + } + }) + .orElseGet(() -> FastSyncState.withResult(NO_PEERS_AVAILABLE)); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java index 6a9ad5f899..1dbb25365f 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -12,7 +12,12 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.FAST_SYNC_UNAVAILABLE; + import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -29,8 +34,18 @@ public CompletableFuture start() { LOG.info("Fast sync enabled"); return fastSyncActions .waitForSuitablePeers() - .thenApply( - result -> - result == FastSyncResult.SUCCESS ? FastSyncResult.FAST_SYNC_UNAVAILABLE : result); + .thenApply(ifSuccessful(fastSyncActions::selectPivotBlock)) + .thenApply(ifSuccessful(() -> FastSyncState.withResult(FAST_SYNC_UNAVAILABLE))) + .thenApply(FastSyncState::getLastActionResult); + } + + private FastSyncState ifSuccessful( + final FastSyncState state, final UnaryOperator nextAction) { + return state.getLastActionResult() == FastSyncResult.SUCCESS ? nextAction.apply(state) : state; + } + + private Function ifSuccessful( + final Supplier nextAction) { + return state -> ifSuccessful(state, ignored -> nextAction.get()); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java index 4224250e26..37860ef82e 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java @@ -16,5 +16,6 @@ public enum FastSyncResult { SUCCESS, FAST_SYNC_UNAVAILABLE, NO_PEERS_AVAILABLE, + CHAIN_TOO_SHORT, UNEXPECTED_ERROR } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java new file mode 100644 index 0000000000..ac2a5eb685 --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; + +import java.util.Objects; +import java.util.OptionalLong; + +public class FastSyncState { + + private final FastSyncResult lastActionResult; + private final OptionalLong pivotBlockNumber; + + public FastSyncState(final FastSyncResult lastActionResult, final OptionalLong pivotBlockNumber) { + this.lastActionResult = lastActionResult; + this.pivotBlockNumber = pivotBlockNumber; + } + + public static FastSyncState withResult(final FastSyncResult result) { + return new FastSyncState(result, OptionalLong.empty()); + } + + public FastSyncResult getLastActionResult() { + return lastActionResult; + } + + public OptionalLong getPivotBlockNumber() { + return pivotBlockNumber; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final FastSyncState that = (FastSyncState) o; + return lastActionResult == that.lastActionResult + && Objects.equals(pivotBlockNumber, that.pivotBlockNumber); + } + + @Override + public int hashCode() { + return Objects.hash(lastActionResult, pivotBlockNumber); + } +} diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java index 41baf4a2f6..e13643cdba 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java @@ -14,6 +14,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.CHAIN_TOO_SHORT; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.NO_PEERS_AVAILABLE; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.SUCCESS; import static tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem.NO_OP_LABELLED_TIMER; import tech.pegasys.pantheon.ethereum.ProtocolContext; @@ -25,6 +28,7 @@ import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; +import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; @@ -34,7 +38,10 @@ public class FastSyncActionsTest { private final SynchronizerConfiguration syncConfig = - new SynchronizerConfiguration.Builder().syncMode(SyncMode.FAST).build(); + new SynchronizerConfiguration.Builder() + .syncMode(SyncMode.FAST) + .fastSyncPivotDistance(1000) + .build(); @SuppressWarnings("unchecked") private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); @@ -64,25 +71,57 @@ public void waitForPeersShouldSucceedIfEnoughPeersAreFound() { for (int i = 0; i < syncConfig.getFastSyncMinimumPeerCount(); i++) { EthProtocolManagerTestUtil.createPeer(ethProtocolManager); } - final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); - assertThat(result).isCompletedWithValue(FastSyncResult.SUCCESS); + final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); + assertThat(result).isCompletedWithValue(FastSyncState.withResult(SUCCESS)); } @Test public void waitForPeersShouldReportSuccessWhenTimeLimitReachedAndAPeerIsAvailable() { EthProtocolManagerTestUtil.createPeer(ethProtocolManager); timeout.set(true); - final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); - assertThat(result).isCompletedWithValue(FastSyncResult.SUCCESS); + final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); + assertThat(result).isCompletedWithValue(FastSyncState.withResult(SUCCESS)); } @Test public void waitForPeersShouldContinueWaitingUntilAtLeastOnePeerIsAvailable() { timeout.set(true); - final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); + final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); assertThat(result).isNotCompleted(); EthProtocolManagerTestUtil.createPeer(ethProtocolManager); - assertThat(result).isCompletedWithValue(FastSyncResult.SUCCESS); + assertThat(result).isCompletedWithValue(FastSyncState.withResult(SUCCESS)); + } + + @Test + public void selectPivotBlockShouldSelectBlockPivotDistanceFromBestPeer() { + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 5000); + + final FastSyncState result = fastSyncActions.selectPivotBlock(); + final FastSyncState expected = new FastSyncState(SUCCESS, OptionalLong.of(4000)); + assertThat(result).isEqualTo(expected); + } + + @Test + public void selectPivotBlockShouldFailIfNoPeersAreAvailable() { + assertThat(fastSyncActions.selectPivotBlock()) + .isEqualTo(FastSyncState.withResult(NO_PEERS_AVAILABLE)); + } + + @Test + public void selectPivotBlockShouldFailIfBestPeerChainIsShorterThanPivotDistance() { + EthProtocolManagerTestUtil.createPeer( + ethProtocolManager, syncConfig.fastSyncPivotDistance() - 1); + + final FastSyncState result = fastSyncActions.selectPivotBlock(); + assertThat(result).isEqualTo(FastSyncState.withResult(CHAIN_TOO_SHORT)); + } + + @Test + public void selectPivotBlockShouldFailIfBestPeerChainIsEqualToPivotDistance() { + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, syncConfig.fastSyncPivotDistance()); + + final FastSyncState result = fastSyncActions.selectPivotBlock(); + assertThat(result).isEqualTo(FastSyncState.withResult(CHAIN_TOO_SHORT)); } } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java index 3ab1e82b6a..574ad983dd 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -15,8 +15,13 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.SUCCESS; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.UNEXPECTED_ERROR; +import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; import org.junit.Test; @@ -29,9 +34,11 @@ public class FastSyncDownloaderTest { private final FastSyncDownloader downloader = new FastSyncDownloader<>(fastSyncActions); @Test - public void shouldWaitForSuitablePeersThenFail() { + public void shouldWaitForSuitablePeersThenSelectPivotBlock() { when(fastSyncActions.waitForSuitablePeers()) - .thenReturn(completedFuture(FastSyncResult.SUCCESS)); + .thenReturn(completedFuture(FastSyncState.withResult(SUCCESS))); + when(fastSyncActions.selectPivotBlock()) + .thenReturn(new FastSyncState(SUCCESS, OptionalLong.of(50))); final CompletableFuture result = downloader.start(); @@ -41,10 +48,13 @@ public void shouldWaitForSuitablePeersThenFail() { @Test public void shouldAbortIfWaitForSuitablePeersFails() { when(fastSyncActions.waitForSuitablePeers()) - .thenReturn(completedFuture(FastSyncResult.UNEXPECTED_ERROR)); + .thenReturn(completedFuture(FastSyncState.withResult(UNEXPECTED_ERROR))); final CompletableFuture result = downloader.start(); - assertThat(result).isCompletedWithValue(FastSyncResult.UNEXPECTED_ERROR); + assertThat(result).isCompletedWithValue(UNEXPECTED_ERROR); + + verify(fastSyncActions).waitForSuitablePeers(); + verifyNoMoreInteractions(fastSyncActions); } } From 27b044c3b7b219e3580165fb107534e7b314babd Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 21 Jan 2019 13:08:44 +1000 Subject: [PATCH 05/17] Fetch the pivot block header. --- .../eth/sync/fastsync/FastSyncActions.java | 20 +++++++++++ .../eth/sync/fastsync/FastSyncDownloader.java | 32 ++++++++++++----- .../eth/sync/fastsync/FastSyncState.java | 34 +++++++++++++++++-- .../sync/fastsync/FastSyncDownloaderTest.java | 28 +++++++++++++-- 4 files changed, 99 insertions(+), 15 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index 5a04fb3940..648af3c1a0 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -23,6 +23,8 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; +import tech.pegasys.pantheon.ethereum.eth.sync.tasks.AbstractGetHeadersFromPeerTask; +import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetHeadersFromPeerByNumberTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; @@ -30,6 +32,7 @@ import tech.pegasys.pantheon.metrics.OperationTimer; import tech.pegasys.pantheon.util.ExceptionUtils; +import java.util.Optional; import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -117,4 +120,21 @@ public FastSyncState selectPivotBlock() { }) .orElseGet(() -> FastSyncState.withResult(NO_PEERS_AVAILABLE)); } + + public CompletableFuture downloadPivotBlockHeader( + final FastSyncState currentState) { + final long pivotBlockNumber = currentState.getPivotBlockNumber().getAsLong(); + final AbstractGetHeadersFromPeerTask getHeaderTask = + GetHeadersFromPeerByNumberTask.forSingleNumber( + protocolSchedule, ethContext, pivotBlockNumber, ethTasksTimer); + return ethContext + .getScheduler() + .timeout(getHeaderTask) + .thenApply( + taskResult -> + new FastSyncState( + SUCCESS, + currentState.getPivotBlockNumber(), + Optional.of(taskResult.getResult().get(0)))); + } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java index 1dbb25365f..edda2e44f0 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -12,12 +12,12 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; +import static java.util.concurrent.CompletableFuture.completedFuture; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.FAST_SYNC_UNAVAILABLE; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -34,18 +34,32 @@ public CompletableFuture start() { LOG.info("Fast sync enabled"); return fastSyncActions .waitForSuitablePeers() - .thenApply(ifSuccessful(fastSyncActions::selectPivotBlock)) - .thenApply(ifSuccessful(() -> FastSyncState.withResult(FAST_SYNC_UNAVAILABLE))) + .thenCompose(ifSuccessful(fastSyncActions::selectPivotBlock)) + .thenCompose(ifSuccessful(fastSyncActions::downloadPivotBlockHeader)) + .thenCompose( + ifSuccessful( + state -> { + LOG.info("Reached end of current fast sync implementation with state {}", state); + return completedFuture(FastSyncState.withResult(FAST_SYNC_UNAVAILABLE)); + })) .thenApply(FastSyncState::getLastActionResult); } - private FastSyncState ifSuccessful( - final FastSyncState state, final UnaryOperator nextAction) { - return state.getLastActionResult() == FastSyncResult.SUCCESS ? nextAction.apply(state) : state; + private Function> ifSuccessful( + final Supplier nextAction) { + return state -> ifSuccessful(state, ignored -> completedFuture(nextAction.get())); } - private Function ifSuccessful( - final Supplier nextAction) { - return state -> ifSuccessful(state, ignored -> nextAction.get()); + private Function> ifSuccessful( + final Function> nextAction) { + return state -> ifSuccessful(state, nextAction); + } + + private CompletableFuture ifSuccessful( + final FastSyncState state, + final Function> nextAction) { + return state.getLastActionResult() == FastSyncResult.SUCCESS + ? nextAction.apply(state) + : completedFuture(state); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java index ac2a5eb685..25b6a21467 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java @@ -12,21 +12,35 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; + import java.util.Objects; +import java.util.Optional; import java.util.OptionalLong; +import com.google.common.base.MoreObjects; + public class FastSyncState { private final FastSyncResult lastActionResult; private final OptionalLong pivotBlockNumber; + private final Optional pivotBlockHeader; public FastSyncState(final FastSyncResult lastActionResult, final OptionalLong pivotBlockNumber) { + this(lastActionResult, pivotBlockNumber, Optional.empty()); + } + + public FastSyncState( + final FastSyncResult lastActionResult, + final OptionalLong pivotBlockNumber, + final Optional pivotBlockHeader) { this.lastActionResult = lastActionResult; this.pivotBlockNumber = pivotBlockNumber; + this.pivotBlockHeader = pivotBlockHeader; } public static FastSyncState withResult(final FastSyncResult result) { - return new FastSyncState(result, OptionalLong.empty()); + return new FastSyncState(result, OptionalLong.empty(), Optional.empty()); } public FastSyncResult getLastActionResult() { @@ -37,6 +51,10 @@ public OptionalLong getPivotBlockNumber() { return pivotBlockNumber; } + public Optional getPivotBlockHeader() { + return pivotBlockHeader; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -47,11 +65,21 @@ public boolean equals(final Object o) { } final FastSyncState that = (FastSyncState) o; return lastActionResult == that.lastActionResult - && Objects.equals(pivotBlockNumber, that.pivotBlockNumber); + && Objects.equals(pivotBlockNumber, that.pivotBlockNumber) + && Objects.equals(pivotBlockHeader, that.pivotBlockHeader); } @Override public int hashCode() { - return Objects.hash(lastActionResult, pivotBlockNumber); + return Objects.hash(lastActionResult, pivotBlockNumber, pivotBlockHeader); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("lastActionResult", lastActionResult) + .add("pivotBlockNumber", pivotBlockNumber) + .add("pivotBlockHeader", pivotBlockHeader) + .toString(); } } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java index 574ad983dd..43d1d96f3e 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -18,6 +18,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.CHAIN_TOO_SHORT; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.SUCCESS; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.UNEXPECTED_ERROR; @@ -34,14 +35,20 @@ public class FastSyncDownloaderTest { private final FastSyncDownloader downloader = new FastSyncDownloader<>(fastSyncActions); @Test - public void shouldWaitForSuitablePeersThenSelectPivotBlock() { + public void shouldCompleteFastSyncSuccessfully() { when(fastSyncActions.waitForSuitablePeers()) .thenReturn(completedFuture(FastSyncState.withResult(SUCCESS))); - when(fastSyncActions.selectPivotBlock()) - .thenReturn(new FastSyncState(SUCCESS, OptionalLong.of(50))); + final FastSyncState selectPivotBlockState = new FastSyncState(SUCCESS, OptionalLong.of(50)); + when(fastSyncActions.selectPivotBlock()).thenReturn(selectPivotBlockState); + when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) + .thenReturn(completedFuture(selectPivotBlockState)); final CompletableFuture result = downloader.start(); + verify(fastSyncActions).waitForSuitablePeers(); + verify(fastSyncActions).selectPivotBlock(); + verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); + verifyNoMoreInteractions(fastSyncActions); assertThat(result).isCompletedWithValue(FastSyncResult.FAST_SYNC_UNAVAILABLE); } @@ -57,4 +64,19 @@ public void shouldAbortIfWaitForSuitablePeersFails() { verify(fastSyncActions).waitForSuitablePeers(); verifyNoMoreInteractions(fastSyncActions); } + + @Test + public void shouldAbortIfSelectPivotBlockFails() { + when(fastSyncActions.waitForSuitablePeers()) + .thenReturn(completedFuture(FastSyncState.withResult(SUCCESS))); + when(fastSyncActions.selectPivotBlock()).thenReturn(FastSyncState.withResult(CHAIN_TOO_SHORT)); + + final CompletableFuture result = downloader.start(); + + assertThat(result).isCompletedWithValue(CHAIN_TOO_SHORT); + + verify(fastSyncActions).waitForSuitablePeers(); + verify(fastSyncActions).selectPivotBlock(); + verifyNoMoreInteractions(fastSyncActions); + } } From d813ffca4d4d4b6492cfbb0f89099ee67b8b1eb5 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 21 Jan 2019 14:25:13 +1000 Subject: [PATCH 06/17] Switch to throwing an exception to abort the fast sync pipeline instead of having to check the return value is success constantly. --- .../eth/sync/DefaultSynchronizer.java | 8 ++- .../eth/sync/fastsync/FastSyncActions.java | 48 +++++++++-------- .../eth/sync/fastsync/FastSyncDownloader.java | 54 ++++++++----------- ...FastSyncResult.java => FastSyncError.java} | 3 +- .../eth/sync/fastsync/FastSyncException.java | 27 ++++++++++ .../eth/sync/fastsync/FastSyncState.java | 28 ++++------ .../sync/fastsync/FastSyncActionsTest.java | 32 ++++++----- .../sync/fastsync/FastSyncDownloaderTest.java | 36 +++++++------ 8 files changed, 127 insertions(+), 109 deletions(-) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/{FastSyncResult.java => FastSyncError.java} (94%) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncException.java diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java index c62310754f..7f3de7742a 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java @@ -18,7 +18,7 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncActions; import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncDownloader; -import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError; import tech.pegasys.pantheon.ethereum.eth.sync.state.PendingBlocks; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; @@ -89,14 +89,12 @@ public void start() { } } - private void handleFastSyncResult(final FastSyncResult result, final Throwable error) { + private void handleFastSyncResult(final Optional result, final Throwable error) { if (error != null) { LOG.error("Fast sync failed. Switching to full sync.", error); } - if (result == FastSyncResult.SUCCESS) { + if (!result.isPresent()) { LOG.info("Fast sync completed successfully."); - } else { - LOG.error("Fast sync failed: {}", result); } startFullSync(); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index 648af3c1a0..e48979a8bb 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -12,11 +12,8 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.CHAIN_TOO_SHORT; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.NO_PEERS_AVAILABLE; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.SUCCESS; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.UNEXPECTED_ERROR; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.CHAIN_TOO_SHORT; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.NO_PEERS_AVAILABLE; import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.BlockHeader; @@ -35,7 +32,6 @@ import java.util.Optional; import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeoutException; import org.apache.logging.log4j.LogManager; @@ -69,38 +65,45 @@ public CompletableFuture waitForSuitablePeers() { ethContext, syncConfig.getFastSyncMinimumPeerCount(), ethTasksTimer); final EthScheduler scheduler = ethContext.getScheduler(); - return scheduler + final CompletableFuture result = new CompletableFuture<>(); + scheduler .timeout(waitForPeersTask, syncConfig.getFastSyncMaximumPeerWaitTime()) .handle( - (result, error) -> { + (waitResult, error) -> { if (ExceptionUtils.rootCause(error) instanceof TimeoutException) { if (ethContext.getEthPeers().bestPeer().isPresent()) { LOG.warn( "Fast sync timed out before minimum peer count was reached. Continuing with reduced peers."); + result.complete(new FastSyncState()); } else { - return FastSyncState.withResult(NO_PEERS_AVAILABLE); + waitForAnyPeer() + .thenAccept(result::complete) + .exceptionally( + taskError -> { + result.completeExceptionally(error); + return null; + }); } } else if (error != null) { LOG.error("Failed to find peers for fast sync", error); - return FastSyncState.withResult(UNEXPECTED_ERROR); + result.completeExceptionally(error); + } else { + result.complete(new FastSyncState()); } - return FastSyncState.withResult(SUCCESS); - }) - .thenCompose( - result -> - result.getLastActionResult() == NO_PEERS_AVAILABLE - ? waitForAnyPeer() - : completedFuture(result)); + return null; + }); + + return result; } - private CompletionStage waitForAnyPeer() { + private CompletableFuture waitForAnyPeer() { LOG.warn( "Maximum wait time for fast sync reached but no peers available. Continuing to wait for any available peer."); final WaitForPeerTask waitForPeerTask = WaitForPeerTask.create(ethContext, ethTasksTimer); return ethContext .getScheduler() .scheduleSyncWorkerTask(waitForPeerTask::run) - .thenApply(voidResult -> FastSyncState.withResult(SUCCESS)); + .thenApply(voidResult -> new FastSyncState()); } public FastSyncState selectPivotBlock() { @@ -112,13 +115,13 @@ public FastSyncState selectPivotBlock() { final long pivotBlockNumber = peer.chainState().getEstimatedHeight() - syncConfig.fastSyncPivotDistance(); if (pivotBlockNumber <= BlockHeader.GENESIS_BLOCK_NUMBER) { - return FastSyncState.withResult(CHAIN_TOO_SHORT); + throw new FastSyncException(CHAIN_TOO_SHORT); } else { LOG.info("Selecting block number {} as fast sync pivot block.", pivotBlockNumber); - return new FastSyncState(SUCCESS, OptionalLong.of(pivotBlockNumber)); + return new FastSyncState(OptionalLong.of(pivotBlockNumber)); } }) - .orElseGet(() -> FastSyncState.withResult(NO_PEERS_AVAILABLE)); + .orElseThrow(() -> new FastSyncException(NO_PEERS_AVAILABLE)); } public CompletableFuture downloadPivotBlockHeader( @@ -133,7 +136,6 @@ public CompletableFuture downloadPivotBlockHeader( .thenApply( taskResult -> new FastSyncState( - SUCCESS, currentState.getPivotBlockNumber(), Optional.of(taskResult.getResult().get(0)))); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java index edda2e44f0..356d0703a2 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -12,12 +12,13 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.FAST_SYNC_UNAVAILABLE; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.FAST_SYNC_UNAVAILABLE; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.UNEXPECTED_ERROR; +import tech.pegasys.pantheon.util.ExceptionUtils; + +import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.function.Function; -import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -30,36 +31,27 @@ public FastSyncDownloader(final FastSyncActions fastSyncActions) { this.fastSyncActions = fastSyncActions; } - public CompletableFuture start() { + public CompletableFuture> start() { LOG.info("Fast sync enabled"); return fastSyncActions .waitForSuitablePeers() - .thenCompose(ifSuccessful(fastSyncActions::selectPivotBlock)) - .thenCompose(ifSuccessful(fastSyncActions::downloadPivotBlockHeader)) + .thenApply(state -> fastSyncActions.selectPivotBlock()) + .thenCompose(fastSyncActions::downloadPivotBlockHeader) .thenCompose( - ifSuccessful( - state -> { - LOG.info("Reached end of current fast sync implementation with state {}", state); - return completedFuture(FastSyncState.withResult(FAST_SYNC_UNAVAILABLE)); - })) - .thenApply(FastSyncState::getLastActionResult); - } - - private Function> ifSuccessful( - final Supplier nextAction) { - return state -> ifSuccessful(state, ignored -> completedFuture(nextAction.get())); - } - - private Function> ifSuccessful( - final Function> nextAction) { - return state -> ifSuccessful(state, nextAction); - } - - private CompletableFuture ifSuccessful( - final FastSyncState state, - final Function> nextAction) { - return state.getLastActionResult() == FastSyncResult.SUCCESS - ? nextAction.apply(state) - : completedFuture(state); + state -> { + LOG.info("Reached end of current fast sync implementation with state {}", state); + throw new FastSyncException(FAST_SYNC_UNAVAILABLE); + }) + .thenApply(state -> Optional.empty()) + .exceptionally( + error -> { + final Throwable rootCause = ExceptionUtils.rootCause(error); + if (rootCause instanceof FastSyncException) { + return Optional.of(((FastSyncException) rootCause).getError()); + } else { + LOG.error("Fast sync encountered an unexpected error", error); + return Optional.of(UNEXPECTED_ERROR); + } + }); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncError.java similarity index 94% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncError.java index 37860ef82e..44cf41210f 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncResult.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncError.java @@ -12,8 +12,7 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; -public enum FastSyncResult { - SUCCESS, +public enum FastSyncError { FAST_SYNC_UNAVAILABLE, NO_PEERS_AVAILABLE, CHAIN_TOO_SHORT, diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncException.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncException.java new file mode 100644 index 0000000000..6ef16c36cc --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; + +public class FastSyncException extends RuntimeException { + + private final FastSyncError error; + + public FastSyncException(final FastSyncError error) { + super("Fast sync failed: " + error); + this.error = error; + } + + public FastSyncError getError() { + return error; + } +} diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java index 25b6a21467..69718124fb 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncState.java @@ -22,31 +22,23 @@ public class FastSyncState { - private final FastSyncResult lastActionResult; private final OptionalLong pivotBlockNumber; private final Optional pivotBlockHeader; - public FastSyncState(final FastSyncResult lastActionResult, final OptionalLong pivotBlockNumber) { - this(lastActionResult, pivotBlockNumber, Optional.empty()); + public FastSyncState() { + this(OptionalLong.empty(), Optional.empty()); + } + + public FastSyncState(final OptionalLong pivotBlockNumber) { + this(pivotBlockNumber, Optional.empty()); } public FastSyncState( - final FastSyncResult lastActionResult, - final OptionalLong pivotBlockNumber, - final Optional pivotBlockHeader) { - this.lastActionResult = lastActionResult; + final OptionalLong pivotBlockNumber, final Optional pivotBlockHeader) { this.pivotBlockNumber = pivotBlockNumber; this.pivotBlockHeader = pivotBlockHeader; } - public static FastSyncState withResult(final FastSyncResult result) { - return new FastSyncState(result, OptionalLong.empty(), Optional.empty()); - } - - public FastSyncResult getLastActionResult() { - return lastActionResult; - } - public OptionalLong getPivotBlockNumber() { return pivotBlockNumber; } @@ -64,20 +56,18 @@ public boolean equals(final Object o) { return false; } final FastSyncState that = (FastSyncState) o; - return lastActionResult == that.lastActionResult - && Objects.equals(pivotBlockNumber, that.pivotBlockNumber) + return Objects.equals(pivotBlockNumber, that.pivotBlockNumber) && Objects.equals(pivotBlockHeader, that.pivotBlockHeader); } @Override public int hashCode() { - return Objects.hash(lastActionResult, pivotBlockNumber, pivotBlockHeader); + return Objects.hash(pivotBlockNumber, pivotBlockHeader); } @Override public String toString() { return MoreObjects.toStringHelper(this) - .add("lastActionResult", lastActionResult) .add("pivotBlockNumber", pivotBlockNumber) .add("pivotBlockHeader", pivotBlockHeader) .toString(); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java index e13643cdba..1211e9e357 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java @@ -13,10 +13,10 @@ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.CHAIN_TOO_SHORT; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.NO_PEERS_AVAILABLE; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.SUCCESS; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.CHAIN_TOO_SHORT; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.NO_PEERS_AVAILABLE; import static tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem.NO_OP_LABELLED_TIMER; import tech.pegasys.pantheon.ethereum.ProtocolContext; @@ -32,6 +32,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.Before; import org.junit.Test; @@ -72,7 +73,7 @@ public void waitForPeersShouldSucceedIfEnoughPeersAreFound() { EthProtocolManagerTestUtil.createPeer(ethProtocolManager); } final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); - assertThat(result).isCompletedWithValue(FastSyncState.withResult(SUCCESS)); + assertThat(result).isCompletedWithValue(new FastSyncState()); } @Test @@ -80,7 +81,7 @@ public void waitForPeersShouldReportSuccessWhenTimeLimitReachedAndAPeerIsAvailab EthProtocolManagerTestUtil.createPeer(ethProtocolManager); timeout.set(true); final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); - assertThat(result).isCompletedWithValue(FastSyncState.withResult(SUCCESS)); + assertThat(result).isCompletedWithValue(new FastSyncState()); } @Test @@ -90,7 +91,7 @@ public void waitForPeersShouldContinueWaitingUntilAtLeastOnePeerIsAvailable() { assertThat(result).isNotCompleted(); EthProtocolManagerTestUtil.createPeer(ethProtocolManager); - assertThat(result).isCompletedWithValue(FastSyncState.withResult(SUCCESS)); + assertThat(result).isCompletedWithValue(new FastSyncState()); } @Test @@ -98,14 +99,13 @@ public void selectPivotBlockShouldSelectBlockPivotDistanceFromBestPeer() { EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 5000); final FastSyncState result = fastSyncActions.selectPivotBlock(); - final FastSyncState expected = new FastSyncState(SUCCESS, OptionalLong.of(4000)); + final FastSyncState expected = new FastSyncState(OptionalLong.of(4000)); assertThat(result).isEqualTo(expected); } @Test public void selectPivotBlockShouldFailIfNoPeersAreAvailable() { - assertThat(fastSyncActions.selectPivotBlock()) - .isEqualTo(FastSyncState.withResult(NO_PEERS_AVAILABLE)); + assertThrowsFastSyncException(NO_PEERS_AVAILABLE, fastSyncActions::selectPivotBlock); } @Test @@ -113,15 +113,21 @@ public void selectPivotBlockShouldFailIfBestPeerChainIsShorterThanPivotDistance( EthProtocolManagerTestUtil.createPeer( ethProtocolManager, syncConfig.fastSyncPivotDistance() - 1); - final FastSyncState result = fastSyncActions.selectPivotBlock(); - assertThat(result).isEqualTo(FastSyncState.withResult(CHAIN_TOO_SHORT)); + assertThrowsFastSyncException(CHAIN_TOO_SHORT, fastSyncActions::selectPivotBlock); } @Test public void selectPivotBlockShouldFailIfBestPeerChainIsEqualToPivotDistance() { EthProtocolManagerTestUtil.createPeer(ethProtocolManager, syncConfig.fastSyncPivotDistance()); - final FastSyncState result = fastSyncActions.selectPivotBlock(); - assertThat(result).isEqualTo(FastSyncState.withResult(CHAIN_TOO_SHORT)); + assertThrowsFastSyncException(CHAIN_TOO_SHORT, fastSyncActions::selectPivotBlock); + } + + private void assertThrowsFastSyncException( + final FastSyncError expectedError, final ThrowingCallable callable) { + assertThatThrownBy(callable) + .isInstanceOf(FastSyncException.class) + .extracting(exception -> ((FastSyncException) exception).getError()) + .isEqualTo(expectedError); } } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java index 43d1d96f3e..009e4ef32d 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -18,10 +18,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.CHAIN_TOO_SHORT; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.SUCCESS; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncResult.UNEXPECTED_ERROR; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.CHAIN_TOO_SHORT; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.UNEXPECTED_ERROR; +import java.util.Optional; import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; @@ -36,30 +36,29 @@ public class FastSyncDownloaderTest { @Test public void shouldCompleteFastSyncSuccessfully() { - when(fastSyncActions.waitForSuitablePeers()) - .thenReturn(completedFuture(FastSyncState.withResult(SUCCESS))); - final FastSyncState selectPivotBlockState = new FastSyncState(SUCCESS, OptionalLong.of(50)); + when(fastSyncActions.waitForSuitablePeers()).thenReturn(completedFuture(new FastSyncState())); + final FastSyncState selectPivotBlockState = new FastSyncState(OptionalLong.of(50)); when(fastSyncActions.selectPivotBlock()).thenReturn(selectPivotBlockState); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(selectPivotBlockState)); - final CompletableFuture result = downloader.start(); + final CompletableFuture> result = downloader.start(); verify(fastSyncActions).waitForSuitablePeers(); verify(fastSyncActions).selectPivotBlock(); verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); verifyNoMoreInteractions(fastSyncActions); - assertThat(result).isCompletedWithValue(FastSyncResult.FAST_SYNC_UNAVAILABLE); + assertThat(result).isCompletedWithValue(Optional.of(FastSyncError.FAST_SYNC_UNAVAILABLE)); } @Test public void shouldAbortIfWaitForSuitablePeersFails() { when(fastSyncActions.waitForSuitablePeers()) - .thenReturn(completedFuture(FastSyncState.withResult(UNEXPECTED_ERROR))); + .thenReturn(completedExceptionally(new FastSyncException(UNEXPECTED_ERROR))); - final CompletableFuture result = downloader.start(); + final CompletableFuture> result = downloader.start(); - assertThat(result).isCompletedWithValue(UNEXPECTED_ERROR); + assertThat(result).isCompletedWithValue(Optional.of(UNEXPECTED_ERROR)); verify(fastSyncActions).waitForSuitablePeers(); verifyNoMoreInteractions(fastSyncActions); @@ -67,16 +66,21 @@ public void shouldAbortIfWaitForSuitablePeersFails() { @Test public void shouldAbortIfSelectPivotBlockFails() { - when(fastSyncActions.waitForSuitablePeers()) - .thenReturn(completedFuture(FastSyncState.withResult(SUCCESS))); - when(fastSyncActions.selectPivotBlock()).thenReturn(FastSyncState.withResult(CHAIN_TOO_SHORT)); + when(fastSyncActions.waitForSuitablePeers()).thenReturn(completedFuture(new FastSyncState())); + when(fastSyncActions.selectPivotBlock()).thenThrow(new FastSyncException(CHAIN_TOO_SHORT)); - final CompletableFuture result = downloader.start(); + final CompletableFuture> result = downloader.start(); - assertThat(result).isCompletedWithValue(CHAIN_TOO_SHORT); + assertThat(result).isCompletedWithValue(Optional.of(CHAIN_TOO_SHORT)); verify(fastSyncActions).waitForSuitablePeers(); verify(fastSyncActions).selectPivotBlock(); verifyNoMoreInteractions(fastSyncActions); } + + private CompletableFuture completedExceptionally(final Throwable error) { + final CompletableFuture result = new CompletableFuture<>(); + result.completeExceptionally(error); + return result; + } } From 6ecea9cc803770227667ed2d2f46f97b735da05f Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 21 Jan 2019 14:29:40 +1000 Subject: [PATCH 07/17] waitForSuitablePeers doesn't need to return a FastSyncState. --- .../eth/sync/fastsync/FastSyncActions.java | 15 ++++++--------- .../eth/sync/fastsync/FastSyncActionsTest.java | 11 +++++------ .../eth/sync/fastsync/FastSyncDownloaderTest.java | 8 ++++---- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index e48979a8bb..f079baafcc 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -59,13 +59,13 @@ public FastSyncActions( this.ethTasksTimer = ethTasksTimer; } - public CompletableFuture waitForSuitablePeers() { + public CompletableFuture waitForSuitablePeers() { final WaitForPeersTask waitForPeersTask = WaitForPeersTask.create( ethContext, syncConfig.getFastSyncMinimumPeerCount(), ethTasksTimer); final EthScheduler scheduler = ethContext.getScheduler(); - final CompletableFuture result = new CompletableFuture<>(); + final CompletableFuture result = new CompletableFuture<>(); scheduler .timeout(waitForPeersTask, syncConfig.getFastSyncMaximumPeerWaitTime()) .handle( @@ -74,7 +74,7 @@ public CompletableFuture waitForSuitablePeers() { if (ethContext.getEthPeers().bestPeer().isPresent()) { LOG.warn( "Fast sync timed out before minimum peer count was reached. Continuing with reduced peers."); - result.complete(new FastSyncState()); + result.complete(null); } else { waitForAnyPeer() .thenAccept(result::complete) @@ -88,7 +88,7 @@ public CompletableFuture waitForSuitablePeers() { LOG.error("Failed to find peers for fast sync", error); result.completeExceptionally(error); } else { - result.complete(new FastSyncState()); + result.complete(null); } return null; }); @@ -96,14 +96,11 @@ public CompletableFuture waitForSuitablePeers() { return result; } - private CompletableFuture waitForAnyPeer() { + private CompletableFuture waitForAnyPeer() { LOG.warn( "Maximum wait time for fast sync reached but no peers available. Continuing to wait for any available peer."); final WaitForPeerTask waitForPeerTask = WaitForPeerTask.create(ethContext, ethTasksTimer); - return ethContext - .getScheduler() - .scheduleSyncWorkerTask(waitForPeerTask::run) - .thenApply(voidResult -> new FastSyncState()); + return ethContext.getScheduler().scheduleSyncWorkerTask(waitForPeerTask::run); } public FastSyncState selectPivotBlock() { diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java index 1211e9e357..deed0f0994 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java @@ -72,26 +72,25 @@ public void waitForPeersShouldSucceedIfEnoughPeersAreFound() { for (int i = 0; i < syncConfig.getFastSyncMinimumPeerCount(); i++) { EthProtocolManagerTestUtil.createPeer(ethProtocolManager); } - final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); - assertThat(result).isCompletedWithValue(new FastSyncState()); + final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); + assertThat(result).isCompleted(); } @Test public void waitForPeersShouldReportSuccessWhenTimeLimitReachedAndAPeerIsAvailable() { EthProtocolManagerTestUtil.createPeer(ethProtocolManager); timeout.set(true); - final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); - assertThat(result).isCompletedWithValue(new FastSyncState()); + assertThat(fastSyncActions.waitForSuitablePeers()).isCompleted(); } @Test public void waitForPeersShouldContinueWaitingUntilAtLeastOnePeerIsAvailable() { timeout.set(true); - final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); + final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); assertThat(result).isNotCompleted(); EthProtocolManagerTestUtil.createPeer(ethProtocolManager); - assertThat(result).isCompletedWithValue(new FastSyncState()); + assertThat(result).isCompleted(); } @Test diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java index 009e4ef32d..a1ed688078 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -36,7 +36,7 @@ public class FastSyncDownloaderTest { @Test public void shouldCompleteFastSyncSuccessfully() { - when(fastSyncActions.waitForSuitablePeers()).thenReturn(completedFuture(new FastSyncState())); + when(fastSyncActions.waitForSuitablePeers()).thenReturn(completedFuture(null)); final FastSyncState selectPivotBlockState = new FastSyncState(OptionalLong.of(50)); when(fastSyncActions.selectPivotBlock()).thenReturn(selectPivotBlockState); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) @@ -66,7 +66,7 @@ public void shouldAbortIfWaitForSuitablePeersFails() { @Test public void shouldAbortIfSelectPivotBlockFails() { - when(fastSyncActions.waitForSuitablePeers()).thenReturn(completedFuture(new FastSyncState())); + when(fastSyncActions.waitForSuitablePeers()).thenReturn(completedFuture(null)); when(fastSyncActions.selectPivotBlock()).thenThrow(new FastSyncException(CHAIN_TOO_SHORT)); final CompletableFuture> result = downloader.start(); @@ -78,8 +78,8 @@ public void shouldAbortIfSelectPivotBlockFails() { verifyNoMoreInteractions(fastSyncActions); } - private CompletableFuture completedExceptionally(final Throwable error) { - final CompletableFuture result = new CompletableFuture<>(); + private CompletableFuture completedExceptionally(final Throwable error) { + final CompletableFuture result = new CompletableFuture<>(); result.completeExceptionally(error); return result; } From 89b134df13efc5f4d373bf4b78ad4353fbccdea1 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 21 Jan 2019 14:57:15 +1000 Subject: [PATCH 08/17] Add a basic test for downloadPivotBlockHeader. --- .../sync/fastsync/FastSyncActionsTest.java | 36 +++++++++++++++---- .../sync/fastsync/FastSyncDownloaderTest.java | 10 ++++-- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java index deed0f0994..d00c4d9734 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java @@ -14,14 +14,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.CHAIN_TOO_SHORT; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.NO_PEERS_AVAILABLE; import static tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem.NO_OP_LABELLED_TIMER; import tech.pegasys.pantheon.ethereum.ProtocolContext; +import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; +import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; +import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.BlockchainSetupUtil; import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; @@ -44,20 +47,25 @@ public class FastSyncActionsTest { .fastSyncPivotDistance(1000) .build(); - @SuppressWarnings("unchecked") - private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); - - @SuppressWarnings("unchecked") - private final ProtocolContext protocolContext = mock(ProtocolContext.class); + private ProtocolSchedule protocolSchedule; + private ProtocolContext protocolContext; private final LabelledMetric ethTasksTimer = NO_OP_LABELLED_TIMER; private final AtomicBoolean timeout = new AtomicBoolean(false); private FastSyncActions fastSyncActions; private EthProtocolManager ethProtocolManager; + private MutableBlockchain blockchain; @Before public void setUp() { - ethProtocolManager = EthProtocolManagerTestUtil.create(timeout::get); + final BlockchainSetupUtil blockchainSetupUtil = BlockchainSetupUtil.forTesting(); + blockchainSetupUtil.importAllBlocks(); + blockchain = blockchainSetupUtil.getBlockchain(); + protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); + protocolContext = blockchainSetupUtil.getProtocolContext(); + ethProtocolManager = + EthProtocolManagerTestUtil.create( + blockchain, blockchainSetupUtil.getWorldArchive(), timeout::get); fastSyncActions = new FastSyncActions<>( syncConfig, @@ -122,6 +130,20 @@ public void selectPivotBlockShouldFailIfBestPeerChainIsEqualToPivotDistance() { assertThrowsFastSyncException(CHAIN_TOO_SHORT, fastSyncActions::selectPivotBlock); } + @Test + public void downloadPivotBlockHeaderShouldRetrievePivotBlockHeader() { + final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1001); + final CompletableFuture result = + fastSyncActions.downloadPivotBlockHeader(new FastSyncState(OptionalLong.of(1))); + assertThat(result).isNotCompleted(); + + final Responder responder = RespondingEthPeer.blockchainResponder(blockchain); + peer.respond(responder); + + assertThat(result) + .isCompletedWithValue(new FastSyncState(OptionalLong.of(1), blockchain.getBlockHeader(1))); + } + private void assertThrowsFastSyncException( final FastSyncError expectedError, final ThrowingCallable callable) { assertThatThrownBy(callable) diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java index a1ed688078..c72458ab72 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -21,6 +21,8 @@ import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.CHAIN_TOO_SHORT; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.UNEXPECTED_ERROR; +import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; + import java.util.Optional; import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; @@ -36,11 +38,15 @@ public class FastSyncDownloaderTest { @Test public void shouldCompleteFastSyncSuccessfully() { - when(fastSyncActions.waitForSuitablePeers()).thenReturn(completedFuture(null)); final FastSyncState selectPivotBlockState = new FastSyncState(OptionalLong.of(50)); + final FastSyncState downloadPivotBlockHeaderState = + new FastSyncState( + OptionalLong.of(50), + Optional.of(new BlockHeaderTestFixture().number(50).buildHeader())); + when(fastSyncActions.waitForSuitablePeers()).thenReturn(completedFuture(null)); when(fastSyncActions.selectPivotBlock()).thenReturn(selectPivotBlockState); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) - .thenReturn(completedFuture(selectPivotBlockState)); + .thenReturn(completedFuture(downloadPivotBlockHeaderState)); final CompletableFuture> result = downloader.start(); From bf5188eb976a9a2433b865072de16e400da4dc7a Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 21 Jan 2019 16:37:57 +1000 Subject: [PATCH 09/17] Create a task specifically for getting the pivot block header so that it can return a single header, handle retrying and provide a place to do additional validation. --- .../eth/sync/SynchronizerConfiguration.java | 2 +- .../eth/sync/fastsync/FastSyncActions.java | 15 ++-- .../sync/tasks/GetPivotBlockHeaderTask.java | 75 +++++++++++++++++++ 3 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java index 805c9a45c4..a5dba7f7c0 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java @@ -32,7 +32,7 @@ public class SynchronizerConfiguration { public static int DEFAULT_PIVOT_DISTANCE_FROM_HEAD = 500; public static float DEFAULT_FULL_VALIDATION_RATE = .1f; public static int DEFAULT_FAST_SYNC_MINIMUM_PEERS = 5; - private static final Duration DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME = Duration.ofMinutes(5); + private static final Duration DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME = Duration.ofMinutes(3); // Fast sync config private final int fastSyncPivotDistance; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index f079baafcc..91463356b2 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -20,8 +20,7 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.AbstractGetHeadersFromPeerTask; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetHeadersFromPeerByNumberTask; +import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetPivotBlockHeaderTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; @@ -124,16 +123,14 @@ public FastSyncState selectPivotBlock() { public CompletableFuture downloadPivotBlockHeader( final FastSyncState currentState) { final long pivotBlockNumber = currentState.getPivotBlockNumber().getAsLong(); - final AbstractGetHeadersFromPeerTask getHeaderTask = - GetHeadersFromPeerByNumberTask.forSingleNumber( - protocolSchedule, ethContext, pivotBlockNumber, ethTasksTimer); + final GetPivotBlockHeaderTask getHeaderTask = + new GetPivotBlockHeaderTask(protocolSchedule, ethContext, ethTasksTimer, pivotBlockNumber); return ethContext .getScheduler() - .timeout(getHeaderTask) + .scheduleSyncWorkerTask(getHeaderTask::getPivotBlockHeader) .thenApply( - taskResult -> + pivotBlockHeader -> new FastSyncState( - currentState.getPivotBlockNumber(), - Optional.of(taskResult.getResult().get(0)))); + currentState.getPivotBlockNumber(), Optional.of(pivotBlockHeader))); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java new file mode 100644 index 0000000000..67c7e8d840 --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.tasks; + +import tech.pegasys.pantheon.ethereum.core.BlockHeader; +import tech.pegasys.pantheon.ethereum.eth.manager.AbstractRetryingPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.metrics.LabelledMetric; +import tech.pegasys.pantheon.metrics.OperationTimer; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; + +public class GetPivotBlockHeaderTask extends AbstractRetryingPeerTask> { + + private static final int MAX_PIVOT_BLOCK_RETRIES = 5; + private final ProtocolSchedule protocolSchedule; + private final EthContext ethContext; + private final LabelledMetric ethTasksTimer; + private final long pivotBlockNumber; + + public GetPivotBlockHeaderTask( + final ProtocolSchedule protocolSchedule, + final EthContext ethContext, + final LabelledMetric ethTasksTimer, + final long pivotBlockNumber) { + super(ethContext, MAX_PIVOT_BLOCK_RETRIES, ethTasksTimer); + this.protocolSchedule = protocolSchedule; + this.ethContext = ethContext; + this.ethTasksTimer = ethTasksTimer; + this.pivotBlockNumber = pivotBlockNumber; + } + + @Override + protected CompletableFuture> executePeerTask() { + final AbstractGetHeadersFromPeerTask getHeadersTask = + GetHeadersFromPeerByNumberTask.forSingleNumber( + protocolSchedule, ethContext, pivotBlockNumber, ethTasksTimer); + return executeSubTask(getHeadersTask::run) + .thenApply( + peerResult -> { + if (!peerResult.getResult().isEmpty()) { + result.get().complete(peerResult.getResult()); + } + return peerResult.getResult(); + }); + } + + public CompletableFuture getPivotBlockHeader() { + return run().thenApply(singletonList -> singletonList.get(0)); + } + + @Override + protected boolean isRetryableError(final Throwable error) { + return error instanceof NoAvailablePeersException + || error instanceof TimeoutException + || error instanceof PeerBreachedProtocolException + || error instanceof PeerDisconnectedException; + } +} From c1e1c2f1a1bee2c2d3bb2f92452a3ddf7b243d84 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 22 Jan 2019 09:09:26 +1000 Subject: [PATCH 10/17] Add basic tests for GetPivotBlockHeader. --- .../eth/sync/fastsync/FastSyncActions.java | 4 +- .../sync/tasks/GetPivotBlockHeaderTask.java | 18 ++++++-- .../ethtaskutils/RetryingMessageTaskTest.java | 4 +- .../tasks/GetPivotBlockHeaderTaskTest.java | 45 +++++++++++++++++++ 4 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTaskTest.java diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index 91463356b2..424ff78b01 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -39,6 +39,7 @@ public class FastSyncActions { private static final Logger LOG = LogManager.getLogger(); + private static final int MAX_PIVOT_BLOCK_RETRIES = 5; private final SynchronizerConfiguration syncConfig; private final ProtocolSchedule protocolSchedule; private final ProtocolContext protocolContext; @@ -124,7 +125,8 @@ public CompletableFuture downloadPivotBlockHeader( final FastSyncState currentState) { final long pivotBlockNumber = currentState.getPivotBlockNumber().getAsLong(); final GetPivotBlockHeaderTask getHeaderTask = - new GetPivotBlockHeaderTask(protocolSchedule, ethContext, ethTasksTimer, pivotBlockNumber); + GetPivotBlockHeaderTask.forPivotBlock( + protocolSchedule, ethContext, ethTasksTimer, pivotBlockNumber, MAX_PIVOT_BLOCK_RETRIES); return ethContext .getScheduler() .scheduleSyncWorkerTask(getHeaderTask::getPivotBlockHeader) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java index 67c7e8d840..bb3473f14f 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java @@ -28,24 +28,34 @@ public class GetPivotBlockHeaderTask extends AbstractRetryingPeerTask> { - private static final int MAX_PIVOT_BLOCK_RETRIES = 5; private final ProtocolSchedule protocolSchedule; private final EthContext ethContext; private final LabelledMetric ethTasksTimer; private final long pivotBlockNumber; - public GetPivotBlockHeaderTask( + private GetPivotBlockHeaderTask( final ProtocolSchedule protocolSchedule, final EthContext ethContext, final LabelledMetric ethTasksTimer, - final long pivotBlockNumber) { - super(ethContext, MAX_PIVOT_BLOCK_RETRIES, ethTasksTimer); + final long pivotBlockNumber, + final int maxRetries) { + super(ethContext, maxRetries, ethTasksTimer); this.protocolSchedule = protocolSchedule; this.ethContext = ethContext; this.ethTasksTimer = ethTasksTimer; this.pivotBlockNumber = pivotBlockNumber; } + public static GetPivotBlockHeaderTask forPivotBlock( + final ProtocolSchedule protocolSchedule, + final EthContext ethContext, + final LabelledMetric ethTasksTimer, + final long pivotBlockNumber, + final int maxRetries) { + return new GetPivotBlockHeaderTask( + protocolSchedule, ethContext, ethTasksTimer, pivotBlockNumber, maxRetries); + } + @Override protected CompletableFuture> executePeerTask() { final AbstractGetHeadersFromPeerTask getHeadersTask = diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java index 2c548d6cad..7cd23e1446 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java @@ -73,11 +73,11 @@ public void failsWhenPeerReturnsPartialResultThenStops() { // Respond max times with no data respondingPeer.respondTimes(emptyResponder, maxRetries); - assertThat(future.isDone()).isFalse(); + assertThat(future).isNotDone(); // Next retry should fail respondingPeer.respond(emptyResponder); - assertThat(future.isDone()).isTrue(); + assertThat(future).isDone(); assertThat(future.isCompletedExceptionally()).isTrue(); assertThatThrownBy(future::get).hasCauseInstanceOf(MaxRetriesReachedException.class); } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTaskTest.java new file mode 100644 index 0000000000..6b67bf191f --- /dev/null +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTaskTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.tasks; + +import static java.util.Collections.singletonList; + +import tech.pegasys.pantheon.ethereum.core.BlockHeader; +import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; +import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.RetryingMessageTaskTest; + +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; + +public class GetPivotBlockHeaderTaskTest extends RetryingMessageTaskTest> { + + private static final long PIVOT_BLOCK_NUMBER = 10; + + @Override + protected List generateDataToBeRequested() { + return singletonList(blockchain.getBlockHeader(PIVOT_BLOCK_NUMBER).get()); + } + + @Override + protected EthTask> createTask(final List requestedData) { + return GetPivotBlockHeaderTask.forPivotBlock( + protocolSchedule, ethContext, ethTasksTimer, PIVOT_BLOCK_NUMBER, maxRetries); + } + + @Test + @Override + @Ignore("It's not possible to return a partial result as we only ever request one header.") + public void failsWhenPeerReturnsPartialResultThenStops() {} +} From 32bdd13b131b3344177872f6027fe484c06014b3 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 22 Jan 2019 13:33:53 +1000 Subject: [PATCH 11/17] Add check to ensure that a majority of peers (which have the pivot block) agree on the block. --- .../eth/sync/DefaultSynchronizer.java | 2 + .../eth/sync/fastsync/FastSyncActions.java | 21 +- .../eth/sync/fastsync/FastSyncError.java | 1 + .../eth/sync/state/PivotBlockRetriever.java | 130 ++++++++++++ ...a => GetPivotBlockHeaderFromPeerTask.java} | 24 ++- .../sync/state/PivotBlockRetrieverTest.java | 192 ++++++++++++++++++ ... GetPivotBlockHeaderFromPeerTaskTest.java} | 13 +- 7 files changed, 358 insertions(+), 25 deletions(-) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetriever.java rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/{GetPivotBlockHeaderTask.java => GetPivotBlockHeaderFromPeerTask.java} (78%) create mode 100644 ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetrieverTest.java rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/{GetPivotBlockHeaderTaskTest.java => GetPivotBlockHeaderFromPeerTaskTest.java} (81%) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java index 7f3de7742a..bc6dac3f42 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java @@ -95,6 +95,8 @@ private void handleFastSyncResult(final Optional result, final Th } if (!result.isPresent()) { LOG.info("Fast sync completed successfully."); + } else { + LOG.error("Fast sync failed: {}", result); } startFullSync(); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index 424ff78b01..a7484faf2b 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -20,7 +20,7 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetPivotBlockHeaderTask; +import tech.pegasys.pantheon.ethereum.eth.sync.state.PivotBlockRetriever; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; @@ -28,7 +28,6 @@ import tech.pegasys.pantheon.metrics.OperationTimer; import tech.pegasys.pantheon.util.ExceptionUtils; -import java.util.Optional; import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; @@ -39,7 +38,6 @@ public class FastSyncActions { private static final Logger LOG = LogManager.getLogger(); - private static final int MAX_PIVOT_BLOCK_RETRIES = 5; private final SynchronizerConfiguration syncConfig; private final ProtocolSchedule protocolSchedule; private final ProtocolContext protocolContext; @@ -123,16 +121,11 @@ public FastSyncState selectPivotBlock() { public CompletableFuture downloadPivotBlockHeader( final FastSyncState currentState) { - final long pivotBlockNumber = currentState.getPivotBlockNumber().getAsLong(); - final GetPivotBlockHeaderTask getHeaderTask = - GetPivotBlockHeaderTask.forPivotBlock( - protocolSchedule, ethContext, ethTasksTimer, pivotBlockNumber, MAX_PIVOT_BLOCK_RETRIES); - return ethContext - .getScheduler() - .scheduleSyncWorkerTask(getHeaderTask::getPivotBlockHeader) - .thenApply( - pivotBlockHeader -> - new FastSyncState( - currentState.getPivotBlockNumber(), Optional.of(pivotBlockHeader))); + return new PivotBlockRetriever<>( + protocolSchedule, + ethContext, + ethTasksTimer, + currentState.getPivotBlockNumber().getAsLong()) + .downloadPivotBlockHeader(); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncError.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncError.java index 44cf41210f..54640b9e3a 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncError.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncError.java @@ -16,5 +16,6 @@ public enum FastSyncError { FAST_SYNC_UNAVAILABLE, NO_PEERS_AVAILABLE, CHAIN_TOO_SHORT, + PIVOT_BLOCK_HEADER_MISMATCH, UNEXPECTED_ERROR } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetriever.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetriever.java new file mode 100644 index 0000000000..c10f1df623 --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetriever.java @@ -0,0 +1,130 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.state; + +import tech.pegasys.pantheon.ethereum.core.BlockHeader; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncException; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncState; +import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetPivotBlockHeaderFromPeerTask; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.metrics.LabelledMetric; +import tech.pegasys.pantheon.metrics.OperationTimer; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class PivotBlockRetriever { + + private static final Logger LOG = LogManager.getLogger(); + private static final int MAX_PIVOT_BLOCK_RETRIES = 3; + private final long pivotBlockNumber; + private final EthContext ethContext; + private final LabelledMetric ethTasksTimer; + private final ProtocolSchedule protocolSchedule; + private final Map confirmationsByBlockNumber = + new ConcurrentHashMap<>(); + private final CompletableFuture result = new CompletableFuture<>(); + private final Collection getHeaderTasks = + new ConcurrentLinkedQueue<>(); + + public PivotBlockRetriever( + final ProtocolSchedule protocolSchedule, + final EthContext ethContext, + final LabelledMetric ethTasksTimer, + final long pivotBlockNumber) { + this.pivotBlockNumber = pivotBlockNumber; + this.ethContext = ethContext; + this.ethTasksTimer = ethTasksTimer; + this.protocolSchedule = protocolSchedule; + } + + @SuppressWarnings("rawtypes") + public CompletableFuture downloadPivotBlockHeader() { + final CompletableFuture[] requestFutures = requestHeaderFromAllPeers(); + + CompletableFuture.allOf(requestFutures) + .thenRun( + () -> { + // All requests have completed but we still haven't reached agreement on a header. + result.completeExceptionally( + new FastSyncException(FastSyncError.PIVOT_BLOCK_HEADER_MISMATCH)); + }); + return result; + } + + @SuppressWarnings("rawtypes") + private CompletableFuture[] requestHeaderFromAllPeers() { + final List peersToQuery = + ethContext + .getEthPeers() + .availablePeers() + .filter(peer -> peer.chainState().getEstimatedHeight() >= pivotBlockNumber) + .collect(Collectors.toList()); + + final int confirmationsRequired = peersToQuery.size() / 2 + 1; + return peersToQuery + .stream() + .map( + peer -> { + final GetPivotBlockHeaderFromPeerTask getHeaderTask = createGetHeaderTask(peer); + getHeaderTasks.add(getHeaderTask); + return ethContext + .getScheduler() + .scheduleSyncWorkerTask(getHeaderTask::getPivotBlockHeader) + .thenAccept(header -> countHeader(header, confirmationsRequired)); + }) + .toArray(CompletableFuture[]::new); + } + + private GetPivotBlockHeaderFromPeerTask createGetHeaderTask(final EthPeer peer) { + return GetPivotBlockHeaderFromPeerTask.forPivotBlock( + protocolSchedule, + ethContext, + ethTasksTimer, + Optional.of(peer), + pivotBlockNumber, + MAX_PIVOT_BLOCK_RETRIES); + } + + private void countHeader(final BlockHeader header, final int confirmationsRequired) { + final int confirmations = + confirmationsByBlockNumber + .computeIfAbsent(header, key -> new AtomicInteger(0)) + .incrementAndGet(); + LOG.debug( + "Received header {} which now has {} confirmations out of {} required.", + header.getHash(), + confirmations, + confirmationsRequired); + if (confirmations >= confirmationsRequired) { + LOG.info( + "Confirmed pivot block hash {} with {} confirmations", header.getHash(), confirmations); + result.complete(new FastSyncState(OptionalLong.of(header.getNumber()), Optional.of(header))); + getHeaderTasks.forEach(GetPivotBlockHeaderFromPeerTask::cancel); + } + } +} diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTask.java similarity index 78% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTask.java index bb3473f14f..f2081988b6 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTask.java @@ -15,45 +15,53 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.AbstractRetryingPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; -public class GetPivotBlockHeaderTask extends AbstractRetryingPeerTask> { +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +public class GetPivotBlockHeaderFromPeerTask extends AbstractRetryingPeerTask> { + private static final Logger LOG = LogManager.getLogger(); private final ProtocolSchedule protocolSchedule; private final EthContext ethContext; private final LabelledMetric ethTasksTimer; + private final Optional peer; private final long pivotBlockNumber; - private GetPivotBlockHeaderTask( + private GetPivotBlockHeaderFromPeerTask( final ProtocolSchedule protocolSchedule, final EthContext ethContext, final LabelledMetric ethTasksTimer, + final Optional peer, final long pivotBlockNumber, final int maxRetries) { super(ethContext, maxRetries, ethTasksTimer); this.protocolSchedule = protocolSchedule; this.ethContext = ethContext; this.ethTasksTimer = ethTasksTimer; + this.peer = peer; this.pivotBlockNumber = pivotBlockNumber; } - public static GetPivotBlockHeaderTask forPivotBlock( + public static GetPivotBlockHeaderFromPeerTask forPivotBlock( final ProtocolSchedule protocolSchedule, final EthContext ethContext, final LabelledMetric ethTasksTimer, + final Optional peer, final long pivotBlockNumber, final int maxRetries) { - return new GetPivotBlockHeaderTask( - protocolSchedule, ethContext, ethTasksTimer, pivotBlockNumber, maxRetries); + return new GetPivotBlockHeaderFromPeerTask( + protocolSchedule, ethContext, ethTasksTimer, peer, pivotBlockNumber, maxRetries); } @Override @@ -61,6 +69,7 @@ protected CompletableFuture> executePeerTask() { final AbstractGetHeadersFromPeerTask getHeadersTask = GetHeadersFromPeerByNumberTask.forSingleNumber( protocolSchedule, ethContext, pivotBlockNumber, ethTasksTimer); + peer.ifPresent(getHeadersTask::assignPeer); return executeSubTask(getHeadersTask::run) .thenApply( peerResult -> { @@ -79,7 +88,6 @@ public CompletableFuture getPivotBlockHeader() { protected boolean isRetryableError(final Throwable error) { return error instanceof NoAvailablePeersException || error instanceof TimeoutException - || error instanceof PeerBreachedProtocolException - || error instanceof PeerDisconnectedException; + || error instanceof PeerBreachedProtocolException; } } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetrieverTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetrieverTest.java new file mode 100644 index 0000000000..f06e459c09 --- /dev/null +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetrieverTest.java @@ -0,0 +1,192 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.state; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem.NO_OP_LABELLED_TIMER; + +import tech.pegasys.pantheon.ethereum.ProtocolContext; +import tech.pegasys.pantheon.ethereum.chain.Blockchain; +import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; +import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; +import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; +import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; +import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; +import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.BlockchainSetupUtil; +import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode; +import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncException; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncState; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.metrics.LabelledMetric; +import tech.pegasys.pantheon.metrics.OperationTimer; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.util.Optional; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Before; +import org.junit.Test; + +public class PivotBlockRetrieverTest { + + private static final long PIVOT_BLOCK_NUMBER = 10; + private final SynchronizerConfiguration syncConfig = + new SynchronizerConfiguration.Builder() + .syncMode(SyncMode.FAST) + .fastSyncPivotDistance(1000) + .build(); + + private ProtocolSchedule protocolSchedule; + private ProtocolContext protocolContext; + + private final LabelledMetric ethTasksTimer = NO_OP_LABELLED_TIMER; + private final AtomicBoolean timeout = new AtomicBoolean(false); + private EthProtocolManager ethProtocolManager; + private MutableBlockchain blockchain; + private PivotBlockRetriever pivotBlockRetriever; + + @Before + public void setUp() { + final BlockchainSetupUtil blockchainSetupUtil = BlockchainSetupUtil.forTesting(); + blockchainSetupUtil.importAllBlocks(); + blockchain = blockchainSetupUtil.getBlockchain(); + protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); + protocolContext = blockchainSetupUtil.getProtocolContext(); + ethProtocolManager = + EthProtocolManagerTestUtil.create( + blockchain, blockchainSetupUtil.getWorldArchive(), timeout::get); + pivotBlockRetriever = + new PivotBlockRetriever<>( + protocolSchedule, ethProtocolManager.ethContext(), ethTasksTimer, PIVOT_BLOCK_NUMBER); + } + + @Test + public void shouldSucceedWhenAllPeersAgree() { + final Responder responder = + RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + final RespondingEthPeer respondingPeerA = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + final RespondingEthPeer respondingPeerB = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + final RespondingEthPeer respondingPeerC = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + + final CompletableFuture future = pivotBlockRetriever.downloadPivotBlockHeader(); + while (!future.isDone()) { + respondingPeerA.respond(responder); + respondingPeerB.respond(responder); + respondingPeerC.respond(responder); + } + + assertThat(future) + .isCompletedWithValue( + new FastSyncState( + OptionalLong.of(PIVOT_BLOCK_NUMBER), + blockchain.getBlockHeader(PIVOT_BLOCK_NUMBER))); + } + + @Test + public void shouldIgnorePeersThatDoNotHaveThePivotBlock() { + final Responder responder = + RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + final RespondingEthPeer respondingPeerA = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1); + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1); + + final CompletableFuture future = pivotBlockRetriever.downloadPivotBlockHeader(); + while (!future.isDone()) { + respondingPeerA.respondWhile(responder, () -> !future.isDone()); + } + + assertThat(future) + .isCompletedWithValue( + new FastSyncState( + OptionalLong.of(PIVOT_BLOCK_NUMBER), + blockchain.getBlockHeader(PIVOT_BLOCK_NUMBER))); + } + + @Test + public void shouldSucceedWhenMajorityOfPeersAgree() { + final Responder responder = + RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + final Responder fakeResponder = responderForFakeBlock(); + + final RespondingEthPeer respondingPeerA = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + final RespondingEthPeer respondingPeerB = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + final RespondingEthPeer respondingPeerC = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + + final CompletableFuture future = pivotBlockRetriever.downloadPivotBlockHeader(); + while (!future.isDone()) { + respondingPeerA.respond(responder); + respondingPeerB.respond(fakeResponder); + respondingPeerC.respond(responder); + } + + assertThat(future) + .isCompletedWithValue( + new FastSyncState( + OptionalLong.of(PIVOT_BLOCK_NUMBER), + blockchain.getBlockHeader(PIVOT_BLOCK_NUMBER))); + } + + @Test + public void shouldFailWhenPeersReturnDifferentHeaders() { + final Responder responderA = + RespondingEthPeer.blockchainResponder(blockchain, protocolContext.getWorldStateArchive()); + final RespondingEthPeer respondingPeerA = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + + final Responder responderB = responderForFakeBlock(); + final RespondingEthPeer respondingPeerB = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + + // Execute task and wait for response + final AtomicReference actualError = new AtomicReference<>(); + final CompletableFuture future = pivotBlockRetriever.downloadPivotBlockHeader(); + while (!future.isDone()) { + respondingPeerA.respond(responderA); + respondingPeerB.respond(responderB); + } + future.whenComplete((result, error) -> actualError.set(error)); + + assertThat(future).isCompletedExceptionally(); + assertThat(actualError.get()) + .isInstanceOf(FastSyncException.class) + .extracting(e -> ((FastSyncException) e).getError()) + .isEqualTo(FastSyncError.PIVOT_BLOCK_HEADER_MISMATCH); + } + + private Responder responderForFakeBlock() { + final Blockchain mockBlockchain = mock(Blockchain.class); + when(mockBlockchain.getBlockHeader(PIVOT_BLOCK_NUMBER)) + .thenReturn( + Optional.of( + new BlockHeaderTestFixture() + .number(PIVOT_BLOCK_NUMBER) + .extraData(BytesValue.of(1)) + .buildHeader())); + return RespondingEthPeer.blockchainResponder(mockBlockchain); + } +} diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTaskTest.java similarity index 81% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTaskTest.java index 6b67bf191f..dcf05847e1 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTaskTest.java @@ -19,11 +19,13 @@ import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.RetryingMessageTaskTest; import java.util.List; +import java.util.Optional; import org.junit.Ignore; import org.junit.Test; -public class GetPivotBlockHeaderTaskTest extends RetryingMessageTaskTest> { +public class GetPivotBlockHeaderFromPeerTaskTest + extends RetryingMessageTaskTest> { private static final long PIVOT_BLOCK_NUMBER = 10; @@ -34,8 +36,13 @@ protected List generateDataToBeRequested() { @Override protected EthTask> createTask(final List requestedData) { - return GetPivotBlockHeaderTask.forPivotBlock( - protocolSchedule, ethContext, ethTasksTimer, PIVOT_BLOCK_NUMBER, maxRetries); + return GetPivotBlockHeaderFromPeerTask.forPivotBlock( + protocolSchedule, + ethContext, + ethTasksTimer, + Optional.empty(), + PIVOT_BLOCK_NUMBER, + maxRetries); } @Test From 1ac4f4f16839edc284a3a763425d7cf2f3b35456 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 23 Jan 2019 08:35:07 +1000 Subject: [PATCH 12/17] Throw exceptions all the way back out the top instead of mapping to an optional error return. Remove unused FastSyncState. --- .../eth/sync/DefaultSynchronizer.java | 23 +++++++---- .../eth/sync/fastsync/FastSyncDownloader.java | 17 +------- .../eth/sync/state/FastSyncState.java | 39 ------------------- .../sync/fastsync/FastSyncDownloaderTest.java | 26 ++++++++++--- 4 files changed, 36 insertions(+), 69 deletions(-) delete mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/FastSyncState.java diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java index bc6dac3f42..2d05bc8fdb 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java @@ -18,12 +18,14 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncActions; import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncDownloader; -import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncException; +import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncState; import tech.pegasys.pantheon.ethereum.eth.sync.state.PendingBlocks; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; +import tech.pegasys.pantheon.util.ExceptionUtils; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; @@ -89,14 +91,19 @@ public void start() { } } - private void handleFastSyncResult(final Optional result, final Throwable error) { - if (error != null) { - LOG.error("Fast sync failed. Switching to full sync.", error); - } - if (!result.isPresent()) { - LOG.info("Fast sync completed successfully."); + private void handleFastSyncResult(final FastSyncState result, final Throwable error) { + + final Throwable rootCause = ExceptionUtils.rootCause(error); + if (rootCause instanceof FastSyncException) { + LOG.error( + "Fast sync failed ({}), switching to full sync.", + ((FastSyncException) rootCause).getError()); + } else if (error != null) { + LOG.error("Fast sync failed, switching to full sync.", error); } else { - LOG.error("Fast sync failed: {}", result); + LOG.info( + "Fast sync completed successfully with pivot block {}", + result.getPivotBlockNumber().getAsLong()); } startFullSync(); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java index 356d0703a2..7d59f7d84f 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -13,11 +13,7 @@ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.FAST_SYNC_UNAVAILABLE; -import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.UNEXPECTED_ERROR; -import tech.pegasys.pantheon.util.ExceptionUtils; - -import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.apache.logging.log4j.LogManager; @@ -31,7 +27,7 @@ public FastSyncDownloader(final FastSyncActions fastSyncActions) { this.fastSyncActions = fastSyncActions; } - public CompletableFuture> start() { + public CompletableFuture start() { LOG.info("Fast sync enabled"); return fastSyncActions .waitForSuitablePeers() @@ -41,17 +37,6 @@ public CompletableFuture> start() { state -> { LOG.info("Reached end of current fast sync implementation with state {}", state); throw new FastSyncException(FAST_SYNC_UNAVAILABLE); - }) - .thenApply(state -> Optional.empty()) - .exceptionally( - error -> { - final Throwable rootCause = ExceptionUtils.rootCause(error); - if (rootCause instanceof FastSyncException) { - return Optional.of(((FastSyncException) rootCause).getError()); - } else { - LOG.error("Fast sync encountered an unexpected error", error); - return Optional.of(UNEXPECTED_ERROR); - } }); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/FastSyncState.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/FastSyncState.java deleted file mode 100644 index 17c0b6fbfa..0000000000 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/FastSyncState.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2018 ConsenSys AG. - * - * 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. - */ -package tech.pegasys.pantheon.ethereum.eth.sync.state; - -import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; - -public final class FastSyncState { - private long fastSyncTargetBlockNumber = -1; - - private final SynchronizerConfiguration config; - - public FastSyncState(final SynchronizerConfiguration config) { - this.config = config; - } - - /** - * Registers the chain height that we're trying to sync to. - * - * @param blockNumber the height of the chain we are syncing to. - */ - public void setFastSyncChainTarget(final long blockNumber) { - fastSyncTargetBlockNumber = blockNumber; - } - - /** @return the block number at which we switch from fast sync to full sync */ - public long pivot() { - return Math.max(fastSyncTargetBlockNumber - config.fastSyncPivotDistance(), 0); - } -} diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java index c72458ab72..b879aa48f9 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -19,6 +19,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.CHAIN_TOO_SHORT; +import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.FAST_SYNC_UNAVAILABLE; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.UNEXPECTED_ERROR; import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; @@ -48,13 +49,13 @@ public void shouldCompleteFastSyncSuccessfully() { when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - final CompletableFuture> result = downloader.start(); + final CompletableFuture result = downloader.start(); verify(fastSyncActions).waitForSuitablePeers(); verify(fastSyncActions).selectPivotBlock(); verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); verifyNoMoreInteractions(fastSyncActions); - assertThat(result).isCompletedWithValue(Optional.of(FastSyncError.FAST_SYNC_UNAVAILABLE)); + assertCompletedExceptionally(result, FAST_SYNC_UNAVAILABLE); } @Test @@ -62,9 +63,9 @@ public void shouldAbortIfWaitForSuitablePeersFails() { when(fastSyncActions.waitForSuitablePeers()) .thenReturn(completedExceptionally(new FastSyncException(UNEXPECTED_ERROR))); - final CompletableFuture> result = downloader.start(); + final CompletableFuture result = downloader.start(); - assertThat(result).isCompletedWithValue(Optional.of(UNEXPECTED_ERROR)); + assertCompletedExceptionally(result, UNEXPECTED_ERROR); verify(fastSyncActions).waitForSuitablePeers(); verifyNoMoreInteractions(fastSyncActions); @@ -75,9 +76,9 @@ public void shouldAbortIfSelectPivotBlockFails() { when(fastSyncActions.waitForSuitablePeers()).thenReturn(completedFuture(null)); when(fastSyncActions.selectPivotBlock()).thenThrow(new FastSyncException(CHAIN_TOO_SHORT)); - final CompletableFuture> result = downloader.start(); + final CompletableFuture result = downloader.start(); - assertThat(result).isCompletedWithValue(Optional.of(CHAIN_TOO_SHORT)); + assertCompletedExceptionally(result, CHAIN_TOO_SHORT); verify(fastSyncActions).waitForSuitablePeers(); verify(fastSyncActions).selectPivotBlock(); @@ -89,4 +90,17 @@ private CompletableFuture completedExceptionally(final Throwable error) { result.completeExceptionally(error); return result; } + + private void assertCompletedExceptionally( + final CompletableFuture future, final FastSyncError expectedError) { + assertThat(future).isCompletedExceptionally(); + future.exceptionally( + actualError -> { + assertThat(actualError) + .isInstanceOf(FastSyncException.class) + .extracting(ex -> ((FastSyncException) ex).getError()) + .isEqualTo(expectedError); + return null; + }); + } } From 3598197804cebeb7f0394bf6887370aabcd9f847 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 23 Jan 2019 08:48:45 +1000 Subject: [PATCH 13/17] Move PivotBlockRetriever to the fastsync package. --- .../eth/sync/fastsync/FastSyncActions.java | 1 - .../{state => fastsync}/PivotBlockRetriever.java | 5 +---- .../PivotBlockRetrieverTest.java | 15 ++------------- 3 files changed, 3 insertions(+), 18 deletions(-) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/{state => fastsync}/PivotBlockRetriever.java (94%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/{state => fastsync}/PivotBlockRetrieverTest.java (91%) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index a7484faf2b..15ae0f3080 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -20,7 +20,6 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; -import tech.pegasys.pantheon.ethereum.eth.sync.state.PivotBlockRetriever; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetriever.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetriever.java similarity index 94% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetriever.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetriever.java index c10f1df623..9eb47a08cc 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetriever.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetriever.java @@ -10,14 +10,11 @@ * 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. */ -package tech.pegasys.pantheon.ethereum.eth.sync.state; +package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; -import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError; -import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncException; -import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncState; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetPivotBlockHeaderFromPeerTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetrieverTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java similarity index 91% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetrieverTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java index f06e459c09..feff85e000 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/PivotBlockRetrieverTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java @@ -10,7 +10,7 @@ * 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. */ -package tech.pegasys.pantheon.ethereum.eth.sync.state; +package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -26,11 +26,6 @@ import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.BlockchainSetupUtil; -import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode; -import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; -import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError; -import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncException; -import tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncState; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; @@ -48,13 +43,7 @@ public class PivotBlockRetrieverTest { private static final long PIVOT_BLOCK_NUMBER = 10; - private final SynchronizerConfiguration syncConfig = - new SynchronizerConfiguration.Builder() - .syncMode(SyncMode.FAST) - .fastSyncPivotDistance(1000) - .build(); - private ProtocolSchedule protocolSchedule; private ProtocolContext protocolContext; private final LabelledMetric ethTasksTimer = NO_OP_LABELLED_TIMER; @@ -68,7 +57,7 @@ public void setUp() { final BlockchainSetupUtil blockchainSetupUtil = BlockchainSetupUtil.forTesting(); blockchainSetupUtil.importAllBlocks(); blockchain = blockchainSetupUtil.getBlockchain(); - protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); + final ProtocolSchedule protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); protocolContext = blockchainSetupUtil.getProtocolContext(); ethProtocolManager = EthProtocolManagerTestUtil.create( From 6a7fac0d4916285702cf752894217b9d70529956 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 23 Jan 2019 09:06:41 +1000 Subject: [PATCH 14/17] Call wait for peers directly instead of sending to the worker pool. --- .../pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index 15ae0f3080..ea1bb9921f 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -96,8 +96,7 @@ public CompletableFuture waitForSuitablePeers() { private CompletableFuture waitForAnyPeer() { LOG.warn( "Maximum wait time for fast sync reached but no peers available. Continuing to wait for any available peer."); - final WaitForPeerTask waitForPeerTask = WaitForPeerTask.create(ethContext, ethTasksTimer); - return ethContext.getScheduler().scheduleSyncWorkerTask(waitForPeerTask::run); + return WaitForPeerTask.create(ethContext, ethTasksTimer).run(); } public FastSyncState selectPivotBlock() { From 1bdc38d50b5381cf0a48d89b66c5737e545244fa Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 23 Jan 2019 09:10:17 +1000 Subject: [PATCH 15/17] Simplify check for any available peers. --- .../pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index ea1bb9921f..6f24f081de 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -68,7 +68,7 @@ public CompletableFuture waitForSuitablePeers() { .handle( (waitResult, error) -> { if (ExceptionUtils.rootCause(error) instanceof TimeoutException) { - if (ethContext.getEthPeers().bestPeer().isPresent()) { + if (ethContext.getEthPeers().availablePeerCount() == 0) { LOG.warn( "Fast sync timed out before minimum peer count was reached. Continuing with reduced peers."); result.complete(null); From 1ba9c7fbfe8199c4b24d3ae00ab18b15b789fd01 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 23 Jan 2019 10:25:16 +1000 Subject: [PATCH 16/17] Pull isRetryingError and assignPeer up to AbstractRetryingPeerTask so it can be reused. --- .../eth/manager/AbstractRetryingPeerTask.java | 24 ++++++++++--- .../eth/sync/fastsync/FastSyncActions.java | 2 +- .../sync/fastsync/PivotBlockRetriever.java | 24 ++++++------- .../eth/sync/tasks/CompleteBlocksTask.java | 27 +++----------- .../tasks/DownloadHeaderSequenceTask.java | 23 +++++------- .../eth/sync/tasks/ImportBlocksTask.java | 4 +-- ...etryingGetHeaderFromPeerByNumberTask.java} | 36 ++++++------------- ...ingGetHeaderFromPeerByNumberTaskTest.java} | 12 ++----- 8 files changed, 59 insertions(+), 93 deletions(-) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/{GetPivotBlockHeaderFromPeerTask.java => RetryingGetHeaderFromPeerByNumberTask.java} (68%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/{GetPivotBlockHeaderFromPeerTaskTest.java => RetryingGetHeaderFromPeerByNumberTaskTest.java} (84%) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractRetryingPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractRetryingPeerTask.java index e5406f23d3..7499d287af 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractRetryingPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractRetryingPeerTask.java @@ -14,6 +14,8 @@ import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.MaxRetriesReachedException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; @@ -21,7 +23,9 @@ import java.time.Duration; import java.util.Collection; +import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -29,7 +33,7 @@ /** * A task that will retry a fixed number of times before completing the associated CompletableFuture * exceptionally with a new {@link MaxRetriesReachedException}. If the future returned from {@link - * #executePeerTask()} is complete with a non-empty list the retry counter is reset. + * #executePeerTask(Optional)} is complete with a non-empty list the retry counter is reset. * * @param The type as a typed list that the peer task can get partial or full results in. */ @@ -40,6 +44,7 @@ public abstract class AbstractRetryingPeerTask> extends private final int maxRetries; private int retryCount = 0; private final LabelledMetric ethTasksTimer; + private Optional assignedPeer = Optional.empty(); /** * @param ethContext The context of the current Eth network we are attached to. @@ -56,6 +61,10 @@ public AbstractRetryingPeerTask( this.maxRetries = maxRetries; } + public void assignPeer(final EthPeer peer) { + assignedPeer = Optional.of(peer); + } + @Override protected void executeTask() { if (result.get().isDone()) { @@ -68,7 +77,7 @@ protected void executeTask() { } retryCount += 1; - executePeerTask() + executePeerTask(assignedPeer) .whenComplete( (peerResult, error) -> { if (error != null) { @@ -83,7 +92,7 @@ protected void executeTask() { }); } - protected abstract CompletableFuture executePeerTask(); + protected abstract CompletableFuture executePeerTask(Optional assignedPeer); private void handleTaskError(final Throwable error) { final Throwable cause = ExceptionUtils.rootCause(error); @@ -118,5 +127,12 @@ private void handleTaskError(final Throwable error) { .scheduleFutureTask(this::executeTaskTimed, Duration.ofSeconds(1))); } - protected abstract boolean isRetryableError(Throwable error); + private boolean isRetryableError(final Throwable error) { + final boolean isPeerError = + error instanceof PeerBreachedProtocolException + || error instanceof PeerDisconnectedException + || error instanceof NoAvailablePeersException; + + return error instanceof TimeoutException || (!assignedPeer.isPresent() && isPeerError); + } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index 6f24f081de..c88649fc32 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -68,7 +68,7 @@ public CompletableFuture waitForSuitablePeers() { .handle( (waitResult, error) -> { if (ExceptionUtils.rootCause(error) instanceof TimeoutException) { - if (ethContext.getEthPeers().availablePeerCount() == 0) { + if (ethContext.getEthPeers().availablePeerCount() > 0) { LOG.warn( "Fast sync timed out before minimum peer count was reached. Continuing with reduced peers."); result.complete(null); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetriever.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetriever.java index 9eb47a08cc..0f9d81e129 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetriever.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/PivotBlockRetriever.java @@ -15,7 +15,7 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetPivotBlockHeaderFromPeerTask; +import tech.pegasys.pantheon.ethereum.eth.sync.tasks.RetryingGetHeaderFromPeerByNumberTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; @@ -45,7 +45,7 @@ public class PivotBlockRetriever { private final Map confirmationsByBlockNumber = new ConcurrentHashMap<>(); private final CompletableFuture result = new CompletableFuture<>(); - private final Collection getHeaderTasks = + private final Collection getHeaderTasks = new ConcurrentLinkedQueue<>(); public PivotBlockRetriever( @@ -87,24 +87,22 @@ private CompletableFuture[] requestHeaderFromAllPeers() { .stream() .map( peer -> { - final GetPivotBlockHeaderFromPeerTask getHeaderTask = createGetHeaderTask(peer); + final RetryingGetHeaderFromPeerByNumberTask getHeaderTask = createGetHeaderTask(peer); getHeaderTasks.add(getHeaderTask); return ethContext .getScheduler() - .scheduleSyncWorkerTask(getHeaderTask::getPivotBlockHeader) + .scheduleSyncWorkerTask(getHeaderTask::getHeader) .thenAccept(header -> countHeader(header, confirmationsRequired)); }) .toArray(CompletableFuture[]::new); } - private GetPivotBlockHeaderFromPeerTask createGetHeaderTask(final EthPeer peer) { - return GetPivotBlockHeaderFromPeerTask.forPivotBlock( - protocolSchedule, - ethContext, - ethTasksTimer, - Optional.of(peer), - pivotBlockNumber, - MAX_PIVOT_BLOCK_RETRIES); + private RetryingGetHeaderFromPeerByNumberTask createGetHeaderTask(final EthPeer peer) { + final RetryingGetHeaderFromPeerByNumberTask task = + RetryingGetHeaderFromPeerByNumberTask.forPivotBlock( + protocolSchedule, ethContext, ethTasksTimer, pivotBlockNumber, MAX_PIVOT_BLOCK_RETRIES); + task.assignPeer(peer); + return task; } private void countHeader(final BlockHeader header, final int confirmationsRequired) { @@ -121,7 +119,7 @@ private void countHeader(final BlockHeader header, final int confirmationsRequir LOG.info( "Confirmed pivot block hash {} with {} confirmations", header.getHash(), confirmations); result.complete(new FastSyncState(OptionalLong.of(header.getNumber()), Optional.of(header))); - getHeaderTasks.forEach(GetPivotBlockHeaderFromPeerTask::cancel); + getHeaderTasks.forEach(RetryingGetHeaderFromPeerByNumberTask::cancel); } } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTask.java index fe6b7443db..b087d6391b 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTask.java @@ -20,9 +20,6 @@ import tech.pegasys.pantheon.ethereum.eth.manager.AbstractRetryingPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; @@ -32,7 +29,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -54,7 +50,6 @@ public class CompleteBlocksTask extends AbstractRetryingPeerTask> private final List headers; private final Map blocks; - private Optional assignedPeer = Optional.empty(); private CompleteBlocksTask( final ProtocolSchedule protocolSchedule, @@ -92,26 +87,12 @@ public static CompleteBlocksTask forHeaders( } @Override - protected CompletableFuture> executePeerTask() { - return requestBodies().thenCompose(this::processBodiesResult); + protected CompletableFuture> executePeerTask(final Optional assignedPeer) { + return requestBodies(assignedPeer).thenCompose(this::processBodiesResult); } - @Override - protected boolean isRetryableError(final Throwable error) { - final boolean isPeerError = - error instanceof PeerBreachedProtocolException - || error instanceof PeerDisconnectedException - || error instanceof NoAvailablePeersException; - - return error instanceof TimeoutException || (!assignedPeer.isPresent() && isPeerError); - } - - public CompleteBlocksTask assignPeer(final EthPeer peer) { - assignedPeer = Optional.of(peer); - return this; - } - - private CompletableFuture>> requestBodies() { + private CompletableFuture>> requestBodies( + final Optional assignedPeer) { final List incompleteHeaders = incompleteHeaders(); LOG.debug( "Requesting bodies to complete {} blocks, starting with {}.", diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java index 856be8712e..1481fee42a 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java @@ -21,9 +21,7 @@ import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.AbstractRetryingPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; @@ -34,8 +32,8 @@ import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; import com.google.common.primitives.Ints; import org.apache.logging.log4j.LogManager; @@ -120,11 +118,12 @@ public static DownloadHeaderSequenceTask endingAtHeader( } @Override - protected CompletableFuture> executePeerTask() { + protected CompletableFuture> executePeerTask( + final Optional assignedPeer) { LOG.debug( "Downloading headers from {} to {}.", startingBlockNumber, referenceHeader.getNumber() - 1); final CompletableFuture> task = - downloadHeaders().thenCompose(this::processHeaders); + downloadHeaders(assignedPeer).thenCompose(this::processHeaders); return task.whenComplete( (r, t) -> { // We're done if we've filled all requested headers @@ -138,15 +137,8 @@ protected CompletableFuture> executePeerTask() { }); } - @Override - protected boolean isRetryableError(final Throwable error) { - return error instanceof NoAvailablePeersException - || error instanceof TimeoutException - || error instanceof PeerBreachedProtocolException - || error instanceof PeerDisconnectedException; - } - - private CompletableFuture>> downloadHeaders() { + private CompletableFuture>> downloadHeaders( + final Optional assignedPeer) { // Figure out parameters for our headers request final boolean partiallyFilled = lastFilledHeaderIndex < segmentLength; final BlockHeader referenceHeaderForNextRequest = @@ -165,6 +157,7 @@ private CompletableFuture>> downloadHeaders() { referenceHeaderForNextRequest.getNumber(), count + 1, ethTasksTimer); + assignedPeer.ifPresent(headersTask::assignPeer); return headersTask.run(); }); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTask.java index 8a4413b721..31c3db6df9 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTask.java @@ -116,8 +116,8 @@ private CompletableFuture> completeBlocks( } final CompleteBlocksTask task = CompleteBlocksTask.forHeaders( - protocolSchedule, ethContext, headers.getResult(), ethTasksTimer) - .assignPeer(peer); + protocolSchedule, ethContext, headers.getResult(), ethTasksTimer); + task.assignPeer(peer); return executeSubTask(() -> ethContext.getScheduler().timeout(task)); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTask.java similarity index 68% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTask.java index f2081988b6..ea37b607e2 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTask.java @@ -16,8 +16,6 @@ import tech.pegasys.pantheon.ethereum.eth.manager.AbstractRetryingPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; @@ -25,51 +23,44 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class GetPivotBlockHeaderFromPeerTask extends AbstractRetryingPeerTask> { - private static final Logger LOG = LogManager.getLogger(); +public class RetryingGetHeaderFromPeerByNumberTask + extends AbstractRetryingPeerTask> { private final ProtocolSchedule protocolSchedule; private final EthContext ethContext; private final LabelledMetric ethTasksTimer; - private final Optional peer; private final long pivotBlockNumber; - private GetPivotBlockHeaderFromPeerTask( + private RetryingGetHeaderFromPeerByNumberTask( final ProtocolSchedule protocolSchedule, final EthContext ethContext, final LabelledMetric ethTasksTimer, - final Optional peer, final long pivotBlockNumber, final int maxRetries) { super(ethContext, maxRetries, ethTasksTimer); this.protocolSchedule = protocolSchedule; this.ethContext = ethContext; this.ethTasksTimer = ethTasksTimer; - this.peer = peer; this.pivotBlockNumber = pivotBlockNumber; } - public static GetPivotBlockHeaderFromPeerTask forPivotBlock( + public static RetryingGetHeaderFromPeerByNumberTask forPivotBlock( final ProtocolSchedule protocolSchedule, final EthContext ethContext, final LabelledMetric ethTasksTimer, - final Optional peer, final long pivotBlockNumber, final int maxRetries) { - return new GetPivotBlockHeaderFromPeerTask( - protocolSchedule, ethContext, ethTasksTimer, peer, pivotBlockNumber, maxRetries); + return new RetryingGetHeaderFromPeerByNumberTask( + protocolSchedule, ethContext, ethTasksTimer, pivotBlockNumber, maxRetries); } @Override - protected CompletableFuture> executePeerTask() { + protected CompletableFuture> executePeerTask( + final Optional assignedPeer) { final AbstractGetHeadersFromPeerTask getHeadersTask = GetHeadersFromPeerByNumberTask.forSingleNumber( protocolSchedule, ethContext, pivotBlockNumber, ethTasksTimer); - peer.ifPresent(getHeadersTask::assignPeer); + assignedPeer.ifPresent(getHeadersTask::assignPeer); return executeSubTask(getHeadersTask::run) .thenApply( peerResult -> { @@ -80,14 +71,7 @@ protected CompletableFuture> executePeerTask() { }); } - public CompletableFuture getPivotBlockHeader() { + public CompletableFuture getHeader() { return run().thenApply(singletonList -> singletonList.get(0)); } - - @Override - protected boolean isRetryableError(final Throwable error) { - return error instanceof NoAvailablePeersException - || error instanceof TimeoutException - || error instanceof PeerBreachedProtocolException; - } } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTaskTest.java similarity index 84% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTaskTest.java index dcf05847e1..9f39023b15 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetPivotBlockHeaderFromPeerTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTaskTest.java @@ -19,12 +19,11 @@ import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.RetryingMessageTaskTest; import java.util.List; -import java.util.Optional; import org.junit.Ignore; import org.junit.Test; -public class GetPivotBlockHeaderFromPeerTaskTest +public class RetryingGetHeaderFromPeerByNumberTaskTest extends RetryingMessageTaskTest> { private static final long PIVOT_BLOCK_NUMBER = 10; @@ -36,13 +35,8 @@ protected List generateDataToBeRequested() { @Override protected EthTask> createTask(final List requestedData) { - return GetPivotBlockHeaderFromPeerTask.forPivotBlock( - protocolSchedule, - ethContext, - ethTasksTimer, - Optional.empty(), - PIVOT_BLOCK_NUMBER, - maxRetries); + return RetryingGetHeaderFromPeerByNumberTask.forPivotBlock( + protocolSchedule, ethContext, ethTasksTimer, PIVOT_BLOCK_NUMBER, maxRetries); } @Test From e73378904983f75f4d93721deab121587058a6ad Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Thu, 24 Jan 2019 07:24:15 +1000 Subject: [PATCH 17/17] Ensure we repeatedly print messages to indicate we're waiting for a peer to connect to avoid the appearance of hanging forever. --- .../eth/sync/SynchronizerConfiguration.java | 2 +- .../eth/sync/fastsync/FastSyncActions.java | 21 +++++++++++++++++-- .../sync/fastsync/FastSyncActionsTest.java | 12 ++++++----- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java index a5dba7f7c0..997f0d91e6 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java @@ -32,7 +32,7 @@ public class SynchronizerConfiguration { public static int DEFAULT_PIVOT_DISTANCE_FROM_HEAD = 500; public static float DEFAULT_FULL_VALIDATION_RATE = .1f; public static int DEFAULT_FAST_SYNC_MINIMUM_PEERS = 5; - private static final Duration DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME = Duration.ofMinutes(3); + private static final Duration DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME = Duration.ofSeconds(3); // Fast sync config private final int fastSyncPivotDistance; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index c88649fc32..5a895c4548 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -20,7 +20,6 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; @@ -96,7 +95,25 @@ public CompletableFuture waitForSuitablePeers() { private CompletableFuture waitForAnyPeer() { LOG.warn( "Maximum wait time for fast sync reached but no peers available. Continuing to wait for any available peer."); - return WaitForPeerTask.create(ethContext, ethTasksTimer).run(); + final CompletableFuture result = new CompletableFuture<>(); + waitForAnyPeer(result); + return result; + } + + private void waitForAnyPeer(final CompletableFuture result) { + ethContext + .getScheduler() + .timeout(WaitForPeersTask.create(ethContext, 1, ethTasksTimer)) + .whenComplete( + (waitResult, throwable) -> { + if (ExceptionUtils.rootCause(throwable) instanceof TimeoutException) { + waitForAnyPeer(result); + } else if (throwable != null) { + result.completeExceptionally(throwable); + } else { + result.complete(waitResult); + } + }); } public FastSyncState selectPivotBlock() { diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java index d00c4d9734..eda59d844c 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActionsTest.java @@ -33,7 +33,7 @@ import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.Before; @@ -51,7 +51,7 @@ public class FastSyncActionsTest { private ProtocolContext protocolContext; private final LabelledMetric ethTasksTimer = NO_OP_LABELLED_TIMER; - private final AtomicBoolean timeout = new AtomicBoolean(false); + private final AtomicInteger timeoutCount = new AtomicInteger(0); private FastSyncActions fastSyncActions; private EthProtocolManager ethProtocolManager; private MutableBlockchain blockchain; @@ -65,7 +65,9 @@ public void setUp() { protocolContext = blockchainSetupUtil.getProtocolContext(); ethProtocolManager = EthProtocolManagerTestUtil.create( - blockchain, blockchainSetupUtil.getWorldArchive(), timeout::get); + blockchain, + blockchainSetupUtil.getWorldArchive(), + () -> timeoutCount.getAndDecrement() > 0); fastSyncActions = new FastSyncActions<>( syncConfig, @@ -87,13 +89,13 @@ public void waitForPeersShouldSucceedIfEnoughPeersAreFound() { @Test public void waitForPeersShouldReportSuccessWhenTimeLimitReachedAndAPeerIsAvailable() { EthProtocolManagerTestUtil.createPeer(ethProtocolManager); - timeout.set(true); + timeoutCount.set(Integer.MAX_VALUE); assertThat(fastSyncActions.waitForSuitablePeers()).isCompleted(); } @Test public void waitForPeersShouldContinueWaitingUntilAtLeastOnePeerIsAvailable() { - timeout.set(true); + timeoutCount.set(1); final CompletableFuture result = fastSyncActions.waitForSuitablePeers(); assertThat(result).isNotCompleted();