mirror of https://github.com/hyperledger/besu
Add checkpoint sync (#3849)
Add a new way to synchronize which is X_CHECKPOINT. This mode is experimental so use it at your own risk. This mode allows you to do like a snapsync but starting from a specific checkpoint instead of starting from the genesis. This checkpoint will be in the genesis configuration of each network. To add the checkpoint mechanism in a network you just have to add the checkpoint section in the genesis. Currently there is a checkpoint for ropten, goerli and mainnet. Mainnet on i3.2xlarge <6 hours Goerli on i3.2xlarge <1 hours Signed-off-by: Karim TAAM <karim.t2am@gmail.com>pull/3930/head
parent
02d389a19e
commit
6f85e1f83b
@ -0,0 +1,63 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.config; |
||||
|
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.OptionalLong; |
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode; |
||||
import com.google.common.collect.ImmutableMap; |
||||
|
||||
public class CheckpointConfigOptions { |
||||
|
||||
public static final CheckpointConfigOptions DEFAULT = |
||||
new CheckpointConfigOptions(JsonUtil.createEmptyObjectNode()); |
||||
|
||||
private final ObjectNode checkpointConfigRoot; |
||||
|
||||
CheckpointConfigOptions(final ObjectNode checkpointConfigRoot) { |
||||
this.checkpointConfigRoot = checkpointConfigRoot; |
||||
} |
||||
|
||||
public Optional<String> getTotalDifficulty() { |
||||
return JsonUtil.getString(checkpointConfigRoot, "totaldifficulty"); |
||||
} |
||||
|
||||
public OptionalLong getNumber() { |
||||
return JsonUtil.getLong(checkpointConfigRoot, "number"); |
||||
} |
||||
|
||||
public Optional<String> getHash() { |
||||
return JsonUtil.getString(checkpointConfigRoot, "hash"); |
||||
} |
||||
|
||||
public boolean isValid() { |
||||
return getTotalDifficulty().isPresent() && getNumber().isPresent() && getHash().isPresent(); |
||||
} |
||||
|
||||
Map<String, Object> asMap() { |
||||
final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder(); |
||||
getTotalDifficulty().ifPresent(l -> builder.put("totaldifficulty", l)); |
||||
getNumber().ifPresent(l -> builder.put("number", l)); |
||||
getHash().ifPresent(l -> builder.put("hash", l)); |
||||
return builder.build(); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "CheckpointConfigOptions{" + "checkpointConfigRoot=" + checkpointConfigRoot + '}'; |
||||
} |
||||
} |
@ -0,0 +1,51 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.peervalidation; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
|
||||
public class CheckpointBlocksPeerValidator extends RequiredBlocksPeerValidator { |
||||
|
||||
public CheckpointBlocksPeerValidator( |
||||
final ProtocolSchedule protocolSchedule, |
||||
final MetricsSystem metricsSystem, |
||||
final long blockNumber, |
||||
final Hash hash, |
||||
final long chainHeightEstimationBuffer) { |
||||
super(protocolSchedule, metricsSystem, blockNumber, hash, chainHeightEstimationBuffer); |
||||
} |
||||
|
||||
public CheckpointBlocksPeerValidator( |
||||
final ProtocolSchedule protocolSchedule, |
||||
final MetricsSystem metricsSystem, |
||||
final long blockNumber, |
||||
final Hash hash) { |
||||
this( |
||||
protocolSchedule, metricsSystem, blockNumber, hash, DEFAULT_CHAIN_HEIGHT_ESTIMATION_BUFFER); |
||||
} |
||||
|
||||
@Override |
||||
boolean validateBlockHeader(final EthPeer ethPeer, final BlockHeader header) { |
||||
final boolean valid = super.validateBlockHeader(ethPeer, header); |
||||
if (valid) { |
||||
ethPeer.setCheckpointHeader(header); |
||||
} |
||||
return valid; |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlockWithReceipts; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint.Checkpoint; |
||||
|
||||
import java.util.Optional; |
||||
import java.util.function.Consumer; |
||||
|
||||
public class CheckpointBlockImportStep implements Consumer<Optional<BlockWithReceipts>> { |
||||
|
||||
private final CheckpointSource checkpointSource; |
||||
private final Checkpoint checkpoint; |
||||
private final MutableBlockchain blockchain; |
||||
|
||||
public CheckpointBlockImportStep( |
||||
final CheckpointSource checkpointSource, |
||||
final Checkpoint checkpoint, |
||||
final MutableBlockchain blockchain) { |
||||
this.checkpointSource = checkpointSource; |
||||
this.checkpoint = checkpoint; |
||||
this.blockchain = blockchain; |
||||
} |
||||
|
||||
@Override |
||||
public void accept(final Optional<BlockWithReceipts> maybeBlock) { |
||||
maybeBlock.ifPresent( |
||||
block -> { |
||||
blockchain.unsafeImportBlock( |
||||
block.getBlock(), |
||||
block.getReceipts(), |
||||
block.getHash().equals(checkpoint.blockHash()) |
||||
? Optional.of(checkpoint.totalDifficulty()) |
||||
: Optional.empty()); |
||||
checkpointSource.setLastHeaderDownloaded(Optional.of(block.getHeader())); |
||||
if (!checkpointSource.hasNext()) { |
||||
blockchain.unsafeSetChainHead( |
||||
checkpointSource.getCheckpoint(), checkpoint.totalDifficulty()); |
||||
} |
||||
}); |
||||
checkpointSource.notifyTaskAvailable(); |
||||
} |
||||
} |
@ -0,0 +1,81 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.BlockWithReceipts; |
||||
import org.hyperledger.besu.ethereum.core.TransactionReceipt; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.GetBlockFromPeerTask; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.GetReceiptsFromPeerTask; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint.Checkpoint; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
public class CheckpointDownloadBlockStep { |
||||
|
||||
private final ProtocolSchedule protocolSchedule; |
||||
private final EthContext ethContext; |
||||
private final Checkpoint checkpoint; |
||||
private final MetricsSystem metricsSystem; |
||||
|
||||
public CheckpointDownloadBlockStep( |
||||
final ProtocolSchedule protocolSchedule, |
||||
final EthContext ethContext, |
||||
final Checkpoint checkpoint, |
||||
final MetricsSystem metricsSystem) { |
||||
this.protocolSchedule = protocolSchedule; |
||||
this.ethContext = ethContext; |
||||
this.checkpoint = checkpoint; |
||||
this.metricsSystem = metricsSystem; |
||||
} |
||||
|
||||
public CompletableFuture<Optional<BlockWithReceipts>> downloadBlock(final Hash hash) { |
||||
final GetBlockFromPeerTask getBlockFromPeerTask = |
||||
GetBlockFromPeerTask.create( |
||||
protocolSchedule, |
||||
ethContext, |
||||
Optional.of(hash), |
||||
checkpoint.blockNumber(), |
||||
metricsSystem); |
||||
return getBlockFromPeerTask |
||||
.run() |
||||
.thenCompose(this::downloadReceipts) |
||||
.exceptionally(throwable -> Optional.empty()); |
||||
} |
||||
|
||||
private CompletableFuture<Optional<BlockWithReceipts>> downloadReceipts( |
||||
final PeerTaskResult<Block> peerTaskResult) { |
||||
final Block block = peerTaskResult.getResult(); |
||||
final GetReceiptsFromPeerTask getReceiptsFromPeerTask = |
||||
GetReceiptsFromPeerTask.forHeaders(ethContext, List.of(block.getHeader()), metricsSystem); |
||||
return getReceiptsFromPeerTask |
||||
.run() |
||||
.thenCompose( |
||||
receiptTaskResult -> { |
||||
final List<TransactionReceipt> transactionReceipts = |
||||
receiptTaskResult.getResult().get(block.getHeader()); |
||||
return CompletableFuture.completedFuture( |
||||
Optional.of(new BlockWithReceipts(block, transactionReceipts))); |
||||
}) |
||||
.exceptionally(throwable -> Optional.empty()); |
||||
} |
||||
} |
@ -0,0 +1,149 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext; |
||||
import org.hyperledger.besu.ethereum.eth.sync.PivotBlockSelector; |
||||
import org.hyperledger.besu.ethereum.eth.sync.SyncMode; |
||||
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncActions; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncDownloader; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncStateStorage; |
||||
import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapDownloaderFactory; |
||||
import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncDownloader; |
||||
import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncState; |
||||
import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldStateDownloader; |
||||
import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; |
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; |
||||
import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloader; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; |
||||
|
||||
import java.nio.file.Path; |
||||
import java.time.Clock; |
||||
import java.util.Optional; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class CheckpointDownloaderFactory extends SnapDownloaderFactory { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CheckpointDownloaderFactory.class); |
||||
|
||||
public static Optional<FastSyncDownloader<?>> createCheckpointDownloader( |
||||
final PivotBlockSelector pivotBlockSelector, |
||||
final SynchronizerConfiguration syncConfig, |
||||
final Path dataDirectory, |
||||
final ProtocolSchedule protocolSchedule, |
||||
final ProtocolContext protocolContext, |
||||
final MetricsSystem metricsSystem, |
||||
final EthContext ethContext, |
||||
final WorldStateStorage worldStateStorage, |
||||
final SyncState syncState, |
||||
final Clock clock) { |
||||
|
||||
final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); |
||||
final FastSyncStateStorage fastSyncStateStorage = |
||||
new FastSyncStateStorage(fastSyncDataDirectory); |
||||
|
||||
if (SyncMode.isFullSync(syncConfig.getSyncMode())) { |
||||
if (fastSyncStateStorage.isFastSyncInProgress()) { |
||||
throw new IllegalStateException( |
||||
"Unable to change the sync mode when snap sync is incomplete, please restart with checkpoint sync mode"); |
||||
} else { |
||||
return Optional.empty(); |
||||
} |
||||
} |
||||
|
||||
ensureDirectoryExists(fastSyncDataDirectory.toFile()); |
||||
|
||||
final FastSyncState fastSyncState = |
||||
fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); |
||||
if (fastSyncState.getPivotBlockHeader().isEmpty() |
||||
&& protocolContext.getBlockchain().getChainHeadBlockNumber() |
||||
!= BlockHeader.GENESIS_BLOCK_NUMBER) { |
||||
LOG.info( |
||||
"Checkpoint sync was requested, but cannot be enabled because the local blockchain is not empty."); |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
final FastSyncActions fastSyncActions; |
||||
if (syncState.getCheckpoint().isEmpty()) { |
||||
LOG.warn("Unable to find a valid checkpoint configuration. The genesis will be used"); |
||||
fastSyncActions = |
||||
new FastSyncActions( |
||||
syncConfig, |
||||
worldStateStorage, |
||||
protocolSchedule, |
||||
protocolContext, |
||||
ethContext, |
||||
syncState, |
||||
pivotBlockSelector, |
||||
metricsSystem); |
||||
} else { |
||||
LOG.info( |
||||
"Checkpoint sync start with block {} and hash {}", |
||||
syncState.getCheckpoint().get().blockNumber(), |
||||
syncState.getCheckpoint().get().blockHash()); |
||||
fastSyncActions = |
||||
new CheckpointSyncActions( |
||||
syncConfig, |
||||
worldStateStorage, |
||||
protocolSchedule, |
||||
protocolContext, |
||||
ethContext, |
||||
syncState, |
||||
pivotBlockSelector, |
||||
metricsSystem); |
||||
} |
||||
|
||||
final SnapSyncState snapSyncState = |
||||
new SnapSyncState( |
||||
fastSyncStateStorage.loadState( |
||||
ScheduleBasedBlockHeaderFunctions.create(protocolSchedule))); |
||||
worldStateStorage.clear(); |
||||
|
||||
final InMemoryTasksPriorityQueues<SnapDataRequest> snapTaskCollection = |
||||
createSnapWorldStateDownloaderTaskCollection(); |
||||
final WorldStateDownloader snapWorldStateDownloader = |
||||
new SnapWorldStateDownloader( |
||||
ethContext, |
||||
worldStateStorage, |
||||
snapTaskCollection, |
||||
syncConfig.getSnapSyncConfiguration(), |
||||
syncConfig.getWorldStateRequestParallelism(), |
||||
syncConfig.getWorldStateMaxRequestsWithoutProgress(), |
||||
syncConfig.getWorldStateMinMillisBeforeStalling(), |
||||
clock, |
||||
metricsSystem); |
||||
final FastSyncDownloader<SnapDataRequest> fastSyncDownloader = |
||||
new SnapSyncDownloader( |
||||
fastSyncActions, |
||||
worldStateStorage, |
||||
snapWorldStateDownloader, |
||||
fastSyncStateStorage, |
||||
snapTaskCollection, |
||||
fastSyncDataDirectory, |
||||
snapSyncState); |
||||
syncState.setWorldStateDownloadStatus(snapWorldStateDownloader); |
||||
return Optional.of(fastSyncDownloader); |
||||
} |
||||
} |
@ -0,0 +1,75 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; |
||||
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; |
||||
|
||||
import java.util.Iterator; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
|
||||
public class CheckpointSource implements Iterator<Hash> { |
||||
|
||||
private final SyncState syncState; |
||||
private final BlockHeader checkpoint; |
||||
private final int nbBlocks; |
||||
private Optional<BlockHeader> lastHeaderDownloaded; |
||||
|
||||
private final AtomicBoolean isDownloading = new AtomicBoolean(false); |
||||
|
||||
public CheckpointSource( |
||||
final SyncState syncState, |
||||
final EthPeer ethPeer, |
||||
final BlockHeaderFunctions blockHeaderFunctions) { |
||||
this.syncState = syncState; |
||||
this.checkpoint = ethPeer.getCheckpointHeader().orElseThrow(); |
||||
this.nbBlocks = blockHeaderFunctions.getCheckPointWindowSize(checkpoint); |
||||
this.lastHeaderDownloaded = Optional.empty(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean hasNext() { |
||||
return syncState.getLocalChainHeight() == 0 |
||||
&& lastHeaderDownloaded |
||||
.map(blockHeader -> blockHeader.getNumber() > (checkpoint.getNumber() - nbBlocks)) |
||||
.orElse(true); |
||||
} |
||||
|
||||
@Override |
||||
public synchronized Hash next() { |
||||
isDownloading.getAndSet(true); |
||||
return lastHeaderDownloaded |
||||
.map(ProcessableBlockHeader::getParentHash) |
||||
.orElse(checkpoint.getHash()); |
||||
} |
||||
|
||||
public synchronized void notifyTaskAvailable() { |
||||
isDownloading.getAndSet(false); |
||||
notifyAll(); |
||||
} |
||||
|
||||
public BlockHeader getCheckpoint() { |
||||
return checkpoint; |
||||
} |
||||
|
||||
public void setLastHeaderDownloaded(final Optional<BlockHeader> lastHeaderDownloaded) { |
||||
this.lastHeaderDownloaded = lastHeaderDownloaded; |
||||
} |
||||
} |
@ -0,0 +1,62 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext; |
||||
import org.hyperledger.besu.ethereum.eth.sync.ChainDownloader; |
||||
import org.hyperledger.besu.ethereum.eth.sync.PivotBlockSelector; |
||||
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncActions; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; |
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
|
||||
public class CheckpointSyncActions extends FastSyncActions { |
||||
public CheckpointSyncActions( |
||||
final SynchronizerConfiguration syncConfig, |
||||
final WorldStateStorage worldStateStorage, |
||||
final ProtocolSchedule protocolSchedule, |
||||
final ProtocolContext protocolContext, |
||||
final EthContext ethContext, |
||||
final SyncState syncState, |
||||
final PivotBlockSelector pivotBlockSelector, |
||||
final MetricsSystem metricsSystem) { |
||||
super( |
||||
syncConfig, |
||||
worldStateStorage, |
||||
protocolSchedule, |
||||
protocolContext, |
||||
ethContext, |
||||
syncState, |
||||
pivotBlockSelector, |
||||
metricsSystem); |
||||
} |
||||
|
||||
@Override |
||||
public ChainDownloader createChainDownloader(final FastSyncState currentState) { |
||||
return CheckpointSyncChainDownloader.create( |
||||
syncConfig, |
||||
worldStateStorage, |
||||
protocolSchedule, |
||||
protocolContext, |
||||
ethContext, |
||||
syncState, |
||||
metricsSystem, |
||||
currentState); |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext; |
||||
import org.hyperledger.besu.ethereum.eth.sync.ChainDownloader; |
||||
import org.hyperledger.besu.ethereum.eth.sync.PipelineChainDownloader; |
||||
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncChainDownloader; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncTargetManager; |
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
|
||||
public class CheckpointSyncChainDownloader extends FastSyncChainDownloader { |
||||
|
||||
public static ChainDownloader create( |
||||
final SynchronizerConfiguration config, |
||||
final WorldStateStorage worldStateStorage, |
||||
final ProtocolSchedule protocolSchedule, |
||||
final ProtocolContext protocolContext, |
||||
final EthContext ethContext, |
||||
final SyncState syncState, |
||||
final MetricsSystem metricsSystem, |
||||
final FastSyncState fastSyncState) { |
||||
|
||||
final FastSyncTargetManager syncTargetManager = |
||||
new FastSyncTargetManager( |
||||
config, |
||||
worldStateStorage, |
||||
protocolSchedule, |
||||
protocolContext, |
||||
ethContext, |
||||
metricsSystem, |
||||
fastSyncState); |
||||
|
||||
return new PipelineChainDownloader( |
||||
syncState, |
||||
syncTargetManager, |
||||
new CheckpointSyncDownloadPipelineFactory( |
||||
config, protocolSchedule, protocolContext, ethContext, fastSyncState, metricsSystem), |
||||
ethContext.getScheduler(), |
||||
metricsSystem); |
||||
} |
||||
} |
@ -0,0 +1,101 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; |
||||
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncDownloadPipelineFactory; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint.Checkpoint; |
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; |
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncTarget; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.metrics.BesuMetricCategory; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
import org.hyperledger.besu.services.pipeline.Pipeline; |
||||
import org.hyperledger.besu.services.pipeline.PipelineBuilder; |
||||
|
||||
import java.util.concurrent.CompletionStage; |
||||
|
||||
public class CheckpointSyncDownloadPipelineFactory extends FastSyncDownloadPipelineFactory { |
||||
|
||||
public CheckpointSyncDownloadPipelineFactory( |
||||
final SynchronizerConfiguration syncConfig, |
||||
final ProtocolSchedule protocolSchedule, |
||||
final ProtocolContext protocolContext, |
||||
final EthContext ethContext, |
||||
final FastSyncState fastSyncState, |
||||
final MetricsSystem metricsSystem) { |
||||
super(syncConfig, protocolSchedule, protocolContext, ethContext, fastSyncState, metricsSystem); |
||||
} |
||||
|
||||
@Override |
||||
public CompletionStage<Void> startPipeline( |
||||
final EthScheduler scheduler, |
||||
final SyncState syncState, |
||||
final SyncTarget syncTarget, |
||||
final Pipeline<?> pipeline) { |
||||
return scheduler |
||||
.startPipeline(createDownloadCheckPointPipeline(syncState, syncTarget)) |
||||
.thenCompose(unused -> scheduler.startPipeline(pipeline)); |
||||
} |
||||
|
||||
protected Pipeline<Hash> createDownloadCheckPointPipeline( |
||||
final SyncState syncState, final SyncTarget target) { |
||||
|
||||
final Checkpoint checkpoint = syncState.getCheckpoint().orElseThrow(); |
||||
|
||||
final CheckpointSource checkPointSource = |
||||
new CheckpointSource( |
||||
syncState, |
||||
target.peer(), |
||||
protocolSchedule.getByBlockNumber(checkpoint.blockNumber()).getBlockHeaderFunctions()); |
||||
|
||||
final CheckpointBlockImportStep checkPointBlockImportStep = |
||||
new CheckpointBlockImportStep( |
||||
checkPointSource, checkpoint, protocolContext.getBlockchain()); |
||||
|
||||
final CheckpointDownloadBlockStep checkPointDownloadBlockStep = |
||||
new CheckpointDownloadBlockStep(protocolSchedule, ethContext, checkpoint, metricsSystem); |
||||
|
||||
return PipelineBuilder.createPipelineFrom( |
||||
"fetchCheckpoints", |
||||
checkPointSource, |
||||
1, |
||||
metricsSystem.createLabelledCounter( |
||||
BesuMetricCategory.SYNCHRONIZER, |
||||
"chain_download_pipeline_processed_total", |
||||
"Number of header process by each chain download pipeline stage", |
||||
"step", |
||||
"action"), |
||||
true, |
||||
"checkpointSync") |
||||
.thenProcessAsyncOrdered("downloadBlock", checkPointDownloadBlockStep::downloadBlock, 1) |
||||
.andFinishWith("importBlock", checkPointBlockImportStep); |
||||
} |
||||
|
||||
@Override |
||||
protected BlockHeader getCommonAncestor(final SyncTarget target) { |
||||
return target |
||||
.peer() |
||||
.getCheckpointHeader() |
||||
.filter(checkpoint -> checkpoint.getNumber() > target.commonAncestor().getNumber()) |
||||
.orElse(target.commonAncestor()); |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.Difficulty; |
||||
|
||||
import org.immutables.value.Value; |
||||
|
||||
@Value.Immutable |
||||
public interface Checkpoint { |
||||
|
||||
long blockNumber(); |
||||
|
||||
Hash blockHash(); |
||||
|
||||
Difficulty totalDifficulty(); |
||||
} |
@ -0,0 +1,82 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; |
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.BlockBody; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; |
||||
import org.hyperledger.besu.ethereum.core.BlockWithReceipts; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint.Checkpoint; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; |
||||
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.Optional; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
public class CheckPointBlockImportStepTest { |
||||
|
||||
private final CheckpointSource checkPointSource = mock(CheckpointSource.class); |
||||
private final Checkpoint checkpoint = mock(Checkpoint.class); |
||||
private MutableBlockchain blockchain; |
||||
private CheckpointBlockImportStep checkPointHeaderImportStep; |
||||
private KeyValueStoragePrefixedKeyBlockchainStorage blockchainStorage; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
blockchainStorage = |
||||
new KeyValueStoragePrefixedKeyBlockchainStorage( |
||||
new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); |
||||
blockchain = |
||||
DefaultBlockchain.createMutable( |
||||
generateBlock(0), blockchainStorage, mock(MetricsSystem.class), 0); |
||||
checkPointHeaderImportStep = |
||||
new CheckpointBlockImportStep(checkPointSource, checkpoint, blockchain); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldSaveNewHeader() { |
||||
when(checkPointSource.hasNext()).thenReturn(true); |
||||
assertThat(blockchainStorage.getBlockHash(1)).isEmpty(); |
||||
final Block block = generateBlock(1); |
||||
checkPointHeaderImportStep.accept(Optional.of(new BlockWithReceipts(block, new ArrayList<>()))); |
||||
assertThat(blockchainStorage.getBlockHash(1)).isPresent(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldSaveChainHeadForLastBlock() { |
||||
when(checkPointSource.hasNext()).thenReturn(false); |
||||
final Block block = generateBlock(2); |
||||
when(checkPointSource.getCheckpoint()).thenReturn(block.getHeader()); |
||||
checkPointHeaderImportStep.accept(Optional.of(new BlockWithReceipts(block, new ArrayList<>()))); |
||||
assertThat(blockchainStorage.getBlockHash(2)).isPresent(); |
||||
} |
||||
|
||||
private Block generateBlock(final int blockNumber) { |
||||
final BlockBody body = new BlockBody(Collections.emptyList(), Collections.emptyList()); |
||||
return new Block(new BlockHeaderTestFixture().number(blockNumber).buildHeader(), body); |
||||
} |
||||
} |
@ -0,0 +1,81 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
public class CheckPointSourceTest { |
||||
|
||||
private final SyncState syncState = mock(SyncState.class); |
||||
private final EthPeer peer = mock(EthPeer.class); |
||||
private final BlockHeaderFunctions blockHeaderFunctions = mock(BlockHeaderFunctions.class); |
||||
private CheckpointSource checkPointSource; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
when(peer.getCheckpointHeader()).thenReturn(Optional.of(header(12))); |
||||
when(blockHeaderFunctions.getCheckPointWindowSize(any(BlockHeader.class))).thenReturn(1); |
||||
checkPointSource = new CheckpointSource(syncState, peer, blockHeaderFunctions); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotHasNextWhenChainHeightIsNotZero() { |
||||
when(syncState.getLocalChainHeight()).thenReturn(1L); |
||||
assertThat(checkPointSource.hasNext()).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldHasNextWhenLocalChainIsZero() { |
||||
when(syncState.getLocalChainHeight()).thenReturn(0L); |
||||
assertThat(checkPointSource.hasNext()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldHasNextWhenMissingHeader() { |
||||
when(syncState.getLocalChainHeight()).thenReturn(0L); |
||||
checkPointSource.setLastHeaderDownloaded(Optional.of(header(12))); |
||||
assertThat(checkPointSource.hasNext()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnCheckPointForFirstNext() { |
||||
assertThat(checkPointSource.next()).isEqualTo(checkPointSource.getCheckpoint().getHash()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnParentHashForOtherNextCall() { |
||||
final BlockHeader header = header(12); |
||||
checkPointSource.setLastHeaderDownloaded(Optional.of(header)); |
||||
assertThat(checkPointSource.next()).isEqualTo(header.getParentHash()); |
||||
} |
||||
|
||||
private BlockHeader header(final int number) { |
||||
return new BlockHeaderTestFixture().number(number).buildHeader(); |
||||
} |
||||
} |
@ -0,0 +1,197 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode.LIGHT_SKIP_DETACHED; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; |
||||
import org.hyperledger.besu.ethereum.core.Difficulty; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; |
||||
import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; |
||||
import org.hyperledger.besu.ethereum.eth.sync.ChainDownloader; |
||||
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncValidationPolicy; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint.Checkpoint; |
||||
import org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint.ImmutableCheckpoint; |
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; |
||||
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.junit.runners.Parameterized; |
||||
import org.junit.runners.Parameterized.Parameters; |
||||
|
||||
@RunWith(Parameterized.class) |
||||
public class CheckPointSyncChainDownloaderTest { |
||||
|
||||
private final FastSyncValidationPolicy validationPolicy = mock(FastSyncValidationPolicy.class); |
||||
private final WorldStateStorage worldStateStorage = mock(WorldStateStorage.class); |
||||
|
||||
protected ProtocolSchedule protocolSchedule; |
||||
protected EthProtocolManager ethProtocolManager; |
||||
protected EthContext ethContext; |
||||
protected ProtocolContext protocolContext; |
||||
private SyncState syncState; |
||||
|
||||
protected MutableBlockchain localBlockchain; |
||||
private BlockchainSetupUtil otherBlockchainSetup; |
||||
protected Blockchain otherBlockchain; |
||||
private Checkpoint checkpoint; |
||||
|
||||
@Parameters |
||||
public static Collection<Object[]> data() { |
||||
return Arrays.asList(new Object[][] {{DataStorageFormat.BONSAI}, {DataStorageFormat.FOREST}}); |
||||
} |
||||
|
||||
private final DataStorageFormat storageFormat; |
||||
|
||||
public CheckPointSyncChainDownloaderTest(final DataStorageFormat storageFormat) { |
||||
this.storageFormat = storageFormat; |
||||
} |
||||
|
||||
@Before |
||||
public void setup() { |
||||
when(validationPolicy.getValidationModeForNextBlock()).thenReturn(LIGHT_SKIP_DETACHED); |
||||
when(worldStateStorage.isWorldStateAvailable(any(), any())).thenReturn(true); |
||||
final BlockchainSetupUtil localBlockchainSetup = BlockchainSetupUtil.forTesting(storageFormat); |
||||
localBlockchain = localBlockchainSetup.getBlockchain(); |
||||
otherBlockchainSetup = BlockchainSetupUtil.forTesting(storageFormat); |
||||
otherBlockchain = otherBlockchainSetup.getBlockchain(); |
||||
protocolSchedule = localBlockchainSetup.getProtocolSchedule(); |
||||
protocolContext = localBlockchainSetup.getProtocolContext(); |
||||
ethProtocolManager = |
||||
EthProtocolManagerTestUtil.create( |
||||
localBlockchain, new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem())); |
||||
ethContext = ethProtocolManager.ethContext(); |
||||
|
||||
final int blockNumber = 10; |
||||
checkpoint = |
||||
ImmutableCheckpoint.builder() |
||||
.blockNumber(blockNumber) |
||||
.blockHash(localBlockchainSetup.getBlocks().get(blockNumber).getHash()) |
||||
.totalDifficulty(Difficulty.ONE) |
||||
.build(); |
||||
|
||||
syncState = |
||||
new SyncState( |
||||
protocolContext.getBlockchain(), |
||||
ethContext.getEthPeers(), |
||||
true, |
||||
Optional.of(checkpoint)); |
||||
} |
||||
|
||||
@After |
||||
public void tearDown() { |
||||
ethProtocolManager.stop(); |
||||
} |
||||
|
||||
private ChainDownloader downloader( |
||||
final SynchronizerConfiguration syncConfig, final long pivotBlockNumber) { |
||||
return CheckpointSyncChainDownloader.create( |
||||
syncConfig, |
||||
worldStateStorage, |
||||
protocolSchedule, |
||||
protocolContext, |
||||
ethContext, |
||||
syncState, |
||||
new NoOpMetricsSystem(), |
||||
new FastSyncState(otherBlockchain.getBlockHeader(pivotBlockNumber).get())); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldSyncToPivotBlockInMultipleSegments() { |
||||
otherBlockchainSetup.importFirstBlocks(30); |
||||
|
||||
final RespondingEthPeer peer = |
||||
EthProtocolManagerTestUtil.createPeer(ethProtocolManager, otherBlockchain); |
||||
final RespondingEthPeer.Responder responder = |
||||
RespondingEthPeer.blockchainResponder(otherBlockchain); |
||||
|
||||
final SynchronizerConfiguration syncConfig = |
||||
SynchronizerConfiguration.builder() |
||||
.downloaderChainSegmentSize(5) |
||||
.downloaderHeadersRequestSize(3) |
||||
.build(); |
||||
final long pivotBlockNumber = 25; |
||||
ethContext |
||||
.getEthPeers() |
||||
.streamAvailablePeers() |
||||
.forEach( |
||||
ethPeer -> { |
||||
ethPeer.setCheckpointHeader( |
||||
otherBlockchainSetup.getBlocks().get((int) checkpoint.blockNumber()).getHeader()); |
||||
}); |
||||
final ChainDownloader downloader = downloader(syncConfig, pivotBlockNumber); |
||||
final CompletableFuture<Void> result = downloader.start(); |
||||
|
||||
peer.respondWhileOtherThreadsWork(responder, () -> !result.isDone()); |
||||
|
||||
assertThat(result).isCompleted(); |
||||
assertThat(localBlockchain.getChainHeadBlockNumber()).isEqualTo(pivotBlockNumber); |
||||
assertThat(localBlockchain.getChainHeadHeader()) |
||||
.isEqualTo(otherBlockchain.getBlockHeader(pivotBlockNumber).get()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldSyncToPivotBlockInSingleSegment() { |
||||
otherBlockchainSetup.importFirstBlocks(30); |
||||
|
||||
final RespondingEthPeer peer = |
||||
EthProtocolManagerTestUtil.createPeer(ethProtocolManager, otherBlockchain); |
||||
final RespondingEthPeer.Responder responder = |
||||
RespondingEthPeer.blockchainResponder(otherBlockchain); |
||||
|
||||
final long pivotBlockNumber = 10; |
||||
final SynchronizerConfiguration syncConfig = SynchronizerConfiguration.builder().build(); |
||||
ethContext |
||||
.getEthPeers() |
||||
.streamAvailablePeers() |
||||
.forEach( |
||||
ethPeer -> { |
||||
ethPeer.setCheckpointHeader( |
||||
otherBlockchainSetup.getBlocks().get((int) checkpoint.blockNumber()).getHeader()); |
||||
}); |
||||
final ChainDownloader downloader = downloader(syncConfig, pivotBlockNumber); |
||||
final CompletableFuture<Void> result = downloader.start(); |
||||
|
||||
peer.respondWhileOtherThreadsWork(responder, () -> !result.isDone()); |
||||
|
||||
assertThat(result).isCompleted(); |
||||
assertThat(localBlockchain.getChainHeadBlockNumber()).isEqualTo(pivotBlockNumber); |
||||
assertThat(localBlockchain.getChainHeadHeader()) |
||||
.isEqualTo(otherBlockchain.getBlockHeader(pivotBlockNumber).get()); |
||||
} |
||||
} |
Loading…
Reference in new issue