mirror of https://github.com/hyperledger/besu
Improving backwards sync (#3638)
Refactor Backwards sync to use a rocks db. Signed-off-by: Jiri Peinlich <jiri.peinlich@gmail.com>pull/3720/head
parent
6c8602d435
commit
918b5359b1
@ -1,172 +0,0 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.backwardsync; |
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerTask; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.RetryingGetBlockFromPeersTask; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
|
||||
import java.time.Duration; |
||||
import java.util.ArrayDeque; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.Queue; |
||||
import java.util.concurrent.CompletableFuture; |
||||
import java.util.function.Function; |
||||
import javax.annotation.concurrent.GuardedBy; |
||||
import javax.annotation.concurrent.ThreadSafe; |
||||
|
||||
import org.slf4j.Logger; |
||||
|
||||
@ThreadSafe |
||||
public class BackwardSyncLookupService { |
||||
private static final Logger LOG = getLogger(BackwardSyncLookupService.class); |
||||
private static final int MAX_RETRIES = 100; |
||||
public static final int UNUSED = -1; |
||||
|
||||
@GuardedBy("this") |
||||
private final Queue<Hash> hashes = new ArrayDeque<>(); |
||||
|
||||
@GuardedBy("this") |
||||
boolean running = false; |
||||
|
||||
private final ProtocolSchedule protocolSchedule; |
||||
private final EthContext ethContext; |
||||
private final MetricsSystem metricsSystem; |
||||
private List<Block> results = new ArrayList<>(); |
||||
private final ProtocolContext protocolContext; |
||||
|
||||
public BackwardSyncLookupService( |
||||
final ProtocolSchedule protocolSchedule, |
||||
final EthContext ethContext, |
||||
final MetricsSystem metricsSystem, |
||||
final ProtocolContext protocolContext) { |
||||
this.protocolSchedule = protocolSchedule; |
||||
this.ethContext = ethContext; |
||||
this.metricsSystem = metricsSystem; |
||||
this.protocolContext = protocolContext; |
||||
} |
||||
|
||||
public CompletableFuture<List<Block>> lookup(final Hash newBlockhash) { |
||||
synchronized (this) { |
||||
hashes.add(newBlockhash); |
||||
if (running) { |
||||
LOG.info( |
||||
"some other future is already running and will process our hash {} when time comes...", |
||||
newBlockhash.toHexString()); |
||||
return CompletableFuture.completedFuture(Collections.emptyList()); |
||||
} |
||||
running = true; |
||||
} |
||||
return findBlocksWithRetries() |
||||
.handle( |
||||
(blocks, throwable) -> { |
||||
synchronized (this) { |
||||
running = false; |
||||
} |
||||
if (throwable != null) { |
||||
throw new BackwardSyncException(throwable); |
||||
} |
||||
return blocks; |
||||
}); |
||||
} |
||||
|
||||
private CompletableFuture<List<Block>> findBlocksWithRetries() { |
||||
|
||||
CompletableFuture<List<Block>> f = tryToFindBlocks(); |
||||
for (int i = 0; i < MAX_RETRIES; i++) { |
||||
f = |
||||
f.thenApply(CompletableFuture::completedFuture) |
||||
.exceptionally( |
||||
ex -> { |
||||
synchronized (this) { |
||||
if (!results.isEmpty()) { |
||||
List<Block> copy = new ArrayList<>(results); |
||||
results = new ArrayList<>(); |
||||
return CompletableFuture.completedFuture(copy); |
||||
} |
||||
} |
||||
LOG.error( |
||||
"Failed to fetch blocks because {} Current peers: {}. Waiting for few seconds ...", |
||||
ex.getMessage(), |
||||
ethContext.getEthPeers().peerCount()); |
||||
return ethContext |
||||
.getScheduler() |
||||
.scheduleFutureTask(this::tryToFindBlocks, Duration.ofSeconds(5)); |
||||
}) |
||||
.thenCompose(Function.identity()); |
||||
} |
||||
return f.thenApply(this::rememberResults).thenCompose(this::possibleNextHash); |
||||
} |
||||
|
||||
private CompletableFuture<List<Block>> tryToFindBlocks() { |
||||
return CompletableFuture.supplyAsync(this::getNextHash) |
||||
.thenCompose(this::tryToFindBlock) |
||||
.thenApply(this::rememberResult) |
||||
.thenCompose(this::possibleNextHash); |
||||
} |
||||
|
||||
private CompletableFuture<List<Block>> possibleNextHash(final List<Block> blocks) { |
||||
synchronized (this) { |
||||
hashes.poll(); |
||||
if (hashes.isEmpty()) { |
||||
results = new ArrayList<>(); |
||||
running = false; |
||||
return CompletableFuture.completedFuture(blocks); |
||||
} |
||||
} |
||||
return tryToFindBlocks(); |
||||
} |
||||
|
||||
private List<Block> rememberResult(final Block block) { |
||||
this.results.add(block); |
||||
return results; |
||||
} |
||||
|
||||
private List<Block> rememberResults(final List<Block> blocks) { |
||||
this.results.addAll(blocks); |
||||
return results; |
||||
} |
||||
|
||||
private synchronized Hash getNextHash() { |
||||
return hashes.peek(); |
||||
} |
||||
|
||||
private CompletableFuture<Block> tryToFindBlock(final Hash targetHash) { |
||||
|
||||
final RetryingGetBlockFromPeersTask getBlockTask = |
||||
RetryingGetBlockFromPeersTask.create( |
||||
protocolContext, |
||||
protocolSchedule, |
||||
ethContext, |
||||
metricsSystem, |
||||
ethContext.getEthPeers().getMaxPeers(), |
||||
Optional.of(targetHash), |
||||
UNUSED); |
||||
return ethContext |
||||
.getScheduler() |
||||
.scheduleSyncWorkerTask(getBlockTask::run) |
||||
.thenApply(AbstractPeerTask.PeerTaskResult::getResult); |
||||
} |
||||
} |
@ -1,193 +0,0 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.backwardsync; |
||||
|
||||
import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; |
||||
import static org.hyperledger.besu.util.Slf4jLambdaHelper.infoLambda; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.GetHeadersFromPeerByHashTask; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.RetryingGetHeadersEndingAtFromPeerByHashTask; |
||||
|
||||
import java.time.Duration; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
import java.util.function.Function; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class BackwardSyncPhase extends BackwardSyncTask { |
||||
private static final Logger LOG = LoggerFactory.getLogger(BackwardSyncPhase.class); |
||||
|
||||
public BackwardSyncPhase(final BackwardSyncContext context, final BackwardChain backwardChain) { |
||||
super(context, backwardChain); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected CompletableFuture<Void> waitForTTD() { |
||||
if (context.isOnTTD()) { |
||||
return CompletableFuture.completedFuture(null); |
||||
} |
||||
LOG.debug("Did not reach TTD yet, falling asleep..."); |
||||
return context |
||||
.getEthContext() |
||||
.getScheduler() |
||||
.scheduleFutureTask(this::waitForTTD, Duration.ofSeconds(5)); |
||||
} |
||||
|
||||
@Override |
||||
public CompletableFuture<Void> executeStep() { |
||||
return CompletableFuture.supplyAsync(this::waitForTTD) |
||||
.thenCompose(Function.identity()) |
||||
.thenApply(this::earliestUnprocessedHash) |
||||
.thenCompose(this::requestHeaders) |
||||
.thenApply(this::saveHeaders) |
||||
.thenApply(this::possibleMerge) |
||||
.thenCompose(this::possiblyMoreBackwardSteps); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected Hash earliestUnprocessedHash(final Void unused) { |
||||
BlockHeader firstHeader = |
||||
backwardChain |
||||
.getFirstAncestorHeader() |
||||
.orElseThrow( |
||||
() -> |
||||
new BackwardSyncException( |
||||
"No unprocessed hashes during backward sync. that is probably a bug.", |
||||
true)); |
||||
Hash parentHash = firstHeader.getParentHash(); |
||||
debugLambda( |
||||
LOG, |
||||
"First unprocessed hash for current pivot is {} expected on height {}", |
||||
parentHash::toHexString, |
||||
() -> firstHeader.getNumber() - 1); |
||||
return parentHash; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected CompletableFuture<BlockHeader> requestHeader(final Hash hash) { |
||||
debugLambda(LOG, "Requesting header for hash {}", hash::toHexString); |
||||
return GetHeadersFromPeerByHashTask.forSingleHash( |
||||
context.getProtocolSchedule(), |
||||
context.getEthContext(), |
||||
hash, |
||||
context.getProtocolContext().getBlockchain().getChainHead().getHeight(), |
||||
context.getMetricsSystem()) |
||||
.run() |
||||
.thenApply( |
||||
peerResult -> { |
||||
final List<BlockHeader> result = peerResult.getResult(); |
||||
if (result.isEmpty()) { |
||||
throw new BackwardSyncException( |
||||
"Did not receive a header for hash {}" + hash.toHexString(), true); |
||||
} |
||||
BlockHeader blockHeader = result.get(0); |
||||
debugLambda( |
||||
LOG, |
||||
"Got header {} with height {}", |
||||
() -> blockHeader.getHash().toHexString(), |
||||
blockHeader::getNumber); |
||||
return blockHeader; |
||||
}); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected CompletableFuture<List<BlockHeader>> requestHeaders(final Hash hash) { |
||||
debugLambda(LOG, "Requesting header for hash {}", hash::toHexString); |
||||
final RetryingGetHeadersEndingAtFromPeerByHashTask |
||||
retryingGetHeadersEndingAtFromPeerByHashTask = |
||||
RetryingGetHeadersEndingAtFromPeerByHashTask.endingAtHash( |
||||
context.getProtocolSchedule(), |
||||
context.getEthContext(), |
||||
hash, |
||||
context.getProtocolContext().getBlockchain().getChainHead().getHeight(), |
||||
BackwardSyncContext.BATCH_SIZE, |
||||
context.getMetricsSystem()); |
||||
return context |
||||
.getEthContext() |
||||
.getScheduler() |
||||
.scheduleSyncWorkerTask(retryingGetHeadersEndingAtFromPeerByHashTask::run) |
||||
.thenApply( |
||||
blockHeaders -> { |
||||
if (blockHeaders.isEmpty()) { |
||||
throw new BackwardSyncException( |
||||
"Did not receive a header for hash {}" + hash.toHexString(), true); |
||||
} |
||||
debugLambda( |
||||
LOG, |
||||
"Got headers {} -> {}", |
||||
blockHeaders.get(0)::getNumber, |
||||
blockHeaders.get(blockHeaders.size() - 1)::getNumber); |
||||
return blockHeaders; |
||||
}); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected Void saveHeader(final BlockHeader blockHeader) { |
||||
backwardChain.prependAncestorsHeader(blockHeader); |
||||
context.putCurrentChainToHeight(blockHeader.getNumber(), backwardChain); |
||||
return null; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected Void saveHeaders(final List<BlockHeader> blockHeaders) { |
||||
for (BlockHeader blockHeader : blockHeaders) { |
||||
saveHeader(blockHeader); |
||||
} |
||||
infoLambda( |
||||
LOG, |
||||
"Saved headers {} -> {}", |
||||
() -> blockHeaders.get(0).getNumber(), |
||||
() -> blockHeaders.get(blockHeaders.size() - 1).getNumber()); |
||||
return null; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected BlockHeader possibleMerge(final Void unused) { |
||||
Optional<BackwardChain> maybeHistoricalBackwardChain = |
||||
context.findCorrectChainFromPivot( |
||||
backwardChain.getFirstAncestorHeader().orElseThrow().getNumber() - 1); |
||||
maybeHistoricalBackwardChain.ifPresent(backwardChain::prependChain); |
||||
return backwardChain.getFirstAncestorHeader().orElseThrow(); |
||||
} |
||||
|
||||
// if the previous header is not present yet, we need to go deeper
|
||||
@VisibleForTesting |
||||
protected CompletableFuture<Void> possiblyMoreBackwardSteps(final BlockHeader blockHeader) { |
||||
CompletableFuture<Void> completableFuture = new CompletableFuture<>(); |
||||
if (context.getProtocolContext().getBlockchain().contains(blockHeader.getHash())) { |
||||
LOG.info("Backward Phase finished."); |
||||
completableFuture.complete(null); |
||||
return completableFuture; |
||||
} |
||||
if (context.getProtocolContext().getBlockchain().getChainHead().getHeight() |
||||
> blockHeader.getNumber() - 1) { |
||||
LOG.warn( |
||||
"Backward sync is following unknown branch {} ({}) and reached bellow previous head {}({})", |
||||
blockHeader.getNumber(), |
||||
blockHeader.getHash(), |
||||
context.getProtocolContext().getBlockchain().getChainHead().getHeight(), |
||||
context.getProtocolContext().getBlockchain().getChainHead().getHash().toHexString()); |
||||
} |
||||
LOG.debug("Backward sync did not reach a know block, need to go deeper"); |
||||
completableFuture.complete(null); |
||||
return completableFuture.thenCompose(this::executeAsync); |
||||
} |
||||
} |
@ -0,0 +1,112 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.backwardsync; |
||||
|
||||
import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; |
||||
import static org.hyperledger.besu.util.Slf4jLambdaHelper.infoLambda; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.RetryingGetHeadersEndingAtFromPeerByHashTask; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class BackwardSyncStep { |
||||
private static final Logger LOG = LoggerFactory.getLogger(BackwardSyncStep.class); |
||||
private final BackwardSyncContext context; |
||||
private final BackwardChain backwardChain; |
||||
|
||||
public BackwardSyncStep(final BackwardSyncContext context, final BackwardChain backwardChain) { |
||||
this.context = context; |
||||
this.backwardChain = backwardChain; |
||||
} |
||||
|
||||
public CompletableFuture<Void> executeAsync(final BlockHeader firstHeader) { |
||||
return CompletableFuture.supplyAsync(() -> firstHeader) |
||||
.thenApply(this::possibleRestoreOldNodes) |
||||
.thenCompose(this::requestHeaders) |
||||
.thenApply(this::saveHeaders) |
||||
.thenCompose(context::executeNextStep); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected Hash possibleRestoreOldNodes(final BlockHeader firstAncestor) { |
||||
Hash lastHash = firstAncestor.getParentHash(); |
||||
Optional<BlockHeader> iterator = backwardChain.getHeader(lastHash); |
||||
while (iterator.isPresent()) { |
||||
backwardChain.prependAncestorsHeader(iterator.get()); |
||||
lastHash = iterator.get().getParentHash(); |
||||
iterator = backwardChain.getHeader(lastHash); |
||||
} |
||||
return lastHash; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected CompletableFuture<List<BlockHeader>> requestHeaders(final Hash hash) { |
||||
debugLambda(LOG, "Requesting header for hash {}", hash::toHexString); |
||||
final RetryingGetHeadersEndingAtFromPeerByHashTask |
||||
retryingGetHeadersEndingAtFromPeerByHashTask = |
||||
RetryingGetHeadersEndingAtFromPeerByHashTask.endingAtHash( |
||||
context.getProtocolSchedule(), |
||||
context.getEthContext(), |
||||
hash, |
||||
context.getProtocolContext().getBlockchain().getChainHead().getHeight(), |
||||
context.getBatchSize(), |
||||
context.getMetricsSystem()); |
||||
return context |
||||
.getEthContext() |
||||
.getScheduler() |
||||
.scheduleSyncWorkerTask(retryingGetHeadersEndingAtFromPeerByHashTask::run) |
||||
.thenApply( |
||||
blockHeaders -> { |
||||
if (blockHeaders.isEmpty()) { |
||||
throw new BackwardSyncException( |
||||
"Did not receive a header for hash {}" + hash.toHexString(), true); |
||||
} |
||||
debugLambda( |
||||
LOG, |
||||
"Got headers {} -> {}", |
||||
blockHeaders.get(0)::getNumber, |
||||
blockHeaders.get(blockHeaders.size() - 1)::getNumber); |
||||
return blockHeaders; |
||||
}); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected Void saveHeader(final BlockHeader blockHeader) { |
||||
backwardChain.prependAncestorsHeader(blockHeader); |
||||
return null; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
protected Void saveHeaders(final List<BlockHeader> blockHeaders) { |
||||
for (BlockHeader blockHeader : blockHeaders) { |
||||
saveHeader(blockHeader); |
||||
} |
||||
infoLambda( |
||||
LOG, |
||||
"Saved headers {} -> {} (head: {})", |
||||
() -> blockHeaders.get(0).getNumber(), |
||||
() -> blockHeaders.get(blockHeaders.size() - 1).getNumber(), |
||||
() -> context.getProtocolContext().getBlockchain().getChainHead().getHeight()); |
||||
return null; |
||||
} |
||||
} |
@ -1,58 +0,0 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.backwardsync; |
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger; |
||||
|
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
import org.slf4j.Logger; |
||||
|
||||
public abstract class BackwardSyncTask { |
||||
protected BackwardSyncContext context; |
||||
protected BackwardChain backwardChain; |
||||
private static final Logger LOG = getLogger(BackwardSyncTask.class); |
||||
|
||||
protected BackwardSyncTask(final BackwardSyncContext context, final BackwardChain backwardChain) { |
||||
this.context = context; |
||||
this.backwardChain = backwardChain; |
||||
} |
||||
|
||||
CompletableFuture<Void> executeAsync(final Void unused) { |
||||
Optional<BackwardChain> currentChain = context.getCurrentChain(); |
||||
if (currentChain.isPresent()) { |
||||
if (!backwardChain.equals(currentChain.get())) { |
||||
LOG.debug( |
||||
"The pivot changed, we should stop current flow, some new flow is waiting to take over..."); |
||||
return CompletableFuture.completedFuture(null); |
||||
} |
||||
if (backwardChain.getFirstAncestorHeader().isEmpty()) { |
||||
LOG.info("The Backwards sync is already finished..."); |
||||
return CompletableFuture.completedFuture(null); |
||||
} |
||||
return executeStep(); |
||||
|
||||
} else { |
||||
CompletableFuture<Void> result = new CompletableFuture<>(); |
||||
result.completeExceptionally( |
||||
new BackwardSyncException( |
||||
"No pivot... that is weird and should not have happened. This method should have been called after the pivot was set...")); |
||||
return result; |
||||
} |
||||
} |
||||
|
||||
abstract CompletableFuture<Void> executeStep(); |
||||
} |
@ -0,0 +1,34 @@ |
||||
/* |
||||
* |
||||
* * Copyright Hyperledger Besu Contributors. |
||||
* * |
||||
* * 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.backwardsync; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
|
||||
public class HashConvertor implements ValueConvertor<Hash> { |
||||
@Override |
||||
public Hash fromBytes(final byte[] bytes) { |
||||
return Hash.wrap(Bytes32.wrap(bytes)); |
||||
} |
||||
|
||||
@Override |
||||
public byte[] toBytes(final Hash value) { |
||||
return value.toArrayUnsafe(); |
||||
} |
||||
} |
@ -0,0 +1,74 @@ |
||||
/* |
||||
* |
||||
* * Copyright Hyperledger Besu Contributors. |
||||
* * |
||||
* * 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.backwardsync; |
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerTask; |
||||
import org.hyperledger.besu.ethereum.eth.manager.task.RetryingGetBlockFromPeersTask; |
||||
|
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
import org.slf4j.Logger; |
||||
|
||||
public class SyncStepStep { |
||||
private static final Logger LOG = getLogger(SyncStepStep.class); |
||||
|
||||
public static final int UNUSED = -1; |
||||
private final BackwardSyncContext context; |
||||
private final BackwardChain backwardChain; |
||||
|
||||
public SyncStepStep(final BackwardSyncContext context, final BackwardChain backwardChain) { |
||||
this.context = context; |
||||
this.backwardChain = backwardChain; |
||||
} |
||||
|
||||
public CompletableFuture<Void> executeAsync(final Hash hash) { |
||||
return CompletableFuture.supplyAsync(() -> hash) |
||||
.thenCompose(this::requestBlock) |
||||
.thenApply(this::saveBlock) |
||||
.thenCompose(context::executeNextStep); |
||||
} |
||||
|
||||
private CompletableFuture<Block> requestBlock(final Hash targetHash) { |
||||
final RetryingGetBlockFromPeersTask getBlockTask = |
||||
RetryingGetBlockFromPeersTask.create( |
||||
context.getProtocolContext(), |
||||
context.getProtocolSchedule(), |
||||
context.getEthContext(), |
||||
context.getMetricsSystem(), |
||||
context.getEthContext().getEthPeers().getMaxPeers(), |
||||
Optional.of(targetHash), |
||||
UNUSED); |
||||
return context |
||||
.getEthContext() |
||||
.getScheduler() |
||||
.scheduleSyncWorkerTask(getBlockTask::run) |
||||
.thenApply(AbstractPeerTask.PeerTaskResult::getResult); |
||||
} |
||||
|
||||
private Void saveBlock(final Block block) { |
||||
LOG.debug( |
||||
"Appending block {}({})", block.getHeader().getNumber(), block.getHash().toHexString()); |
||||
backwardChain.appendTrustedBlock(block); |
||||
return null; |
||||
} |
||||
} |
@ -1,132 +0,0 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.backwardsync; |
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; |
||||
import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; |
||||
import static org.mockito.ArgumentMatchers.anyLong; |
||||
import static org.mockito.Mockito.spy; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.config.StubGenesisConfigOptions; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.BlockDataGenerator; |
||||
import org.hyperledger.besu.ethereum.core.TransactionReceipt; |
||||
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.RespondingEthPeer; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Modifier; |
||||
import java.util.List; |
||||
import java.util.concurrent.CompletableFuture; |
||||
import javax.annotation.Nonnull; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Answers; |
||||
import org.mockito.Mock; |
||||
import org.mockito.Spy; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class BackwardSyncLookupServiceTest { |
||||
public static final int REMOTE_HEIGHT = 50; |
||||
private static final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); |
||||
|
||||
@Spy |
||||
private final ProtocolSchedule protocolSchedule = |
||||
MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions()); |
||||
|
||||
@Spy private final ProtocolSpec mockProtocolSpec = protocolSchedule.getByBlockNumber(0L); |
||||
private MutableBlockchain remoteBlockchain; |
||||
private RespondingEthPeer peer; |
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) |
||||
private MetricsSystem metricsSystem; |
||||
|
||||
@Mock private ProtocolContext protocolContext; |
||||
|
||||
private BackwardSyncLookupService backwardSyncLookupService; |
||||
|
||||
@Before |
||||
public void setup() throws NoSuchFieldException, IllegalAccessException { |
||||
when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(mockProtocolSpec); |
||||
Block genesisBlock = blockDataGenerator.genesisBlock(); |
||||
remoteBlockchain = createInMemoryBlockchain(genesisBlock); |
||||
final Field max_retries = BackwardSyncLookupService.class.getDeclaredField("MAX_RETRIES"); |
||||
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers"); |
||||
modifiersField.setAccessible(true); |
||||
modifiersField.setInt(max_retries, max_retries.getModifiers() & ~Modifier.FINAL); |
||||
|
||||
max_retries.setAccessible(true); |
||||
max_retries.set(null, 1); |
||||
|
||||
for (int i = 1; i <= REMOTE_HEIGHT; i++) { |
||||
final BlockDataGenerator.BlockOptions options = |
||||
new BlockDataGenerator.BlockOptions() |
||||
.setBlockNumber(i) |
||||
.setParentHash(remoteBlockchain.getBlockHashByNumber(i - 1).orElseThrow()); |
||||
final Block block = blockDataGenerator.block(options); |
||||
final List<TransactionReceipt> receipts = blockDataGenerator.receipts(block); |
||||
|
||||
remoteBlockchain.appendBlock(block, receipts); |
||||
} |
||||
EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(); |
||||
|
||||
peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager); |
||||
EthContext ethContext = ethProtocolManager.ethContext(); |
||||
|
||||
backwardSyncLookupService = |
||||
spy( |
||||
new BackwardSyncLookupService( |
||||
protocolSchedule, ethContext, metricsSystem, protocolContext)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFindABlockWhenResponding() throws Exception { |
||||
final Hash hash = getBlockByNumber(23).getHash(); |
||||
|
||||
final CompletableFuture<List<Block>> future = backwardSyncLookupService.lookup(hash); |
||||
|
||||
respondUntilFutureIsDone(future); |
||||
|
||||
final List<Block> blocks = future.get(); |
||||
assertThat(blocks.get(0)).isEqualTo(getBlockByNumber(23)); |
||||
} |
||||
|
||||
private void respondUntilFutureIsDone(final CompletableFuture<?> future) { |
||||
final RespondingEthPeer.Responder responder = |
||||
RespondingEthPeer.blockchainResponder(remoteBlockchain); |
||||
|
||||
peer.respondWhileOtherThreadsWork(responder, () -> !future.isDone()); |
||||
} |
||||
|
||||
@Nonnull |
||||
private Block getBlockByNumber(final int number) { |
||||
return remoteBlockchain.getBlockByNumber(number).orElseThrow(); |
||||
} |
||||
} |
@ -1,111 +0,0 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.backwardsync; |
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; |
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.Block; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; |
||||
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
import javax.annotation.Nonnull; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class BackwardSyncTaskTest { |
||||
|
||||
public static final int HEIGHT = 20_000; |
||||
|
||||
@Mock private BackwardSyncContext context; |
||||
private List<Block> blocks; |
||||
|
||||
GenericKeyValueStorageFacade<Hash, BlockHeader> headersStorage; |
||||
GenericKeyValueStorageFacade<Hash, Block> blocksStorage; |
||||
|
||||
@Before |
||||
public void initContextAndChain() { |
||||
blocks = ChainForTestCreator.prepareChain(2, HEIGHT); |
||||
headersStorage = |
||||
new GenericKeyValueStorageFacade<>( |
||||
Hash::toArrayUnsafe, |
||||
new BlocksHeadersConvertor(new MainnetBlockHeaderFunctions()), |
||||
new InMemoryKeyValueStorage()); |
||||
blocksStorage = |
||||
new GenericKeyValueStorageFacade<>( |
||||
Hash::toArrayUnsafe, |
||||
new BlocksConvertor(new MainnetBlockHeaderFunctions()), |
||||
new InMemoryKeyValueStorage()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFailWhenPivotNotSetInContext() { |
||||
when(context.getCurrentChain()).thenReturn(Optional.empty()); |
||||
BackwardSyncTask step = createBackwardSyncTask(); |
||||
CompletableFuture<Void> completableFuture = step.executeAsync(null); |
||||
assertThatThrownBy(completableFuture::get) |
||||
.getCause() |
||||
.isInstanceOf(BackwardSyncException.class) |
||||
.hasMessageContaining("No pivot"); |
||||
} |
||||
|
||||
@Nonnull |
||||
private BackwardSyncTask createBackwardSyncTask() { |
||||
final BackwardChain backwardChain = |
||||
new BackwardChain(headersStorage, blocksStorage, blocks.get(1)); |
||||
return createBackwardSyncTask(backwardChain); |
||||
} |
||||
|
||||
@Nonnull |
||||
private BackwardSyncTask createBackwardSyncTask(final BackwardChain backwardChain) { |
||||
return new BackwardSyncTask(context, backwardChain) { |
||||
@Override |
||||
CompletableFuture<Void> executeStep() { |
||||
return CompletableFuture.completedFuture(null); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFinishImmediatelyFailWhenPivotIsDifferent() { |
||||
final BackwardChain backwardChain = |
||||
new BackwardChain(headersStorage, blocksStorage, blocks.get(0)); |
||||
when(context.getCurrentChain()).thenReturn(Optional.of(backwardChain)); |
||||
BackwardSyncTask step = createBackwardSyncTask(); |
||||
CompletableFuture<Void> completableFuture = step.executeAsync(null); |
||||
assertThat(completableFuture.isDone()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldExecuteWhenPivotIsCorrect() { |
||||
final BackwardChain backwardChain = |
||||
new BackwardChain(headersStorage, blocksStorage, blocks.get(1)); |
||||
BackwardSyncTask step = createBackwardSyncTask(); |
||||
when(context.getCurrentChain()).thenReturn(Optional.of(backwardChain)); |
||||
CompletableFuture<Void> completableFuture = step.executeAsync(null); |
||||
assertThat(completableFuture.isDone()).isTrue(); |
||||
} |
||||
} |
Loading…
Reference in new issue