diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 9d594406172..648af5738a4 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -425,6 +425,8 @@ public BesuController build() { ethProtocolManager, pivotBlockSelector); + protocolContext.setSynchronizer(Optional.of(synchronizer)); + final MiningCoordinator miningCoordinator = createMiningCoordinator( protocolSchedule, @@ -493,7 +495,6 @@ protected Synchronizer createSynchronizer( clock, metricsSystem, getFullSyncTerminationCondition(protocolContext.getBlockchain()), - ethProtocolManager, pivotBlockSelector); return toUse; diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 6eac33e3278..ccb7958623f 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -132,6 +132,7 @@ public void setUp() { .thenReturn(ValidationResult.valid()); when(mockTransactionValidator.validateForSender(any(), any(), any())) .thenReturn(ValidationResult.valid()); + when(mockWorldState.copy()).thenReturn(mockWorldState); when(mockWorldStateArchive.getMutable(any(), any(), anyBoolean())) .thenReturn(Optional.of(mockWorldState)); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 130955c7fb3..360855a86fa 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -34,6 +34,7 @@ public enum RpcMethod { CLIQUE_GET_SIGNER_METRICS("clique_getSignerMetrics"), DEBUG_ACCOUNT_AT("debug_accountAt"), DEBUG_METRICS("debug_metrics"), + DEBUG_RESYNC_WORLDSTATE("debug_resyncWorldState"), DEBUG_SET_HEAD("debug_setHead"), DEBUG_REPLAY_BLOCK("debug_replayBlock"), DEBUG_STORAGE_RANGE_AT("debug_storageRangeAt"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugResyncWorldstate.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugResyncWorldstate.java new file mode 100644 index 00000000000..578fdbf0708 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugResyncWorldstate.java @@ -0,0 +1,53 @@ +/* + * 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.api.jsonrpc.internal.methods; + +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.Synchronizer; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; + +public class DebugResyncWorldstate implements JsonRpcMethod { + private final Synchronizer synchronizer; + private final ProtocolSchedule protocolSchedule; + private final Blockchain blockchain; + + public DebugResyncWorldstate( + final ProtocolSchedule protocolSchedule, + final Blockchain blockchain, + final Synchronizer synchronizer) { + this.synchronizer = synchronizer; + this.protocolSchedule = protocolSchedule; + this.blockchain = blockchain; + } + + @Override + public String getName() { + return RpcMethod.DEBUG_RESYNC_WORLDSTATE.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext request) { + protocolSchedule + .getByBlockNumber(blockchain.getChainHeadBlockNumber()) + .getBadBlocksManager() + .reset(); + return new JsonRpcSuccessResponse( + request.getRequest().getId(), synchronizer.resyncWorldState()); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java index 6f47b451a5e..1fe84f4f32f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java @@ -145,15 +145,22 @@ private Optional performActionWithBlock( if (previous == null) { return Optional.empty(); } - try (final MutableWorldState mutableWorldState = + try (final var worldState = worldStateArchive - .getMutable(previous.getStateRoot(), previous.getHash(), false) + .getMutable(previous.getStateRoot(), previous.getBlockHash(), false) + .map( + ws -> { + if (!ws.isPersistable()) { + return ws.copy(); + } + return ws; + }) .orElseThrow( () -> new IllegalArgumentException( "Missing worldstate for stateroot " + previous.getStateRoot().toShortHexString()))) { - return action.perform(body, header, blockchain, mutableWorldState, transactionProcessor); + return action.perform(body, header, blockchain, worldState, transactionProcessor); } catch (Exception ex) { return Optional.empty(); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java index c4a1a474ebd..33cc16be0cf 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugBatchSendRawTransaction; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugGetBadBlocks; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugMetrics; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugResyncWorldstate; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugSetHead; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugStandardTraceBadBlockToFile; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugStandardTraceBlockToFile; @@ -36,6 +37,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; @@ -53,6 +55,7 @@ public class DebugJsonRpcMethods extends ApiGroupJsonRpcMethods { private final ProtocolSchedule protocolSchedule; private final ObservableMetricsSystem metricsSystem; private final TransactionPool transactionPool; + private final Synchronizer synchronizer; private final Path dataDir; DebugJsonRpcMethods( @@ -61,12 +64,14 @@ public class DebugJsonRpcMethods extends ApiGroupJsonRpcMethods { final ProtocolSchedule protocolSchedule, final ObservableMetricsSystem metricsSystem, final TransactionPool transactionPool, + final Synchronizer synchronizer, final Path dataDir) { this.blockchainQueries = blockchainQueries; this.protocolContext = protocolContext; this.protocolSchedule = protocolSchedule; this.metricsSystem = metricsSystem; this.transactionPool = transactionPool; + this.synchronizer = synchronizer; this.dataDir = dataDir; } @@ -88,6 +93,7 @@ protected Map create() { new DebugAccountRange(blockchainQueries), new DebugStorageRangeAt(blockchainQueries, blockReplay), new DebugMetrics(metricsSystem), + new DebugResyncWorldstate(protocolSchedule, protocolContext.getBlockchain(), synchronizer), new DebugTraceBlock( () -> new BlockTracer(blockReplay), ScheduleBasedBlockHeaderFunctions.create(protocolSchedule), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java index 3e8c25fac99..2f250fea0ff 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java @@ -100,6 +100,7 @@ public Map methods( protocolSchedule, metricsSystem, transactionPool, + synchronizer, dataDir), new EeaJsonRpcMethods( blockchainQueries, protocolSchedule, transactionPool, privacyParameters), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java index 833dfefc35e..881d8914a72 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java @@ -118,6 +118,7 @@ public void setUp() throws Exception { when(protocolSpec.getMiningBeneficiaryCalculator()).thenReturn(BlockHeader::getCoinbase); when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); when(protocolSpec.getBadBlocksManager()).thenReturn(new BadBlockManager()); + when(mutableWorldState.copy()).thenReturn(mutableWorldState); } @Test diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java index 7af5561c804..df2206ba931 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java @@ -167,7 +167,16 @@ public BlockProcessingResult validateAndProcessBlock( return new BlockProcessingResult( Optional.of(new BlockProcessingOutputs(worldState, receipts))); } - } catch (StorageException | MerkleTrieException ex) { + } catch (MerkleTrieException ex) { + context + .getSynchronizer() + .ifPresentOrElse( + synchronizer -> synchronizer.healWorldState(ex.getMaybeAddress(), ex.getLocation()), + () -> + handleAndLogImportFailure( + block, new BlockProcessingResult(Optional.empty(), ex))); + return new BlockProcessingResult(Optional.empty(), ex); + } catch (StorageException ex) { var retval = new BlockProcessingResult(Optional.empty(), ex); handleAndLogImportFailure(block, retval); return retval; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java index 0b115937053..1a8a80a8e25 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; @@ -30,6 +31,8 @@ public class ProtocolContext { private final WorldStateArchive worldStateArchive; private final ConsensusContext consensusContext; + private Optional synchronizer; + public ProtocolContext( final MutableBlockchain blockchain, final WorldStateArchive worldStateArchive, @@ -37,6 +40,7 @@ public ProtocolContext( this.blockchain = blockchain; this.worldStateArchive = worldStateArchive; this.consensusContext = consensusContext; + this.synchronizer = Optional.empty(); } public static ProtocolContext init( @@ -50,6 +54,14 @@ public static ProtocolContext init( consensusContextFactory.create(blockchain, worldStateArchive, protocolSchedule)); } + public Optional getSynchronizer() { + return synchronizer; + } + + public void setSynchronizer(final Optional synchronizer) { + this.synchronizer = synchronizer; + } + public MutableBlockchain getBlockchain() { return blockchain; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java index 7cf935c28fd..b8133714a0a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java @@ -180,7 +180,7 @@ public void setBalance(final Wei value) { @Override public Bytes getCode() { if (code == null) { - code = context.getCode(address).orElse(Bytes.EMPTY); + code = context.getCode(address, codeHash).orElse(Bytes.EMPTY); } return code; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldState.java index 64ee2c42fbe..2c2e07a34a8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldState.java @@ -20,9 +20,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import org.apache.tuweni.bytes.Bytes; @@ -84,13 +86,19 @@ protected Hash calculateRootHash(final BonsaiWorldStateUpdater worldStateUpdater final Bytes accountKey = accountUpdate.getKey(); final BonsaiValue bonsaiValue = accountUpdate.getValue(); final BonsaiAccount updatedAccount = bonsaiValue.getUpdated(); - if (updatedAccount == null) { - final Hash addressHash = Hash.hash(accountKey); - accountTrie.remove(addressHash); - } else { - final Hash addressHash = updatedAccount.getAddressHash(); - final Bytes accountValue = updatedAccount.serializeAccount(); - accountTrie.put(addressHash, accountValue); + try { + if (updatedAccount == null) { + final Hash addressHash = Hash.hash(accountKey); + accountTrie.remove(addressHash); + } else { + final Hash addressHash = updatedAccount.getAddressHash(); + final Bytes accountValue = updatedAccount.serializeAccount(); + accountTrie.put(addressHash, accountValue); + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(Address.wrap(accountKey)), e.getHash(), e.getLocation()); } } @@ -129,10 +137,19 @@ private void updateAccountStorage( storageAccountUpdate.getValue().entrySet()) { final Hash keyHash = storageUpdate.getKey(); final UInt256 updatedStorage = storageUpdate.getValue().getUpdated(); - if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) { - storageTrie.remove(keyHash); - } else { - storageTrie.put(keyHash, BonsaiWorldView.encodeTrieValue(updatedStorage)); + try { + if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) { + storageTrie.remove(keyHash); + } else { + storageTrie.put(keyHash, BonsaiWorldView.encodeTrieValue(updatedStorage)); + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), + Optional.of(Address.wrap(updatedAddress)), + e.getHash(), + e.getLocation()); } } @@ -150,7 +167,12 @@ public void persist(final BlockHeader blockHeader) { final Hash newWorldStateRootHash = rootHash(localUpdater); archive .getTrieLogManager() - .saveTrieLog(archive, localUpdater, newWorldStateRootHash, blockHeader, this); + .saveTrieLog( + archive, + localUpdater, + newWorldStateRootHash, + blockHeader, + (BonsaiPersistedWorldState) this.copy()); worldStateRootHash = newWorldStateRootHash; worldStateBlockHash = blockHeader.getBlockHash(); isPersisted = true; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldStateKeyValueStorage.java index b4b5dcc44b6..f539b899da2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldStateKeyValueStorage.java @@ -14,13 +14,10 @@ */ package org.hyperledger.besu.ethereum.bonsai; -import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; -import java.util.Optional; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,15 +32,8 @@ public BonsaiInMemoryWorldStateKeyValueStorage( final KeyValueStorage codeStorage, final KeyValueStorage storageStorage, final KeyValueStorage trieBranchStorage, - final KeyValueStorage trieLogStorage, - final Optional fallbackNodeFinder) { - super( - accountStorage, - codeStorage, - storageStorage, - trieBranchStorage, - trieLogStorage, - fallbackNodeFinder); + final KeyValueStorage trieLogStorage) { + super(accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java index 222aef38ee6..e0839400378 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.SnapshotMutableWorldState; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.worldstate.WorldState; @@ -75,9 +76,29 @@ public Optional getNextWorldView() { } public void setNextWorldView(final Optional nextWorldView) { + maybeUnSubscribe(); this.nextWorldView = nextWorldView; } + private void maybeUnSubscribe() { + nextWorldView + .filter(WorldState.class::isInstance) + .map(WorldState.class::cast) + .ifPresent( + ws -> { + try { + ws.close(); + } catch (final Exception e) { + // no-op + } + }); + } + + @Override + public void close() throws Exception { + maybeUnSubscribe(); + } + public TrieLogLayer getTrieLog() { return trieLog; } @@ -87,7 +108,7 @@ public long getHeight() { } @Override - public Optional getCode(final Address address) { + public Optional getCode(final Address address, final Hash codeHash) { BonsaiLayeredWorldState currentLayer = this; while (currentLayer != null) { final Optional maybeCode = currentLayer.trieLog.getCode(address); @@ -104,7 +125,7 @@ public Optional getCode(final Address address) { } else if (currentLayer.getNextWorldView().get() instanceof BonsaiLayeredWorldState) { currentLayer = (BonsaiLayeredWorldState) currentLayer.getNextWorldView().get(); } else { - return currentLayer.getNextWorldView().get().getCode(address); + return currentLayer.getNextWorldView().get().getCode(address, codeHash); } } return Optional.empty(); @@ -271,6 +292,8 @@ public MutableWorldState copy() { new StorageException( "Unable to copy Layered Worldstate for " + blockHash().toHexString()))) { return new BonsaiInMemoryWorldState(archive, snapshot.getWorldStateStorage()); + } catch (MerkleTrieException ex) { + throw ex; // need to throw to trigger the heal } catch (Exception ex) { throw new RuntimeException(ex); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java index e093623b7c1..b5ed61e5a29 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.evm.account.Account; @@ -35,6 +36,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -91,15 +93,14 @@ public MutableWorldState copy() { worldStateStorage.codeStorage, worldStateStorage.storageStorage, worldStateStorage.trieBranchStorage, - worldStateStorage.trieLogStorage, - getWorldStateStorage().getMaybeFallbackNodeFinder()); + worldStateStorage.trieLogStorage); return new BonsaiInMemoryWorldState(archive, bonsaiInMemoryWorldStateKeyValueStorage); } @Override - public Optional getCode(@Nonnull final Address address) { - return worldStateStorage.getCode(null, Hash.hash(address)); + public Optional getCode(@Nonnull final Address address, final Hash codeHash) { + return worldStateStorage.getCode(codeHash, Hash.hash(address)); } public void setArchiveStateUnSafe(final BlockHeader blockHeader) { @@ -147,7 +148,7 @@ protected Hash calculateRootHash( return Hash.wrap(rootHash); } - private static void addTheAccounts( + private void addTheAccounts( final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater, final BonsaiWorldStateUpdater worldStateUpdater, final StoredMerklePatriciaTrie accountTrie) { @@ -156,20 +157,26 @@ private static void addTheAccounts( final Bytes accountKey = accountUpdate.getKey(); final BonsaiValue bonsaiValue = accountUpdate.getValue(); final BonsaiAccount updatedAccount = bonsaiValue.getUpdated(); - if (updatedAccount == null) { - final Hash addressHash = Hash.hash(accountKey); - accountTrie.remove(addressHash); - stateUpdater.removeAccountInfoState(addressHash); - } else { - final Hash addressHash = updatedAccount.getAddressHash(); - final Bytes accountValue = updatedAccount.serializeAccount(); - stateUpdater.putAccountInfoState(Hash.hash(accountKey), accountValue); - accountTrie.put(addressHash, accountValue); + try { + if (updatedAccount == null) { + final Hash addressHash = Hash.hash(accountKey); + accountTrie.remove(addressHash); + stateUpdater.removeAccountInfoState(addressHash); + } else { + final Hash addressHash = updatedAccount.getAddressHash(); + final Bytes accountValue = updatedAccount.serializeAccount(); + stateUpdater.putAccountInfoState(Hash.hash(accountKey), accountValue); + accountTrie.put(addressHash, accountValue); + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(Address.wrap(accountKey)), e.getHash(), e.getLocation()); } } } - private static void updateCode( + private void updateCode( final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater, final BonsaiWorldStateUpdater worldStateUpdater) { for (final Map.Entry> codeUpdate : @@ -214,12 +221,21 @@ private void updateAccountStorageState( storageAccountUpdate.getValue().entrySet()) { final Hash keyHash = storageUpdate.getKey(); final UInt256 updatedStorage = storageUpdate.getValue().getUpdated(); - if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) { - stateUpdater.removeStorageValueBySlotHash(updatedAddressHash, keyHash); - storageTrie.remove(keyHash); - } else { - stateUpdater.putStorageValueBySlotHash(updatedAddressHash, keyHash, updatedStorage); - storageTrie.put(keyHash, BonsaiWorldView.encodeTrieValue(updatedStorage)); + try { + if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) { + stateUpdater.removeStorageValueBySlotHash(updatedAddressHash, keyHash); + storageTrie.remove(keyHash); + } else { + stateUpdater.putStorageValueBySlotHash(updatedAddressHash, keyHash, updatedStorage); + storageTrie.put(keyHash, BonsaiWorldView.encodeTrieValue(updatedStorage)); + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), + Optional.of(Address.wrap(updatedAddress)), + e.getHash(), + e.getLocation()); } } @@ -258,18 +274,24 @@ private void clearStorage( oldAccount.getStorageRoot(), Function.identity(), Function.identity()); - Map entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256); - while (!entriesToDelete.isEmpty()) { - entriesToDelete - .keySet() - .forEach( - k -> stateUpdater.removeStorageValueBySlotHash(Hash.hash(address), Hash.wrap(k))); - entriesToDelete.keySet().forEach(storageTrie::remove); - if (entriesToDelete.size() == 256) { - entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256); - } else { - break; + try { + Map entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256); + while (!entriesToDelete.isEmpty()) { + entriesToDelete + .keySet() + .forEach( + k -> stateUpdater.removeStorageValueBySlotHash(Hash.hash(address), Hash.wrap(k))); + entriesToDelete.keySet().forEach(storageTrie::remove); + if (entriesToDelete.size() == 256) { + entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256); + } else { + break; + } } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(Address.wrap(address)), e.getHash(), e.getLocation()); } } } @@ -427,6 +449,15 @@ public Optional getStorageValueBySlotHash(final Address address, final .map(UInt256::fromBytes); } + public Optional getStorageValueBySlotHash( + final Supplier> storageRootSupplier, + final Address address, + final Hash slotHash) { + return worldStateStorage + .getStorageValueBySlotHash(storageRootSupplier, Hash.hash(address), slotHash) + .map(UInt256::fromBytes); + } + @Override public UInt256 getPriorStorageValue(final Address address, final UInt256 storageKey) { return getStorageValue(address, storageKey); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotWorldStateKeyValueStorage.java index c060c5bda39..8eaf5b277c8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotWorldStateKeyValueStorage.java @@ -19,8 +19,6 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; -import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.plugin.services.exception.StorageException; @@ -50,25 +48,6 @@ public class BonsaiSnapshotWorldStateKeyValueStorage extends BonsaiWorldStateKey private final AtomicBoolean shouldClose = new AtomicBoolean(false); private final AtomicBoolean isClosed = new AtomicBoolean(false); - public BonsaiSnapshotWorldStateKeyValueStorage(final StorageProvider snappableStorageProvider) { - this( - snappableStorageProvider - .getSnappableStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE) - .takeSnapshot(), - snappableStorageProvider - .getSnappableStorageBySegmentIdentifier(KeyValueSegmentIdentifier.CODE_STORAGE) - .takeSnapshot(), - snappableStorageProvider - .getSnappableStorageBySegmentIdentifier( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE) - .takeSnapshot(), - snappableStorageProvider - .getSnappableStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE) - .takeSnapshot(), - snappableStorageProvider.getStorageBySegmentIdentifier( - KeyValueSegmentIdentifier.TRIE_LOG_STORAGE)); - } - public BonsaiSnapshotWorldStateKeyValueStorage( final SnappedKeyValueStorage accountStorage, final SnappedKeyValueStorage codeStorage, @@ -100,6 +79,12 @@ public void clearFlatDatabase() { throw new StorageException("Snapshot storage does not implement clear"); } + @Override + public void clearTrieLog() { + // snapshot storage does not implement clear + throw new StorageException("Snapshot storage does not implement clear"); + } + @Override public synchronized long subscribe(final BonsaiStorageSubscriber sub) { if (isClosed.get()) { @@ -143,6 +128,16 @@ public void onClearFlatDatabaseStorage() { } } + @Override + public void onClearTrieLog() { + // when the parent storage clears, close regardless of subscribers + try { + doClose(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override public synchronized void close() throws Exception { // when the parent storage clears, close @@ -180,10 +175,10 @@ private void doClose() throws Exception { public static class SnapshotUpdater implements BonsaiWorldStateKeyValueStorage.BonsaiUpdater { - private final SnappedKeyValueStorage accountStorage; - private final SnappedKeyValueStorage codeStorage; - private final SnappedKeyValueStorage storageStorage; - private final SnappedKeyValueStorage trieBranchStorage; + private final KeyValueStorageTransaction accountStorageTransaction; + private final KeyValueStorageTransaction codeStorageTransaction; + private final KeyValueStorageTransaction storageStorageTransaction; + private final KeyValueStorageTransaction trieBranchStorageTransaction; private final KeyValueStorageTransaction trieLogStorageTransaction; public SnapshotUpdater( @@ -192,16 +187,16 @@ public SnapshotUpdater( final SnappedKeyValueStorage storageStorage, final SnappedKeyValueStorage trieBranchStorage, final KeyValueStorage trieLogStorage) { - this.accountStorage = accountStorage; - this.codeStorage = codeStorage; - this.storageStorage = storageStorage; - this.trieBranchStorage = trieBranchStorage; + this.accountStorageTransaction = accountStorage.getSnapshotTransaction(); + this.codeStorageTransaction = codeStorage.getSnapshotTransaction(); + this.storageStorageTransaction = storageStorage.getSnapshotTransaction(); + this.trieBranchStorageTransaction = trieBranchStorage.getSnapshotTransaction(); this.trieLogStorageTransaction = trieLogStorage.startTransaction(); } @Override public BonsaiUpdater removeCode(final Hash accountHash) { - codeStorage.getSnapshotTransaction().remove(accountHash.toArrayUnsafe()); + codeStorageTransaction.remove(accountHash.toArrayUnsafe()); return this; } @@ -212,13 +207,13 @@ public WorldStateStorage.Updater putCode( // Don't save empty values return this; } - codeStorage.getSnapshotTransaction().put(accountHash.toArrayUnsafe(), code.toArrayUnsafe()); + codeStorageTransaction.put(accountHash.toArrayUnsafe(), code.toArrayUnsafe()); return this; } @Override public BonsaiUpdater removeAccountInfoState(final Hash accountHash) { - accountStorage.getSnapshotTransaction().remove(accountHash.toArrayUnsafe()); + accountStorageTransaction.remove(accountHash.toArrayUnsafe()); return this; } @@ -228,31 +223,26 @@ public BonsaiUpdater putAccountInfoState(final Hash accountHash, final Bytes acc // Don't save empty values return this; } - accountStorage - .getSnapshotTransaction() - .put(accountHash.toArrayUnsafe(), accountValue.toArrayUnsafe()); + accountStorageTransaction.put(accountHash.toArrayUnsafe(), accountValue.toArrayUnsafe()); return this; } @Override public BonsaiUpdater putStorageValueBySlotHash( final Hash accountHash, final Hash slotHash, final Bytes storage) { - storageStorage - .getSnapshotTransaction() - .put(Bytes.concatenate(accountHash, slotHash).toArrayUnsafe(), storage.toArrayUnsafe()); + storageStorageTransaction.put( + Bytes.concatenate(accountHash, slotHash).toArrayUnsafe(), storage.toArrayUnsafe()); return this; } @Override public void removeStorageValueBySlotHash(final Hash accountHash, final Hash slotHash) { - storageStorage - .getSnapshotTransaction() - .remove(Bytes.concatenate(accountHash, slotHash).toArrayUnsafe()); + storageStorageTransaction.remove(Bytes.concatenate(accountHash, slotHash).toArrayUnsafe()); } @Override public KeyValueStorageTransaction getTrieBranchStorageTransaction() { - return trieBranchStorage.getSnapshotTransaction(); + return trieBranchStorageTransaction; } @Override @@ -263,13 +253,9 @@ public KeyValueStorageTransaction getTrieLogStorageTransaction() { @Override public WorldStateStorage.Updater saveWorldState( final Bytes blockHash, final Bytes32 nodeHash, final Bytes node) { - trieBranchStorage - .getSnapshotTransaction() - .put(Bytes.EMPTY.toArrayUnsafe(), node.toArrayUnsafe()); - trieBranchStorage.getSnapshotTransaction().put(WORLD_ROOT_HASH_KEY, nodeHash.toArrayUnsafe()); - trieBranchStorage - .getSnapshotTransaction() - .put(WORLD_BLOCK_HASH_KEY, blockHash.toArrayUnsafe()); + trieBranchStorageTransaction.put(Bytes.EMPTY.toArrayUnsafe(), node.toArrayUnsafe()); + trieBranchStorageTransaction.put(WORLD_ROOT_HASH_KEY, nodeHash.toArrayUnsafe()); + trieBranchStorageTransaction.put(WORLD_BLOCK_HASH_KEY, blockHash.toArrayUnsafe()); return this; } @@ -280,16 +266,14 @@ public WorldStateStorage.Updater putAccountStateTrieNode( // Don't save empty nodes return this; } - trieBranchStorage - .getSnapshotTransaction() - .put(location.toArrayUnsafe(), node.toArrayUnsafe()); + trieBranchStorageTransaction.put(location.toArrayUnsafe(), node.toArrayUnsafe()); return this; } @Override public WorldStateStorage.Updater removeAccountStateTrieNode( final Bytes location, final Bytes32 nodeHash) { - trieBranchStorage.getSnapshotTransaction().remove(location.toArrayUnsafe()); + trieBranchStorageTransaction.remove(location.toArrayUnsafe()); return this; } @@ -300,15 +284,17 @@ public WorldStateStorage.Updater putAccountStorageTrieNode( // Don't save empty nodes return this; } - trieBranchStorage - .getSnapshotTransaction() - .put(Bytes.concatenate(accountHash, location).toArrayUnsafe(), node.toArrayUnsafe()); + trieBranchStorageTransaction.put( + Bytes.concatenate(accountHash, location).toArrayUnsafe(), node.toArrayUnsafe()); return this; } @Override public void commit() { - // only commit the trielog layer transaction, leave the snapshot transactions open: + accountStorageTransaction.commit(); + codeStorageTransaction.commit(); + storageStorageTransaction.commit(); + trieBranchStorageTransaction.commit(); trieLogStorageTransaction.commit(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java index 6126203b97a..3084b18ac91 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java @@ -16,7 +16,6 @@ package org.hyperledger.besu.ethereum.bonsai; -import static com.google.common.base.Preconditions.checkNotNull; import static org.hyperledger.besu.datatypes.Hash.fromPlugin; import static org.hyperledger.besu.ethereum.bonsai.LayeredTrieLogManager.RETAINED_LAYERS; @@ -28,16 +27,22 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.SnapshotMutableWorldState; import org.hyperledger.besu.ethereum.proof.WorldStateProof; +import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; +import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; +import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.worldstate.WorldState; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.function.Function; import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; @@ -275,6 +280,9 @@ Optional rollMutableStateToBlockHash( LOG.debug("Archive rolling finished, now at {}", blockHash); return Optional.of(mutableState); + } catch (final MerkleTrieException re) { + // need to throw to trigger the heal + throw re; } catch (final Exception e) { // if we fail we must clean up the updater bonsaiUpdater.reset(); @@ -282,7 +290,11 @@ Optional rollMutableStateToBlockHash( return Optional.empty(); } } catch (final RuntimeException re) { - LOG.error("Archive rolling failed for block hash " + blockHash, re); + LOG.trace("Archive rolling failed for block hash " + blockHash, re); + if (re instanceof MerkleTrieException) { + // need to throw to trigger the heal + throw re; + } return Optional.empty(); } } @@ -302,6 +314,57 @@ public MutableWorldState getMutable() { return persistedState; } + public void prepareStateHealing(final Address address, final Bytes location) { + final Set keysToDelete = new HashSet<>(); + final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = worldStateStorage.updater(); + final Hash accountHash = Hash.hash(address); + final StoredMerklePatriciaTrie accountTrie = + new StoredMerklePatriciaTrie<>( + (l, h) -> { + final Optional node = worldStateStorage.getAccountStateTrieNode(l, h); + if (node.isPresent()) { + keysToDelete.add(l); + } + return node; + }, + persistedState.worldStateRootHash, + Function.identity(), + Function.identity()); + try { + accountTrie + .get(accountHash) + .map(RLP::input) + .map(StateTrieAccountValue::readFrom) + .ifPresent( + account -> { + final StoredMerklePatriciaTrie storageTrie = + new StoredMerklePatriciaTrie<>( + (l, h) -> { + Optional node = + worldStateStorage.getAccountStorageTrieNode(accountHash, l, h); + if (node.isPresent()) { + keysToDelete.add(Bytes.concatenate(accountHash, l)); + } + return node; + }, + account.getStorageRoot(), + Function.identity(), + Function.identity()); + try { + storageTrie.getPath(location); + } catch (Exception eA) { + LOG.warn("Invalid slot found for account {} at location {}", address, location); + // ignore + } + }); + } catch (Exception eA) { + LOG.warn("Invalid node for account {} at location {}", address, location); + // ignore + } + keysToDelete.forEach(bytes -> updater.removeAccountStateTrieNode(bytes, null)); + updater.commit(); + } + public TrieLogManager getTrieLogManager() { return trieLogManager; } @@ -324,9 +387,4 @@ public Optional getAccountProof( // FIXME we can do proofs for layered tries and the persisted trie return Optional.empty(); } - - public void useFallbackNodeFinder(final Optional fallbackNodeFinder) { - checkNotNull(fallbackNodeFinder); - worldStateStorage.useFallbackNodeFinder(fallbackNodeFinder); - } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java index 46ba2e5f197..0494832740e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java @@ -14,15 +14,12 @@ */ package org.hyperledger.besu.ethereum.bonsai; -import static com.google.common.base.Preconditions.checkNotNull; - import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.StoredNodeFactory; -import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; @@ -33,6 +30,7 @@ import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -52,16 +50,13 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC protected final KeyValueStorage trieLogStorage; protected final Subscribers subscribers = Subscribers.create(); - private Optional maybeFallbackNodeFinder; - public BonsaiWorldStateKeyValueStorage(final StorageProvider provider) { this( provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE), provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.CODE_STORAGE), provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE), provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE), - provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE), - Optional.empty()); + provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE)); } public BonsaiWorldStateKeyValueStorage( @@ -70,33 +65,23 @@ public BonsaiWorldStateKeyValueStorage( final KeyValueStorage storageStorage, final KeyValueStorage trieBranchStorage, final KeyValueStorage trieLogStorage) { - this( - accountStorage, - codeStorage, - storageStorage, - trieBranchStorage, - trieLogStorage, - Optional.empty()); - } - - public BonsaiWorldStateKeyValueStorage( - final KeyValueStorage accountStorage, - final KeyValueStorage codeStorage, - final KeyValueStorage storageStorage, - final KeyValueStorage trieBranchStorage, - final KeyValueStorage trieLogStorage, - final Optional fallbackNodeFinder) { this.accountStorage = accountStorage; this.codeStorage = codeStorage; this.storageStorage = storageStorage; this.trieBranchStorage = trieBranchStorage; this.trieLogStorage = trieLogStorage; - this.maybeFallbackNodeFinder = fallbackNodeFinder; } @Override public Optional getCode(final Bytes32 codeHash, final Hash accountHash) { - return codeStorage.get(accountHash.toArrayUnsafe()).map(Bytes::wrap); + if (codeHash.equals(Hash.EMPTY)) { + return Optional.of(Bytes.EMPTY); + } else { + return codeStorage + .get(accountHash.toArrayUnsafe()) + .map(Bytes::wrap) + .filter(b -> Hash.hash(b).equals(codeHash)); + } } public Optional getAccount(final Hash accountHash) { @@ -127,17 +112,10 @@ public Optional getAccountStateTrieNode(final Bytes location, final Bytes if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) { return Optional.of(MerklePatriciaTrie.EMPTY_TRIE_NODE); } else { - final Optional value = - trieBranchStorage.get(location.toArrayUnsafe()).map(Bytes::wrap); - if (value.isPresent()) { - return value - .filter(b -> Hash.hash(b).equals(nodeHash)) - .or( - () -> - maybeFallbackNodeFinder.flatMap( - finder -> finder.getAccountStateTrieNode(location, nodeHash))); - } - return Optional.empty(); + return trieBranchStorage + .get(location.toArrayUnsafe()) + .map(Bytes::wrap) + .filter(b -> Hash.hash(b).equals(nodeHash)); } } @@ -147,20 +125,10 @@ public Optional getAccountStorageTrieNode( if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) { return Optional.of(MerklePatriciaTrie.EMPTY_TRIE_NODE); } else { - final Optional value = - trieBranchStorage - .get(Bytes.concatenate(accountHash, location).toArrayUnsafe()) - .map(Bytes::wrap); - if (value.isPresent()) { - return value - .filter(b -> Hash.hash(b).equals(nodeHash)) - .or( - () -> - maybeFallbackNodeFinder.flatMap( - finder -> - finder.getAccountStorageTrieNode(accountHash, location, nodeHash))); - } - return Optional.empty(); + return trieBranchStorage + .get(Bytes.concatenate(accountHash, location).toArrayUnsafe()) + .map(Bytes::wrap) + .filter(b -> Hash.hash(b).equals(nodeHash)); } } @@ -181,24 +149,37 @@ public Optional getWorldStateBlockHash() { } public Optional getStorageValueBySlotHash(final Hash accountHash, final Hash slotHash) { + return getStorageValueBySlotHash( + () -> + getAccount(accountHash) + .map( + b -> + StateTrieAccountValue.readFrom( + org.hyperledger.besu.ethereum.rlp.RLP.input(b)) + .getStorageRoot()), + accountHash, + slotHash); + } + + public Optional getStorageValueBySlotHash( + final Supplier> storageRootSupplier, + final Hash accountHash, + final Hash slotHash) { Optional response = storageStorage .get(Bytes.concatenate(accountHash, slotHash).toArrayUnsafe()) .map(Bytes::wrap); if (response.isEmpty()) { - final Optional account = getAccount(accountHash); + final Optional storageRoot = storageRootSupplier.get(); final Optional worldStateRootHash = getWorldStateRootHash(); - if (account.isPresent() && worldStateRootHash.isPresent()) { - final StateTrieAccountValue accountValue = - StateTrieAccountValue.readFrom( - org.hyperledger.besu.ethereum.rlp.RLP.input(account.get())); + if (storageRoot.isPresent() && worldStateRootHash.isPresent()) { response = new StoredMerklePatriciaTrie<>( new StoredNodeFactory<>( (location, hash) -> getAccountStorageTrieNode(accountHash, location, hash), Function.identity(), Function.identity()), - accountValue.getStorageRoot()) + storageRoot.get()) .get(slotHash) .map(bytes -> Bytes32.leftPad(RLP.decodeValue(bytes))); } @@ -214,11 +195,10 @@ public Optional getNodeData(final Bytes location, final Bytes32 hash) { @Override public boolean isWorldStateAvailable(final Bytes32 rootHash, final Hash blockHash) { return trieBranchStorage - .get(WORLD_ROOT_HASH_KEY) - .map(Bytes32::wrap) - .filter(hash -> hash.equals(rootHash)) - .isPresent() - || trieLogStorage.containsKey(blockHash.toArrayUnsafe()); + .get(WORLD_ROOT_HASH_KEY) + .map(Bytes32::wrap) + .map(hash -> hash.equals(rootHash) || trieLogStorage.containsKey(blockHash.toArrayUnsafe())) + .orElse(false); } @Override @@ -231,6 +211,12 @@ public void clear() { trieLogStorage.clear(); } + @Override + public void clearTrieLog() { + subscribers.forEach(BonsaiStorageSubscriber::onClearTrieLog); + trieLogStorage.clear(); + } + @Override public void clearFlatDatabase() { subscribers.forEach(BonsaiStorageSubscriber::onClearFlatDatabaseStorage); @@ -263,15 +249,6 @@ public void removeNodeAddedListener(final long id) { throw new RuntimeException("removeNodeAddedListener not available"); } - public Optional getMaybeFallbackNodeFinder() { - return maybeFallbackNodeFinder; - } - - public void useFallbackNodeFinder(final Optional maybeFallbackNodeFinder) { - checkNotNull(maybeFallbackNodeFinder); - this.maybeFallbackNodeFinder = maybeFallbackNodeFinder; - } - public synchronized long subscribe(final BonsaiStorageSubscriber sub) { return subscribers.subscribe(sub); } @@ -442,6 +419,8 @@ default void onClearStorage() {} default void onClearFlatDatabaseStorage() {} + default void onClearTrieLog() {} + default void onCloseStorage() {} } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java index 56a288032fa..8b428ed2807 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.EvmAccount; @@ -37,6 +38,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import com.google.common.collect.ForwardingMap; import org.apache.tuweni.bytes.Bytes; @@ -55,6 +57,7 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater storagePreloader; private final Map> codeToUpdate = new ConcurrentHashMap<>(); private final Set
storageToClear = Collections.synchronizedSet(new HashSet<>()); + private final Set emptySlot = Collections.synchronizedSet(new HashSet<>()); // storage sub mapped by _hashed_ key. This is because in self_destruct calls we need to // enumerate the old storage and delete it. Those are trie stored by hashed key by spec and the @@ -90,11 +93,12 @@ void cloneFromUpdater(final BonsaiWorldStateUpdater source) { storageToUpdate.putAll(source.storageToUpdate); updatedAccounts.putAll(source.updatedAccounts); deletedAccounts.addAll(source.deletedAccounts); + emptySlot.addAll(source.emptySlot); } @Override public Account get(final Address address) { - return super.get(address); + return super.getAccount(address); } @Override @@ -149,18 +153,31 @@ Map>> getStorageToUpdate() { @Override protected BonsaiAccount getForMutation(final Address address) { - final BonsaiValue bonsaiValue = accountsToUpdate.get(address); - if (bonsaiValue == null) { - final Account account = wrappedWorldView().get(address); - if (account instanceof BonsaiAccount) { - final BonsaiAccount mutableAccount = new BonsaiAccount((BonsaiAccount) account, this, true); - accountsToUpdate.put(address, new BonsaiValue<>((BonsaiAccount) account, mutableAccount)); - return mutableAccount; + return loadAccount(address, BonsaiValue::getUpdated); + } + + protected BonsaiAccount loadAccount( + final Address address, + final Function, BonsaiAccount> bonsaiAccountFunction) { + try { + final BonsaiValue bonsaiValue = accountsToUpdate.get(address); + if (bonsaiValue == null) { + final Account account = wrappedWorldView().get(address); + if (account instanceof BonsaiAccount) { + final BonsaiAccount mutableAccount = + new BonsaiAccount((BonsaiAccount) account, this, true); + accountsToUpdate.put(address, new BonsaiValue<>((BonsaiAccount) account, mutableAccount)); + return mutableAccount; + } else { + return null; + } } else { - return null; + return bonsaiAccountFunction.apply(bonsaiValue); } - } else { - return bonsaiValue.getUpdated(); + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); } } @@ -192,7 +209,12 @@ public void commit() { codeValue.setUpdated(null); } else { wrappedWorldView() - .getCode(deletedAddress) + .getCode( + deletedAddress, + Optional.ofNullable(accountValue) + .map(BonsaiValue::getPrior) + .map(BonsaiAccount::getCodeHash) + .orElse(Hash.EMPTY)) .ifPresent( deletedCode -> codeToUpdate.put(deletedAddress, new BonsaiValue<>(deletedCode, null))); @@ -242,9 +264,9 @@ public void commit() { tracked -> { final Address updatedAddress = tracked.getAddress(); final BonsaiAccount updatedAccount; + final BonsaiValue updatedAccountValue = + accountsToUpdate.get(updatedAddress); if (tracked.getWrappedAccount() == null) { - final BonsaiValue updatedAccountValue = - accountsToUpdate.get(updatedAddress); updatedAccount = new BonsaiAccount(this, tracked); tracked.setWrappedAccount(updatedAccount); if (updatedAccountValue == null) { @@ -272,7 +294,16 @@ public void commit() { codeToUpdate.computeIfAbsent( updatedAddress, addr -> - new BonsaiValue<>(wrappedWorldView().getCode(addr).orElse(null), null)); + new BonsaiValue<>( + wrappedWorldView() + .getCode( + addr, + Optional.ofNullable(updatedAccountValue) + .map(BonsaiValue::getPrior) + .map(BonsaiAccount::getCodeHash) + .orElse(Hash.EMPTY)) + .orElse(null), + null)); pendingCode.setUpdated(updatedAccount.getCode()); } @@ -321,10 +352,10 @@ public void commit() { } @Override - public Optional getCode(final Address address) { + public Optional getCode(final Address address, final Hash codeHash) { final BonsaiValue localCode = codeToUpdate.get(address); if (localCode == null) { - return wrappedWorldView().getCode(address); + return wrappedWorldView().getCode(address, codeHash); } else { return Optional.ofNullable(localCode.getUpdated()); } @@ -352,18 +383,40 @@ public Optional getStorageValueBySlotHash(final Address address, final return Optional.ofNullable(value.getUpdated()); } } - final Optional valueUInt = - wrappedWorldView().getStorageValueBySlotHash(address, slotHash); - valueUInt.ifPresent( - v -> - storageToUpdate - .computeIfAbsent( - address, - key -> - new StorageConsumingMap<>( - address, new ConcurrentHashMap<>(), storagePreloader)) - .put(slotHash, new BonsaiValue<>(v, v))); - return valueUInt; + final Bytes slot = Bytes.concatenate(Hash.hash(address), slotHash); + if (emptySlot.contains(slot)) { + return Optional.empty(); + } else { + try { + final Optional valueUInt = + (wrappedWorldView() instanceof BonsaiPersistedWorldState) + ? ((BonsaiPersistedWorldState) wrappedWorldView()) + .getStorageValueBySlotHash( + () -> + Optional.ofNullable(loadAccount(address, BonsaiValue::getPrior)) + .map(BonsaiAccount::getStorageRoot), + address, + slotHash) + : wrappedWorldView().getStorageValueBySlotHash(address, slotHash); + valueUInt.ifPresentOrElse( + v -> + storageToUpdate + .computeIfAbsent( + address, + key -> + new StorageConsumingMap<>( + address, new ConcurrentHashMap<>(), storagePreloader)) + .put(slotHash, new BonsaiValue<>(v, v)), + () -> { + emptySlot.add(Bytes.concatenate(Hash.hash(address), slotHash)); + }); + return valueUInt; + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); + } + } } @Override @@ -554,15 +607,21 @@ private void rollAccountChange( private BonsaiValue loadAccountFromParent( final Address address, final BonsaiValue defaultValue) { - final Account parentAccount = wrappedWorldView().get(address); - if (parentAccount instanceof BonsaiAccount) { - final BonsaiAccount account = (BonsaiAccount) parentAccount; - final BonsaiValue loadedAccountValue = - new BonsaiValue<>(new BonsaiAccount(account), account); - accountsToUpdate.put(address, loadedAccountValue); - return loadedAccountValue; - } else { - return defaultValue; + try { + final Account parentAccount = wrappedWorldView().get(address); + if (parentAccount instanceof BonsaiAccount) { + final BonsaiAccount account = (BonsaiAccount) parentAccount; + final BonsaiValue loadedAccountValue = + new BonsaiValue<>(new BonsaiAccount(account), account); + accountsToUpdate.put(address, loadedAccountValue); + return loadedAccountValue; + } else { + return defaultValue; + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); } } @@ -574,7 +633,11 @@ private void rollCodeChange( } BonsaiValue codeValue = codeToUpdate.get(address); if (codeValue == null) { - final Bytes storedCode = wrappedWorldView().getCode(address).orElse(Bytes.EMPTY); + final Bytes storedCode = + wrappedWorldView() + .getCode( + address, Optional.ofNullable(expectedCode).map(Hash::hash).orElse(Hash.EMPTY)) + .orElse(Bytes.EMPTY); if (!storedCode.isEmpty()) { codeValue = new BonsaiValue<>(storedCode, storedCode); codeToUpdate.put(address, codeValue); @@ -706,6 +769,7 @@ public void reset() { storageToUpdate.clear(); codeToUpdate.clear(); accountsToUpdate.clear(); + emptySlot.clear(); super.reset(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldView.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldView.java index 507e511f95b..8847dd95473 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldView.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldView.java @@ -30,7 +30,7 @@ public interface BonsaiWorldView extends WorldView { - Optional getCode(Address address); + Optional getCode(Address address, final Hash codeHash); Optional getStateTrieNode(Bytes location); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/SnapshotTrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/SnapshotTrieLogManager.java index 45c1effe319..ddaefd3990b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/SnapshotTrieLogManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/SnapshotTrieLogManager.java @@ -105,6 +105,11 @@ public synchronized void onClearFlatDatabaseStorage() { dropArchive(); } + @Override + public void onClearTrieLog() { + dropArchive(); + } + private void dropArchive() { // drop all cached snapshot worldstates, they are unsafe when the db has been truncated LOG.info("Key-value storage truncated, dropping cached worldstates"); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java index f2fd2a2501a..da7055b51c3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java @@ -49,6 +49,12 @@ public void addBadBlock(final Block badBlock, final Optional cause) { } } + public void reset() { + this.badBlocks.invalidateAll(); + this.badHeaders.invalidateAll(); + this.latestValidHashes.invalidateAll(); + } + /** * Return all invalid blocks * diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Synchronizer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Synchronizer.java index 72f9de06dc5..8034d9e1fea 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Synchronizer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Synchronizer.java @@ -14,12 +14,15 @@ */ package org.hyperledger.besu.ethereum.core; +import org.hyperledger.besu.plugin.data.Address; import org.hyperledger.besu.plugin.data.SyncStatus; import org.hyperledger.besu.plugin.services.BesuEvents; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import org.apache.tuweni.bytes.Bytes; + /** Provides an interface to block synchronization processes. */ public interface Synchronizer { @@ -40,6 +43,10 @@ public interface Synchronizer { */ Optional getSyncStatus(); + boolean resyncWorldState(); + + boolean healWorldState(final Optional
maybeAccountToRepair, final Bytes location); + long subscribeSyncStatus(final BesuEvents.SyncStatusListener listener); boolean unsubscribeSyncStatus(long observerId); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java index 02ae5cae2fe..fee45331b41 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldState; @@ -144,6 +145,12 @@ public BlockProcessingResult processBlock( try { worldState.persist(blockHeader); + } catch (MerkleTrieException e) { + LOG.trace("Merkle trie exception during Transaction processing ", e); + if (worldState instanceof BonsaiPersistedWorldState) { + ((BonsaiWorldStateUpdater) worldState.updater()).reset(); + } + throw e; } catch (Exception e) { LOG.error("failed persisting block", e); return new BlockProcessingResult(Optional.empty(), e); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index 527d67446e5..b0a816838c3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.worldstate.GoQuorumMutablePrivateWorldStateUpdater; import org.hyperledger.besu.evm.AccessListEntry; @@ -469,6 +470,9 @@ public TransactionProcessingResult processTransaction( return TransactionProcessingResult.failed( gasUsedByTransaction, refunded, validationResult, initialFrame.getRevertReason()); } + } catch (final MerkleTrieException re) { + // need to throw to trigger the heal + throw re; } catch (final RuntimeException re) { LOG.error("Critical Exception Processing Transaction", re); return TransactionProcessingResult.invalid( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/WorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/WorldStateKeyValueStorage.java index 362bd514134..9cf1c0cb23b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/WorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/WorldStateKeyValueStorage.java @@ -97,6 +97,11 @@ public void clear() { keyValueStorage.clear(); } + @Override + public void clearTrieLog() { + // nothing to do for forest + } + @Override public void clearFlatDatabase() { // nothing to do for forest diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorage.java index 5411829e186..a425bf2e7f6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorage.java @@ -44,6 +44,8 @@ default boolean contains(final Bytes32 hash) { void clear(); + void clearTrieLog(); + void clearFlatDatabase(); Updater updater(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java index 145cbee9c9b..77f3e11a151 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java @@ -18,11 +18,9 @@ import static org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -32,10 +30,8 @@ import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.StorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import java.util.Optional; import java.util.TreeMap; import org.apache.tuweni.bytes.Bytes; @@ -48,7 +44,7 @@ public class BonsaiWorldStateKeyValueStorageTest { @Test public void getCode_returnsEmpty() { final BonsaiWorldStateKeyValueStorage storage = emptyStorage(); - assertThat(storage.getCode(null, Hash.EMPTY)).isEmpty(); + assertThat(storage.getCode(Hash.EMPTY, Hash.EMPTY)).contains(Bytes.EMPTY); } @Test @@ -89,7 +85,8 @@ public void getCode_saveAndGetSpecialValues() { .putCode(Hash.EMPTY, Bytes.EMPTY) .commit(); - assertThat(storage.getCode(null, Hash.EMPTY)).contains(MerklePatriciaTrie.EMPTY_TRIE_NODE); + assertThat(storage.getCode(Hash.hash(MerklePatriciaTrie.EMPTY_TRIE_NODE), Hash.EMPTY)) + .contains(MerklePatriciaTrie.EMPTY_TRIE_NODE); } @Test @@ -98,7 +95,7 @@ public void getCode_saveAndGetRegularValue() { final BonsaiWorldStateKeyValueStorage storage = emptyStorage(); storage.updater().putCode(Hash.EMPTY, bytes).commit(); - assertThat(storage.getCode(null, Hash.EMPTY)).contains(bytes); + assertThat(storage.getCode(Hash.hash(bytes), Hash.EMPTY)).contains(bytes); } @Test @@ -255,9 +252,8 @@ public void reconcilesNonConflictingUpdaters() { updaterA.commit(); updaterB.commit(); - assertThat(storage.getCode(null, accountHashA)).contains(bytesA); - assertThat(storage.getCode(null, accountHashB)).contains(bytesB); - assertThat(storage.getCode(null, accountHashD)).contains(bytesC); + assertThat(storage.getCode(Hash.hash(bytesB), accountHashB)).contains(bytesB); + assertThat(storage.getCode(Hash.hash(bytesC), accountHashD)).contains(bytesC); } @Test @@ -295,62 +291,6 @@ public void isWorldStateAvailable_afterCallingSaveWorldstate() { assertThat(storage.isWorldStateAvailable(Bytes32.wrap(nodeHashKey), Hash.EMPTY)).isTrue(); } - @Test - public void getAccountStateTrieNode_callFallbackMechanismForInvalidNode() { - - PeerTrieNodeFinder peerTrieNodeFinder = mock(PeerTrieNodeFinder.class); - - final Bytes location = Bytes.fromHexString("0x01"); - final Bytes bytesInDB = Bytes.fromHexString("0x123456"); - - final Hash hashToFind = Hash.hash(Bytes.of(1)); - final Bytes bytesToFind = Bytes.fromHexString("0x123457"); - - final BonsaiWorldStateKeyValueStorage storage = emptyStorage(); - - when(peerTrieNodeFinder.getAccountStateTrieNode(location, hashToFind)) - .thenReturn(Optional.of(bytesToFind)); - storage.useFallbackNodeFinder(Optional.of(peerTrieNodeFinder)); - - storage.updater().putAccountStateTrieNode(location, Hash.hash(bytesInDB), bytesInDB).commit(); - - Optional accountStateTrieNodeResult = - storage.getAccountStateTrieNode(location, hashToFind); - - verify(peerTrieNodeFinder).getAccountStateTrieNode(location, hashToFind); - assertThat(accountStateTrieNodeResult).contains(bytesToFind); - } - - @Test - public void getAccountStorageTrieNode_callFallbackMechanismForInvalidNode() { - - PeerTrieNodeFinder peerTrieNodeFinder = mock(PeerTrieNodeFinder.class); - - final Hash account = Hash.hash(Bytes32.ZERO); - final Bytes location = Bytes.fromHexString("0x01"); - final Bytes bytesInDB = Bytes.fromHexString("0x123456"); - - final Hash hashToFind = Hash.hash(Bytes.of(1)); - final Bytes bytesToFind = Bytes.fromHexString("0x123457"); - - final BonsaiWorldStateKeyValueStorage storage = emptyStorage(); - - when(peerTrieNodeFinder.getAccountStorageTrieNode(account, location, hashToFind)) - .thenReturn(Optional.of(bytesToFind)); - storage.useFallbackNodeFinder(Optional.of(peerTrieNodeFinder)); - - storage - .updater() - .putAccountStorageTrieNode(account, location, Hash.hash(bytesInDB), bytesInDB) - .commit(); - - Optional accountStateTrieNodeResult = - storage.getAccountStorageTrieNode(account, location, hashToFind); - - verify(peerTrieNodeFinder).getAccountStorageTrieNode(account, location, hashToFind); - assertThat(accountStateTrieNodeResult).contains(bytesToFind); - } - private BonsaiWorldStateKeyValueStorage emptyStorage() { return new BonsaiWorldStateKeyValueStorage(new InMemoryKeyValueStorageProvider()); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java index 5ce55ce7177..4cc3217acde 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java @@ -15,12 +15,12 @@ package org.hyperledger.besu.ethereum.eth.sync; import static com.google.common.base.Preconditions.checkNotNull; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.infoLambda; import org.hyperledger.besu.consensus.merge.ForkchoiceEvent; import org.hyperledger.besu.consensus.merge.UnverifiedForkchoiceListener; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive; -import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.sync.checkpointsync.CheckpointDownloaderFactory; @@ -33,24 +33,27 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapPersistedContext; import org.hyperledger.besu.ethereum.eth.sync.state.PendingBlocksManager; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; -import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStatePeerTrieNodeFinder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager; import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; import org.hyperledger.besu.ethereum.worldstate.Pruner; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.plugin.data.Address; import org.hyperledger.besu.plugin.data.SyncStatus; import org.hyperledger.besu.plugin.services.BesuEvents.SyncStatusListener; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.util.log.FramedLogMessage; import java.nio.file.Path; import java.time.Clock; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,13 +65,10 @@ public class DefaultSynchronizer implements Synchronizer, UnverifiedForkchoiceLi private final SyncState syncState; private final AtomicBoolean running = new AtomicBoolean(false); private final Optional blockPropagationManager; - private final Optional> fastSyncDownloader; + private final Supplier>> fastSyncFactory; + private Optional> fastSyncDownloader; private final Optional fullSyncDownloader; - private final EthContext ethContext; private final ProtocolContext protocolContext; - private final ProtocolManager protocolManager; - private final WorldStateStorage worldStateStorage; - private final MetricsSystem metricsSystem; private final PivotBlockSelector pivotBlockSelector; private final SyncTerminationCondition terminationCondition; @@ -86,16 +86,11 @@ public DefaultSynchronizer( final Clock clock, final MetricsSystem metricsSystem, final SyncTerminationCondition terminationCondition, - final ProtocolManager protocolManager, final PivotBlockSelector pivotBlockSelector) { this.maybePruner = maybePruner; this.syncState = syncState; - this.protocolManager = protocolManager; this.pivotBlockSelector = pivotBlockSelector; - this.ethContext = ethContext; this.protocolContext = protocolContext; - this.worldStateStorage = worldStateStorage; - this.metricsSystem = metricsSystem; this.terminationCondition = terminationCondition; ChainHeadTracker.trackChainHeadForPeers( @@ -133,48 +128,54 @@ public DefaultSynchronizer( terminationCondition)); if (SyncMode.FAST.equals(syncConfig.getSyncMode())) { - this.fastSyncDownloader = - FastDownloaderFactory.create( - pivotBlockSelector, - syncConfig, - dataDirectory, - protocolSchedule, - protocolContext, - metricsSystem, - ethContext, - worldStateStorage, - syncState, - clock); + this.fastSyncFactory = + () -> + FastDownloaderFactory.create( + pivotBlockSelector, + syncConfig, + dataDirectory, + protocolSchedule, + protocolContext, + metricsSystem, + ethContext, + worldStateStorage, + syncState, + clock); } else if (SyncMode.X_CHECKPOINT.equals(syncConfig.getSyncMode())) { - this.fastSyncDownloader = - CheckpointDownloaderFactory.createCheckpointDownloader( - new SnapPersistedContext(storageProvider), - pivotBlockSelector, - syncConfig, - dataDirectory, - protocolSchedule, - protocolContext, - metricsSystem, - ethContext, - worldStateStorage, - syncState, - clock); + this.fastSyncFactory = + () -> + CheckpointDownloaderFactory.createCheckpointDownloader( + new SnapPersistedContext(storageProvider), + pivotBlockSelector, + syncConfig, + dataDirectory, + protocolSchedule, + protocolContext, + metricsSystem, + ethContext, + worldStateStorage, + syncState, + clock); } else { - this.fastSyncDownloader = - SnapDownloaderFactory.createSnapDownloader( - new SnapPersistedContext(storageProvider), - pivotBlockSelector, - syncConfig, - dataDirectory, - protocolSchedule, - protocolContext, - metricsSystem, - ethContext, - worldStateStorage, - syncState, - clock); + this.fastSyncFactory = + () -> + SnapDownloaderFactory.createSnapDownloader( + new SnapPersistedContext(storageProvider), + pivotBlockSelector, + syncConfig, + dataDirectory, + protocolSchedule, + protocolContext, + metricsSystem, + ethContext, + worldStateStorage, + syncState, + clock); } + // create a non-resync fast sync downloader: + this.fastSyncDownloader = this.fastSyncFactory.get(); + metricsSystem.createLongGauge( BesuMetricCategory.ETHEREUM, "best_known_block_number", @@ -209,10 +210,8 @@ public CompletableFuture start() { CompletableFuture future; if (fastSyncDownloader.isPresent()) { future = fastSyncDownloader.get().start().thenCompose(this::handleSyncResult); - } else { syncState.markInitialSyncPhaseAsDone(); - enableFallbackNodeFinder(); future = startFullSync(); } return future.thenApply(this::finalizeSync); @@ -261,8 +260,6 @@ private CompletableFuture handleSyncResult(final FastSyncState result) { pivotBlockSelector.close(); syncState.markInitialSyncPhaseAsDone(); - enableFallbackNodeFinder(); - if (terminationCondition.shouldContinueDownload()) { return startFullSync(); } else { @@ -271,19 +268,6 @@ private CompletableFuture handleSyncResult(final FastSyncState result) { } } - private void enableFallbackNodeFinder() { - if (worldStateStorage instanceof BonsaiWorldStateKeyValueStorage) { - final Optional fallbackNodeFinder = - Optional.of( - new WorldStatePeerTrieNodeFinder( - ethContext, protocolManager, protocolContext.getBlockchain(), metricsSystem)); - ((BonsaiWorldStateArchive) protocolContext.getWorldStateArchive()) - .useFallbackNodeFinder(fallbackNodeFinder); - ((BonsaiWorldStateKeyValueStorage) worldStateStorage) - .useFallbackNodeFinder(fallbackNodeFinder); - } - } - private CompletableFuture startFullSync() { maybePruner.ifPresent(Pruner::start); return fullSyncDownloader @@ -305,6 +289,52 @@ public Optional getSyncStatus() { return syncState.syncStatus(); } + @Override + public boolean resyncWorldState() { + // if sync is running currently, stop it and delete the fast sync state + if (fastSyncDownloader.isPresent() && running.get()) { + stop(); + fastSyncDownloader.get().deleteFastSyncState(); + } + // recreate fast sync with resync and start + this.syncState.markInitialSyncRestart(); + this.syncState.markResyncNeeded(); + this.fastSyncDownloader = this.fastSyncFactory.get(); + start(); + return true; + } + + @Override + public boolean healWorldState( + final Optional
maybeAccountToRepair, final Bytes location) { + // recreate fast sync with resync and start + if (fastSyncDownloader.isPresent() && running.get()) { + stop(); + fastSyncDownloader.get().deleteFastSyncState(); + } + + final List lines = new ArrayList<>(); + lines.add("Besu has identified a problem with its worldstate database."); + lines.add("Your node will fetch the correct data from peers to repair the problem."); + lines.add("Starting the sync pipeline..."); + infoLambda(LOG, FramedLogMessage.generate(lines)); + + this.syncState.markInitialSyncRestart(); + this.syncState.markResyncNeeded(); + maybeAccountToRepair.ifPresent( + address -> { + if (this.protocolContext.getWorldStateArchive() instanceof BonsaiWorldStateArchive) { + ((BonsaiWorldStateArchive) this.protocolContext.getWorldStateArchive()) + .prepareStateHealing( + org.hyperledger.besu.datatypes.Address.wrap(address), location); + } + this.syncState.markAccountToRepair(maybeAccountToRepair); + }); + this.fastSyncDownloader = this.fastSyncFactory.get(); + start(); + return true; + } + @Override public long subscribeSyncStatus(final SyncStatusListener listener) { checkNotNull(listener); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java index 454c0fe2f16..d4aca18190f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java @@ -157,6 +157,7 @@ private synchronized CompletionStage startDownloadForSyncTarget(final Sync if (!syncTargetManager.shouldContinueDownloading()) { return CompletableFuture.completedFuture(null); } + syncState.setSyncTarget(target.peer(), target.commonAncestor()); debugLambda( LOG, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStep.java index dd05a02dd0f..d00f979b773 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStep.java @@ -59,10 +59,12 @@ protected Hash possibleRestoreOldNodes(final BlockHeader firstAncestor) { @VisibleForTesting protected CompletableFuture> requestHeaders(final Hash hash) { - if (context.getProtocolContext().getBlockchain().contains(hash)) { + final Optional blockHeader = + context.getProtocolContext().getBlockchain().getBlockHeader(hash); + if (blockHeader.isPresent()) { LOG.debug( "Hash {} already present in local blockchain no need to request headers to peers", hash); - return CompletableFuture.completedFuture(List.of()); + return CompletableFuture.completedFuture(List.of(blockHeader.get())); } final int batchSize = context.getBatchSize(); @@ -104,8 +106,9 @@ protected Void saveHeaders(final List blockHeaders) { saveHeader(blockHeader); } - logProgress(blockHeaders.get(blockHeaders.size() - 1).getNumber()); - + if (!blockHeaders.isEmpty()) { + logProgress(blockHeaders.get(blockHeaders.size() - 1).getNumber()); + } return null; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardsSyncAlgorithm.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardsSyncAlgorithm.java index 648c706f1f9..9cfe6c448f4 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardsSyncAlgorithm.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardsSyncAlgorithm.java @@ -24,21 +24,25 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.task.WaitForPeersTask; +import org.hyperledger.besu.plugin.services.BesuEvents; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; -public class BackwardsSyncAlgorithm { +public class BackwardsSyncAlgorithm implements BesuEvents.InitialSyncCompletionListener { private static final Logger LOG = getLogger(BackwardsSyncAlgorithm.class); private final BackwardSyncContext context; private final FinalBlockConfirmation finalBlockConfirmation; + private final AtomicReference latch = + new AtomicReference<>(new CountDownLatch(1)); private volatile boolean finished = false; public BackwardsSyncAlgorithm( @@ -80,11 +84,13 @@ public CompletableFuture pickNextStep() { final MutableBlockchain blockchain = context.getProtocolContext().getBlockchain(); final BlockHeader firstAncestorHeader = maybeFirstAncestorHeader.get(); - if (blockchain.contains(firstAncestorHeader.getHash())) { + final BlockHeader chainHeader = blockchain.getChainHeadHeader(); + if (blockchain.contains(firstAncestorHeader.getHash()) + && firstAncestorHeader.getNumber() <= chainHeader.getNumber()) { return executeProcessKnownAncestors(); } - if (blockchain.getChainHead().getHeight() > firstAncestorHeader.getNumber()) { + if (chainHeader.getNumber() > firstAncestorHeader.getNumber()) { debugLambda( LOG, "Backward reached below current chain head {} : {}", @@ -125,19 +131,16 @@ protected CompletableFuture executeForwardAsync() { @VisibleForTesting protected CompletableFuture waitForReady() { - final CountDownLatch latch = new CountDownLatch(1); - final long idTTD = - context.getSyncState().subscribeTTDReached(reached -> countDownIfReady(latch)); - final long idIS = - context.getSyncState().subscribeCompletionReached(() -> countDownIfReady(latch)); - return CompletableFuture.runAsync(() -> checkReadiness(latch, idTTD, idIS)); + final long idTTD = context.getSyncState().subscribeTTDReached(reached -> countDownIfReady()); + final long idIS = context.getSyncState().subscribeCompletionReached(this); + return CompletableFuture.runAsync(() -> checkReadiness(idTTD, idIS)); } - private void checkReadiness(final CountDownLatch latch, final long idTTD, final long idIS) { + private void checkReadiness(final long idTTD, final long idIS) { try { if (!context.isReady()) { LOG.debug("Waiting for preconditions..."); - final boolean await = latch.await(2, TimeUnit.MINUTES); + final boolean await = latch.get().await(2, TimeUnit.MINUTES); if (await) { LOG.debug("Preconditions meet, ensure at least one peer is connected"); waitForPeers(1).get(); @@ -156,9 +159,9 @@ private void checkReadiness(final CountDownLatch latch, final long idTTD, final } } - private void countDownIfReady(final CountDownLatch latch) { + private void countDownIfReady() { if (context.isReady()) { - latch.countDown(); + latch.get().countDown(); } } @@ -167,4 +170,14 @@ private CompletableFuture waitForPeers(final int count) { WaitForPeersTask.create(context.getEthContext(), count, context.getMetricsSystem()); return waitForPeersTask.run(); } + + @Override + public void onInitialSyncCompleted() { + countDownIfReady(); + } + + @Override + public void onInitialSyncRestart() { + latch.set(new CountDownLatch(1)); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/FinalBlockConfirmation.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/FinalBlockConfirmation.java index b4ebbf2c9c7..078220c535e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/FinalBlockConfirmation.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/FinalBlockConfirmation.java @@ -44,7 +44,9 @@ static FinalBlockConfirmation genesisConfirmation(final MutableBlockchain blockc } static FinalBlockConfirmation ancestorConfirmation(final MutableBlockchain blockchain) { - return firstHeader -> blockchain.contains(firstHeader.getParentHash()); + return firstHeader -> + blockchain.contains(firstHeader.getParentHash()) + && blockchain.getChainHeadBlockNumber() + 1 >= firstHeader.getNumber(); } static FinalBlockConfirmation confirmationChain(final FinalBlockConfirmation... confirmations) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStep.java index 05b8e37b63d..beeaf741f00 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStep.java @@ -105,11 +105,11 @@ protected Void saveBlocks(final List blocks) { } for (Block block : blocks) { - final Optional parent = + final Optional parent = context .getProtocolContext() .getBlockchain() - .getBlockByHash(block.getHeader().getParentHash()); + .getBlockHeader(block.getHeader().getParentHash()); if (parent.isEmpty()) { context.halveBatchSize(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ProcessKnownAncestorsStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ProcessKnownAncestorsStep.java index 90ecf0861c3..97ec615e3df 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ProcessKnownAncestorsStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ProcessKnownAncestorsStep.java @@ -20,8 +20,10 @@ import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda; import static org.slf4j.LoggerFactory.getLogger; +import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import com.google.common.annotations.VisibleForTesting; @@ -47,17 +49,33 @@ public CompletableFuture executeAsync() { protected void processKnownAncestors() { while (backwardChain.getFirstAncestorHeader().isPresent()) { BlockHeader header = backwardChain.getFirstAncestorHeader().orElseThrow(); - if (context.getProtocolContext().getBlockchain().contains(header.getHash())) { + final long chainHeadBlockNumber = + context.getProtocolContext().getBlockchain().getChainHeadBlockNumber(); + boolean isFirstUnProcessedHeader = true; + if (context.getProtocolContext().getBlockchain().contains(header.getHash()) + && header.getNumber() <= chainHeadBlockNumber) { debugLambda( LOG, "Block {} is already imported, we can ignore it for the sync process", - () -> header.toLogString()); + header::toLogString); backwardChain.dropFirstHeader(); - } else if (context.getProtocolContext().getBlockchain().contains(header.getParentHash()) - && backwardChain.isTrusted(header.getHash())) { - debugLambda(LOG, "Importing trusted block {}", header::toLogString); - context.saveBlock(backwardChain.getTrustedBlock(header.getHash())); - } else { + isFirstUnProcessedHeader = false; + } else if (context.getProtocolContext().getBlockchain().contains(header.getParentHash())) { + final boolean isTrustedBlock = backwardChain.isTrusted(header.getHash()); + final Optional block = + isTrustedBlock + ? Optional.of(backwardChain.getTrustedBlock(header.getHash())) + : context.getProtocolContext().getBlockchain().getBlockByHash(header.getHash()); + if (block.isPresent()) { + debugLambda(LOG, "Importing block {}", header::toLogString); + context.saveBlock(block.get()); + if (isTrustedBlock) { + backwardChain.dropFirstHeader(); + isFirstUnProcessedHeader = false; + } + } + } + if (isFirstUnProcessedHeader) { debugLambda(LOG, "First unprocessed header is {}", header::toLogString); return; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java index 781162a36f2..d53d816e693 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java @@ -14,6 +14,7 @@ */ 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; @@ -79,7 +80,13 @@ public static Optional> createCheckpointDownloader( final FastSyncState fastSyncState = fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); - if (fastSyncState.getPivotBlockHeader().isEmpty() + + if (syncState.isResyncNeeded()) { + snapContext.clear(); + syncState + .getAccountToRepair() + .ifPresent(address -> snapContext.addInconsistentAccount(Hash.hash(address))); + } else if (fastSyncState.getPivotBlockHeader().isEmpty() && protocolContext.getBlockchain().getChainHeadBlockNumber() != BlockHeader.GENESIS_BLOCK_NUMBER) { LOG.info( @@ -101,10 +108,12 @@ public static Optional> createCheckpointDownloader( pivotBlockSelector, metricsSystem); } else { - LOG.info( - "Checkpoint sync start with block {} and hash {}", - syncState.getCheckpoint().get().blockNumber(), - syncState.getCheckpoint().get().blockHash()); + if (!syncState.isResyncNeeded()) { + LOG.info( + "Checkpoint sync start with block {} and hash {}", + syncState.getCheckpoint().get().blockNumber(), + syncState.getCheckpoint().get().blockHash()); + } fastSyncActions = new CheckpointSyncActions( syncConfig, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java index 7791504fcff..4efe3b8a7c0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -85,6 +85,7 @@ protected CompletableFuture start(final FastSyncState fastSyncSta if (worldStateStorage instanceof BonsaiWorldStateKeyValueStorage) { LOG.info("Clearing bonsai flat account db"); worldStateStorage.clearFlatDatabase(); + worldStateStorage.clearTrieLog(); } LOG.debug("Start sync with initial sync state {}", fastSyncState); return findPivotBlock(fastSyncState, fss -> downloadChainAndWorldState(fastSyncActions, fss)); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncTargetManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncTargetManager.java index dc07eb28a55..a32b81c8f1a 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncTargetManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncTargetManager.java @@ -150,8 +150,16 @@ private boolean peerHasDifferentPivotBlock(final List result) { @Override public boolean shouldContinueDownloading() { final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); - return !protocolContext.getBlockchain().getChainHeadHash().equals(pivotBlockHeader.getHash()) - || !worldStateStorage.isWorldStateAvailable( - pivotBlockHeader.getStateRoot(), pivotBlockHeader.getBlockHash()); + boolean isValidChainHead = + protocolContext.getBlockchain().getChainHeadHash().equals(pivotBlockHeader.getHash()); + if (!isValidChainHead) { + if (protocolContext.getBlockchain().contains(pivotBlockHeader.getHash())) { + protocolContext.getBlockchain().rewindToBlock(pivotBlockHeader.getHash()); + } else { + return true; + } + } + return !worldStateStorage.isWorldStateAvailable( + pivotBlockHeader.getStateRoot(), pivotBlockHeader.getBlockHash()); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastDownloaderFactory.java index b7e2bbf4a76..137488ebdbb 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastDownloaderFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastDownloaderFactory.java @@ -80,13 +80,16 @@ public static Optional> create( final FastSyncState fastSyncState = fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); - if (fastSyncState.getPivotBlockHeader().isEmpty() + + if (!syncState.isResyncNeeded() + && fastSyncState.getPivotBlockHeader().isEmpty() && protocolContext.getBlockchain().getChainHeadBlockNumber() != BlockHeader.GENESIS_BLOCK_NUMBER) { LOG.info( "Fast sync was requested, but cannot be enabled because the local blockchain is not empty."); return Optional.empty(); } + if (worldStateStorage instanceof BonsaiWorldStateKeyValueStorage) { worldStateStorage.clearFlatDatabase(); } else { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java index e977eb736c2..184c4aace80 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync; +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; @@ -75,7 +76,13 @@ public static Optional> createSnapDownloader( final FastSyncState fastSyncState = fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); - if (fastSyncState.getPivotBlockHeader().isEmpty() + + if (syncState.isResyncNeeded()) { + snapContext.clear(); + syncState + .getAccountToRepair() + .ifPresent(address -> snapContext.addInconsistentAccount(Hash.hash(address))); + } else if (fastSyncState.getPivotBlockHeader().isEmpty() && protocolContext.getBlockchain().getChainHeadBlockNumber() != BlockHeader.GENESIS_BLOCK_NUMBER) { LOG.info( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapPersistedContext.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapPersistedContext.java index 905ce6d54a9..ad430c61a1c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapPersistedContext.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapPersistedContext.java @@ -41,7 +41,7 @@ public class SnapPersistedContext { private final GenericKeyValueStorageFacade accountRangeToDownload; - private final GenericKeyValueStorageFacade inconsistentAccounts; + private final GenericKeyValueStorageFacade healContext; public SnapPersistedContext(final StorageProvider storageProvider) { this.accountRangeToDownload = @@ -61,7 +61,7 @@ public byte[] toBytes(final AccountRangeDataRequest value) { }, storageProvider.getStorageBySegmentIdentifier( KeyValueSegmentIdentifier.SNAPSYNC_MISSING_ACCOUNT_RANGE)); - this.inconsistentAccounts = + this.healContext = new GenericKeyValueStorageFacade<>( BigInteger::toByteArray, new ValueConvertor<>() { @@ -95,25 +95,25 @@ public void updatePersistedTasks(final List accountRa public void addInconsistentAccount(final Bytes inconsistentAccount) { final BigInteger index = - inconsistentAccounts + healContext .get(SNAP_INCONSISTENT_ACCOUNT_INDEX) .map(bytes -> new BigInteger(bytes.toArrayUnsafe()).add(BigInteger.ONE)) .orElse(BigInteger.ZERO); - inconsistentAccounts.putAll( + healContext.putAll( keyValueStorageTransaction -> { keyValueStorageTransaction.put(SNAP_INCONSISTENT_ACCOUNT_INDEX, index.toByteArray()); keyValueStorageTransaction.put(index.toByteArray(), inconsistentAccount.toArrayUnsafe()); }); } - public List getPersistedTasks() { + public List getCurrentAccountRange() { return accountRangeToDownload .streamValuesFromKeysThat(bytes -> true) .collect(Collectors.toList()); } public HashSet getInconsistentAccounts() { - return inconsistentAccounts + return healContext .streamValuesFromKeysThat(notEqualsTo(SNAP_INCONSISTENT_ACCOUNT_INDEX)) .collect(Collectors.toCollection(HashSet::new)); } @@ -124,12 +124,12 @@ public void clearAccountRangeTasks() { public void clear() { accountRangeToDownload.clear(); - inconsistentAccounts.clear(); + healContext.clear(); } public void close() throws IOException { accountRangeToDownload.close(); - inconsistentAccounts.close(); + healContext.close(); } private Predicate notEqualsTo(final byte[] name) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadState.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadState.java index 9a5867275d5..f4f0c778391 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadState.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadState.java @@ -198,6 +198,7 @@ public synchronized void startHeal() { public synchronized void reloadHeal() { worldStateStorage.clearFlatDatabase(); + worldStateStorage.clearTrieLog(); pendingTrieNodeRequests.clear(); pendingCodeRequests.clear(); snapSyncState.setHealStatus(false); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloader.java index 25c999185f4..97a7cd72b74 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloader.java @@ -147,21 +147,24 @@ public CompletableFuture run( final Map ranges = RangeManager.generateAllRanges(16); snapsyncMetricsManager.initRange(ranges); - final List persistedTasks = snapContext.getPersistedTasks(); + final List currentAccountRange = + snapContext.getCurrentAccountRange(); final HashSet inconsistentAccounts = snapContext.getInconsistentAccounts(); - if (!persistedTasks.isEmpty()) { // continue to download worldstate ranges + if (!currentAccountRange.isEmpty()) { // continue to download worldstate ranges newDownloadState.setInconsistentAccounts(inconsistentAccounts); snapContext - .getPersistedTasks() + .getCurrentAccountRange() .forEach( snapDataRequest -> { snapsyncMetricsManager.notifyStateDownloaded( snapDataRequest.getStartKeyHash(), snapDataRequest.getEndKeyHash()); newDownloadState.enqueueRequest(snapDataRequest); }); - } else if (!inconsistentAccounts.isEmpty()) { // restart only the heal step + } else if (!snapContext.getInconsistentAccounts().isEmpty()) { // restart only the heal step snapSyncState.setHealStatus(true); + worldStateStorage.clearFlatDatabase(); + worldStateStorage.clearTrieLog(); newDownloadState.setInconsistentAccounts(inconsistentAccounts); newDownloadState.enqueueRequest( SnapDataRequest.createAccountTrieNodeDataRequest( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/state/SyncState.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/state/SyncState.java index 026453c1bee..a13910bb4e6 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/state/SyncState.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/state/SyncState.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint.Checkpoint; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloadStatus; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; +import org.hyperledger.besu.plugin.data.Address; import org.hyperledger.besu.plugin.data.SyncStatus; import org.hyperledger.besu.plugin.services.BesuEvents.InitialSyncCompletionListener; import org.hyperledger.besu.plugin.services.BesuEvents.SyncStatusListener; @@ -58,6 +59,10 @@ public class SyncState { private final Optional checkpoint; private volatile boolean isInitialSyncPhaseDone; + private volatile boolean isResyncNeeded; + + private Optional
maybeAccountToRepair; + public SyncState(final Blockchain blockchain, final EthPeers ethPeers) { this(blockchain, ethPeers, false, Optional.empty()); } @@ -311,12 +316,34 @@ public Optional getCheckpoint() { return checkpoint; } + public boolean isInitialSyncPhaseDone() { + return isInitialSyncPhaseDone; + } + public void markInitialSyncPhaseAsDone() { isInitialSyncPhaseDone = true; + isResyncNeeded = false; completionListenerSubscribers.forEach(InitialSyncCompletionListener::onInitialSyncCompleted); } - public boolean isInitialSyncPhaseDone() { - return isInitialSyncPhaseDone; + public boolean isResyncNeeded() { + return isResyncNeeded; + } + + public void markResyncNeeded() { + isResyncNeeded = true; + } + + public Optional
getAccountToRepair() { + return maybeAccountToRepair; + } + + public void markAccountToRepair(final Optional
address) { + maybeAccountToRepair = address; + } + + public void markInitialSyncRestart() { + isInitialSyncPhaseDone = false; + completionListenerSubscribers.forEach(InitialSyncCompletionListener::onInitialSyncRestart); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinder.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinder.java deleted file mode 100644 index 75f36092eaa..00000000000 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinder.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * 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.worldstate; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.eth.EthProtocolVersion; -import org.hyperledger.besu.ethereum.eth.manager.EthContext; -import org.hyperledger.besu.ethereum.eth.manager.snap.RetryingGetTrieNodeFromPeerTask; -import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; -import org.hyperledger.besu.ethereum.eth.manager.task.RetryingGetNodeDataFromPeerTask; -import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager; -import org.hyperledger.besu.ethereum.trie.CompactEncoding; -import org.hyperledger.besu.ethereum.worldstate.PeerTrieNodeFinder; -import org.hyperledger.besu.plugin.services.MetricsSystem; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** This class is used to retrieve missing nodes in the trie by querying the peers */ -public class WorldStatePeerTrieNodeFinder implements PeerTrieNodeFinder { - - private static final Logger LOG = LoggerFactory.getLogger(WorldStatePeerTrieNodeFinder.class); - - private final Cache foundNodes = - CacheBuilder.newBuilder().maximumSize(10_000).expireAfterWrite(5, TimeUnit.MINUTES).build(); - - private static final long TIMEOUT_SECONDS = 1; - - final ProtocolManager protocolManager; - final EthContext ethContext; - final Blockchain blockchain; - final MetricsSystem metricsSystem; - - public WorldStatePeerTrieNodeFinder( - final EthContext ethContext, - final ProtocolManager protocolManager, - final Blockchain blockchain, - final MetricsSystem metricsSystem) { - this.ethContext = ethContext; - this.protocolManager = protocolManager; - this.blockchain = blockchain; - this.metricsSystem = metricsSystem; - } - - @Override - public Optional getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { - Optional cachedValue = Optional.ofNullable(foundNodes.getIfPresent(nodeHash)); - if (cachedValue.isPresent()) { - return cachedValue; - } - final Optional response = - findByGetNodeData(Hash.wrap(nodeHash)) - .or(() -> findByGetTrieNodeData(Hash.wrap(nodeHash), Optional.empty(), location)); - response.ifPresent( - bytes -> { - LOG.debug( - "Fixed missing account state trie node for location {} and hash {}", - location, - nodeHash); - foundNodes.put(nodeHash, bytes); - }); - return response; - } - - @Override - public Optional getAccountStorageTrieNode( - final Hash accountHash, final Bytes location, final Bytes32 nodeHash) { - Optional cachedValue = Optional.ofNullable(foundNodes.getIfPresent(nodeHash)); - if (cachedValue.isPresent()) { - return cachedValue; - } - final Optional response = - // Call findByGetNodeData only if protocol version < eth 67 - (protocolManager.getHighestProtocolVersion() < EthProtocolVersion.V67 - ? findByGetNodeData(Hash.wrap(nodeHash)) - : Optional.empty()) - .or( - () -> - findByGetTrieNodeData(Hash.wrap(nodeHash), Optional.of(accountHash), location)); - response.ifPresent( - bytes -> { - LOG.debug( - "Fixed missing storage state trie node for location {} and hash {}", - location, - nodeHash); - foundNodes.put(nodeHash, bytes); - }); - return response; - } - - @VisibleForTesting - public Optional findByGetNodeData(final Hash nodeHash) { - if (protocolManager.getHighestProtocolVersion() >= EthProtocolVersion.V67) { - return Optional.empty(); - } - final BlockHeader chainHead = blockchain.getChainHeadHeader(); - final RetryingGetNodeDataFromPeerTask retryingGetNodeDataFromPeerTask = - RetryingGetNodeDataFromPeerTask.forHashes( - ethContext, List.of(nodeHash), chainHead.getNumber(), metricsSystem); - try { - final Map response = - retryingGetNodeDataFromPeerTask.run().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - if (response.containsKey(nodeHash)) { - LOG.debug("Found node {} with getNodeData request", nodeHash); - return Optional.of(response.get(nodeHash)); - } else { - LOG.debug("Found invalid node {} with getNodeData request", nodeHash); - } - } catch (InterruptedException | ExecutionException | TimeoutException e) { - LOG.debug("Error when trying to find node {} with getNodeData request", nodeHash); - } - return Optional.empty(); - } - - @VisibleForTesting - public Optional findByGetTrieNodeData( - final Hash nodeHash, final Optional accountHash, final Bytes location) { - final BlockHeader chainHead = blockchain.getChainHeadHeader(); - final Map> request = new HashMap<>(); - if (accountHash.isPresent()) { - request.put(accountHash.get(), List.of(CompactEncoding.encode(location))); - } else { - request.put(CompactEncoding.encode(location), new ArrayList<>()); - } - final Bytes path = CompactEncoding.encode(location); - final EthTask> getTrieNodeFromPeerTask = - RetryingGetTrieNodeFromPeerTask.forTrieNodes(ethContext, request, chainHead, metricsSystem); - try { - final Map response = - getTrieNodeFromPeerTask.run().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - final Bytes nodeValue = - response.get(Bytes.concatenate(accountHash.map(Bytes::wrap).orElse(Bytes.EMPTY), path)); - if (nodeValue != null && Hash.hash(nodeValue).equals(nodeHash)) { - LOG.debug("Found node {} with getTrieNode request", nodeHash); - return Optional.of(nodeValue); - } else { - LOG.debug("Found invalid node {} with getTrieNode request", nodeHash); - } - } catch (InterruptedException | ExecutionException | TimeoutException e) { - LOG.debug("Error when trying to find node {} with getTrieNode request", nodeHash); - } - return Optional.empty(); - } -} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageHandler.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageHandler.java index 003183a86c8..a346b07e414 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageHandler.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageHandler.java @@ -25,12 +25,14 @@ import java.time.Duration; import java.time.Instant; +import java.util.concurrent.atomic.AtomicBoolean; class NewPooledTransactionHashesMessageHandler implements EthMessages.MessageCallback { private final NewPooledTransactionHashesMessageProcessor transactionsMessageProcessor; private final EthScheduler scheduler; private final Duration txMsgKeepAlive; + private final AtomicBoolean isEnabled = new AtomicBoolean(true); public NewPooledTransactionHashesMessageHandler( final EthScheduler scheduler, @@ -47,9 +49,23 @@ public void exec(final EthMessage message) { final NewPooledTransactionHashesMessage transactionsMessage = NewPooledTransactionHashesMessage.readFrom(message.getData(), capability); final Instant startedAt = now(); - scheduler.scheduleTxWorkerTask( - () -> - transactionsMessageProcessor.processNewPooledTransactionHashesMessage( - message.getPeer(), transactionsMessage, startedAt, txMsgKeepAlive)); + if (isEnabled.get()) { + scheduler.scheduleTxWorkerTask( + () -> + transactionsMessageProcessor.processNewPooledTransactionHashesMessage( + message.getPeer(), transactionsMessage, startedAt, txMsgKeepAlive)); + } + } + + public void setEnabled() { + isEnabled.set(true); + } + + public void setDisabled() { + isEnabled.set(false); + } + + public boolean isEnabled() { + return isEnabled.get(); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTracker.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTracker.java index 07326672d03..2815a426b6e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTracker.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTracker.java @@ -33,6 +33,11 @@ public class PeerTransactionTracker implements EthPeer.DisconnectCallback { private final Map> seenTransactions = new ConcurrentHashMap<>(); private final Map> transactionsToSend = new ConcurrentHashMap<>(); + public void reset() { + seenTransactions.clear(); + transactionsToSend.clear(); + } + public synchronized void markTransactionsAsSeen( final EthPeer peer, final Collection transactions) { markTransactionHashesAsSeen(peer, toHashList(transactions)); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java index cb34f3bc972..70f657bf5ba 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java @@ -54,6 +54,7 @@ import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -79,6 +80,7 @@ public class TransactionPool implements BlockAddedObserver { private final MiningParameters miningParameters; private final LabelledMetric duplicateTransactionCounter; private final TransactionPoolConfiguration configuration; + private final AtomicBoolean isPoolEnabled = new AtomicBoolean(true); public TransactionPool( final AbstractPendingTransactionsSorter pendingTransactions, @@ -110,6 +112,10 @@ void handleConnect(final EthPeer peer) { transactionBroadcaster.relayTransactionPoolTo(peer); } + public void reset() { + pendingTransactions.reset(); + } + public ValidationResult addLocalTransaction( final Transaction transaction) { final ValidationResultAndAccount validationResult = validateLocalTransaction(transaction); @@ -222,9 +228,11 @@ public void unsubscribeDroppedTransactions(final long id) { @Override public void onBlockAdded(final BlockAddedEvent event) { LOG.trace("Block added event {}", event); - event.getAddedTransactions().forEach(pendingTransactions::transactionAddedToBlock); - pendingTransactions.manageBlockAdded(event.getBlock()); - reAddTransactions(event.getRemovedTransactions()); + if (isPoolEnabled.get()) { + event.getAddedTransactions().forEach(pendingTransactions::transactionAddedToBlock); + pendingTransactions.manageBlockAdded(event.getBlock()); + reAddTransactions(event.getRemovedTransactions()); + } } private void reAddTransactions(final List reAddTransactions) { @@ -316,10 +324,18 @@ && strictReplayProtectionShouldBeEnforceLocally(chainHeadBlockHeader) "EIP-1559 transaction are not allowed yet"); } - try (var worldState = + try (final var worldState = protocolContext .getWorldStateArchive() - .getMutable(chainHeadBlockHeader.getStateRoot(), chainHeadBlockHeader.getHash(), false) + .getMutable( + chainHeadBlockHeader.getStateRoot(), chainHeadBlockHeader.getBlockHash(), false) + .map( + ws -> { + if (!ws.isPersistable()) { + return ws.copy(); + } + return ws; + }) .orElseThrow()) { final Account senderAccount = worldState.get(transaction.getSender()); return new ValidationResultAndAccount( @@ -426,4 +442,16 @@ static ValidationResultAndAccount invalid(final TransactionInvalidReason reason) return new ValidationResultAndAccount(ValidationResult.invalid(reason)); } } + + public void setEnabled() { + isPoolEnabled.set(true); + } + + public void setDisabled() { + isPoolEnabled.set(false); + } + + public boolean isEnabled() { + return isPoolEnabled.get(); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java index b2aea31c7a1..0e22531d55b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.plugin.services.BesuEvents; import org.hyperledger.besu.plugin.services.MetricsSystem; import java.time.Clock; @@ -116,31 +117,46 @@ static TransactionPool createTransactionPool( metricsSystem), transactionPoolConfiguration.getTxMessageKeepAliveSeconds()); - if (syncState.isInitialSyncPhaseDone()) { - enableTransactionPool( - protocolContext, - ethContext, - transactionTracker, - transactionPool, - transactionsMessageHandler, - pooledTransactionsMessageHandler); - } else { - syncState.subscribeCompletionReached( - () -> { - enableTransactionPool( - protocolContext, - ethContext, - transactionTracker, - transactionPool, - transactionsMessageHandler, - pooledTransactionsMessageHandler); - }); + subscribeTransactionHandlers( + protocolContext, + ethContext, + transactionTracker, + transactionPool, + transactionsMessageHandler, + pooledTransactionsMessageHandler); + + if (!syncState.isInitialSyncPhaseDone()) { + LOG.info("Disabling transaction handling during initial sync"); + pooledTransactionsMessageHandler.setDisabled(); + transactionsMessageHandler.setDisabled(); + transactionPool.setDisabled(); } + syncState.subscribeCompletionReached( + new BesuEvents.InitialSyncCompletionListener() { + @Override + public void onInitialSyncCompleted() { + LOG.info("Enabling transaction handling following initial sync"); + transactionPool.reset(); + transactionTracker.reset(); + transactionPool.setEnabled(); + transactionsMessageHandler.setEnabled(); + pooledTransactionsMessageHandler.setEnabled(); + } + + @Override + public void onInitialSyncRestart() { + LOG.info("Disabling transaction handling during re-sync"); + pooledTransactionsMessageHandler.setDisabled(); + transactionsMessageHandler.setDisabled(); + transactionPool.setDisabled(); + } + }); + return transactionPool; } - private static void enableTransactionPool( + private static void subscribeTransactionHandlers( final ProtocolContext protocolContext, final EthContext ethContext, final PeerTransactionTracker transactionTracker, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageHandler.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageHandler.java index 372778fc2c2..8193de1e0ce 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageHandler.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageHandler.java @@ -23,12 +23,14 @@ import java.time.Duration; import java.time.Instant; +import java.util.concurrent.atomic.AtomicBoolean; class TransactionsMessageHandler implements EthMessages.MessageCallback { private final TransactionsMessageProcessor transactionsMessageProcessor; private final EthScheduler scheduler; private final Duration txMsgKeepAlive; + private final AtomicBoolean isEnabled = new AtomicBoolean(true); public TransactionsMessageHandler( final EthScheduler scheduler, @@ -43,9 +45,23 @@ public TransactionsMessageHandler( public void exec(final EthMessage message) { final TransactionsMessage transactionsMessage = TransactionsMessage.readFrom(message.getData()); final Instant startedAt = now(); - scheduler.scheduleTxWorkerTask( - () -> - transactionsMessageProcessor.processTransactionsMessage( - message.getPeer(), transactionsMessage, startedAt, txMsgKeepAlive)); + if (isEnabled.get()) { + scheduler.scheduleTxWorkerTask( + () -> + transactionsMessageProcessor.processTransactionsMessage( + message.getPeer(), transactionsMessage, startedAt, txMsgKeepAlive)); + } + } + + public void setDisabled() { + isEnabled.set(false); + } + + public void setEnabled() { + isEnabled.set(true); + } + + public boolean isEnabled() { + return isEnabled.get(); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java index 59fde3c4795..2afbe6262dc 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java @@ -134,6 +134,12 @@ public AbstractPendingTransactionsSorter( pendingTransactions::size); } + public void reset() { + pendingTransactions.clear(); + transactionsBySender.clear(); + lowestInvalidKnownNonceCache.reset(); + } + public void evictOldTransactions() { final Instant removeTransactionsBefore = clock.instant().minus(poolConfig.getPendingTxRetentionPeriod(), ChronoUnit.HOURS); @@ -402,7 +408,11 @@ private TransactionAddedStatus addTransaction( LOG, "Transaction {} not added because nonce too far in the future for sender {}", transaction::toTraceLog, - maybeSenderAccount::toString); + () -> + maybeSenderAccount + .map(Account::getAddress) + .map(Address::toString) + .orElse("unknown")); return NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsSorter.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsSorter.java index f5363815393..32708f26481 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsSorter.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsSorter.java @@ -93,6 +93,13 @@ public BaseFeePendingTransactionsSorter( .thenComparing(PendingTransaction::getSequence) .reversed()); + @Override + public void reset() { + super.reset(); + prioritizedTransactionsStaticRange.clear(); + prioritizedTransactionsDynamicRange.clear(); + } + @Override public void manageBlockAdded(final Block block) { block.getHeader().getBaseFee().ifPresent(this::updateBaseFee); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsSorter.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsSorter.java index 51743281fc4..1d9ad3184f1 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsSorter.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsSorter.java @@ -52,6 +52,12 @@ public GasPricePendingTransactionsSorter( super(poolConfig, clock, metricsSystem, chainHeadHeaderSupplier); } + @Override + public void reset() { + super.reset(); + prioritizedTransactions.clear(); + } + @Override public void manageBlockAdded(final Block block) { // nothing to do diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LowestInvalidNonceCache.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LowestInvalidNonceCache.java index 4e084889675..ca4c3902047 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LowestInvalidNonceCache.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LowestInvalidNonceCache.java @@ -126,6 +126,11 @@ public String toString() { + '}'; } + public void reset() { + lowestInvalidKnownNonceBySender.clear(); + evictionOrder.clear(); + } + private static class InvalidNonceStatus implements Comparable { final Address address; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java index 17eb2e45a92..d9df79a402c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -66,6 +67,7 @@ public class BackwardSyncStepTest { private final DeterministicEthScheduler ethScheduler = new DeterministicEthScheduler(); + private MutableBlockchain localBlockchain; private MutableBlockchain remoteBlockchain; private RespondingEthPeer peer; GenericKeyValueStorageFacade headersStorage; @@ -91,7 +93,7 @@ public void setup() { Block genesisBlock = blockDataGenerator.genesisBlock(); remoteBlockchain = createInMemoryBlockchain(genesisBlock); - MutableBlockchain localBlockchain = createInMemoryBlockchain(genesisBlock); + localBlockchain = spy(createInMemoryBlockchain(genesisBlock)); for (int i = 1; i <= REMOTE_HEIGHT; i++) { final BlockDataGenerator.BlockOptions options = @@ -171,7 +173,10 @@ public void shouldNotRequestHeaderIfAlreadyPresent() throws Exception { final CompletableFuture> future = step.requestHeaders(lookingForBlock.getHeader().getHash()); - assertThat(future.get().isEmpty()).isTrue(); + verify(localBlockchain).getBlockHeader(lookingForBlock.getHash()); + verify(context, never()).getEthContext(); + final BlockHeader blockHeader = future.get().get(0); + assertThat(blockHeader).isEqualTo(lookingForBlock.getHeader()); } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinderTest.java deleted file mode 100644 index 502774a992c..00000000000 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinderTest.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * 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.worldstate; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.eth.EthProtocolVersion; -import org.hyperledger.besu.ethereum.eth.manager.EthPeers; -import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; -import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; -import org.hyperledger.besu.ethereum.eth.manager.PeerRequest; -import org.hyperledger.besu.ethereum.eth.manager.RequestManager; -import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; -import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; -import org.hyperledger.besu.ethereum.eth.manager.task.SnapProtocolManagerTestUtil; -import org.hyperledger.besu.ethereum.eth.messages.EthPV63; -import org.hyperledger.besu.ethereum.eth.messages.NodeDataMessage; -import org.hyperledger.besu.ethereum.eth.messages.snap.SnapV1; -import org.hyperledger.besu.ethereum.eth.messages.snap.TrieNodesMessage; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; - -import java.math.BigInteger; -import java.util.List; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.assertj.core.api.Assertions; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -@SuppressWarnings({"FieldCanBeLocal", "unused"}) -@RunWith(MockitoJUnitRunner.class) -public class WorldStatePeerTrieNodeFinderTest { - - WorldStatePeerTrieNodeFinder worldStatePeerTrieNodeFinder; - - private final BlockHeaderTestFixture blockHeaderBuilder = new BlockHeaderTestFixture(); - - @Mock private Blockchain blockchain; - private EthProtocolManager ethProtocolManager; - private SnapProtocolManager snapProtocolManager; - private EthPeers ethPeers; - private final PeerRequest peerRequest = mock(PeerRequest.class); - private final RequestManager.ResponseStream responseStream = - mock(RequestManager.ResponseStream.class); - - @Before - public void setup() throws Exception { - ethProtocolManager = spy(EthProtocolManagerTestUtil.create()); - ethPeers = ethProtocolManager.ethContext().getEthPeers(); - snapProtocolManager = SnapProtocolManagerTestUtil.create(ethPeers); - worldStatePeerTrieNodeFinder = - spy( - new WorldStatePeerTrieNodeFinder( - ethProtocolManager.ethContext(), - ethProtocolManager, - blockchain, - new NoOpMetricsSystem())); - } - - private RespondingEthPeer.Responder respondToGetNodeDataRequest( - final RespondingEthPeer peer, final Bytes32 nodeValue) { - return RespondingEthPeer.targetedResponder( - (cap, msg) -> { - if (msg.getCode() != EthPV63.GET_NODE_DATA) { - return false; - } - return true; - }, - (cap, msg) -> NodeDataMessage.create(List.of(nodeValue))); - } - - private RespondingEthPeer.Responder respondToGetTrieNodeRequest( - final RespondingEthPeer peer, final Bytes32 nodeValue) { - return RespondingEthPeer.targetedResponder( - (cap, msg) -> { - if (msg.getCode() != SnapV1.GET_TRIE_NODES) { - return false; - } - return true; - }, - (cap, msg) -> TrieNodesMessage.create(Optional.of(BigInteger.ONE), List.of(nodeValue))); - } - - @Test - public void getAccountStateTrieNodeShouldReturnValueFromGetNodeDataRequest() { - - BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); - when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); - when(ethProtocolManager.getHighestProtocolVersion()).thenReturn(EthProtocolVersion.V66); - final Bytes32 nodeValue = Bytes32.random(); - final Bytes32 nodeHash = Hash.hash(nodeValue); - - final RespondingEthPeer targetPeer = - EthProtocolManagerTestUtil.createPeer(ethProtocolManager, blockHeader.getNumber()); - - var response = - new Object() { - Optional accountStateTrieNode = Optional.empty(); - }; - - new Thread( - () -> - targetPeer.respondWhileOtherThreadsWork( - respondToGetNodeDataRequest(targetPeer, nodeValue), - () -> response.accountStateTrieNode.isEmpty())) - .start(); - - response.accountStateTrieNode = - worldStatePeerTrieNodeFinder.getAccountStateTrieNode(Bytes.EMPTY, nodeHash); - - Assertions.assertThat(response.accountStateTrieNode).contains(nodeValue); - } - - @Test - public void getAccountStateTrieNodeShouldReturnValueFromGetTrieNodeRequest() { - - final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); - when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); - when(ethProtocolManager.getHighestProtocolVersion()).thenReturn(EthProtocolVersion.V66); - - final Bytes32 nodeValue = Bytes32.random(); - final Bytes32 nodeHash = Hash.hash(nodeValue); - - final RespondingEthPeer targetPeer = - SnapProtocolManagerTestUtil.createPeer( - ethProtocolManager, snapProtocolManager, blockHeader.getNumber()); - - var response = - new Object() { - Optional accountStateTrieNode = Optional.empty(); - }; - - new Thread( - () -> - targetPeer.respondWhileOtherThreadsWork( - respondToGetTrieNodeRequest(targetPeer, nodeValue), - () -> response.accountStateTrieNode.isEmpty())) - .start(); - - response.accountStateTrieNode = - worldStatePeerTrieNodeFinder.getAccountStateTrieNode(Bytes.EMPTY, nodeHash); - Assertions.assertThat(response.accountStateTrieNode).contains(nodeValue); - } - - @Test - public void getAccountStateTrieNodeShouldReturnEmptyWhenFoundNothing() { - - final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); - when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); - when(ethProtocolManager.getHighestProtocolVersion()).thenReturn(EthProtocolVersion.V66); - - final Bytes32 nodeValue = Bytes32.random(); - final Bytes32 nodeHash = Hash.hash(nodeValue); - - var response = - new Object() { - Optional accountStateTrieNode = Optional.empty(); - }; - - response.accountStateTrieNode = - worldStatePeerTrieNodeFinder.getAccountStateTrieNode(Bytes.EMPTY, nodeHash); - Assertions.assertThat(response.accountStateTrieNode).isEmpty(); - } - - @Test - public void getAccountStorageTrieNodeShouldReturnValueFromGetNodeDataRequest() { - - BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); - when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); - when(ethProtocolManager.getHighestProtocolVersion()).thenReturn(EthProtocolVersion.V66); - - final Hash accountHash = Hash.wrap(Bytes32.random()); - final Bytes32 nodeValue = Bytes32.random(); - final Bytes32 nodeHash = Hash.hash(nodeValue); - - final RespondingEthPeer targetPeer = - EthProtocolManagerTestUtil.createPeer(ethProtocolManager, blockHeader.getNumber()); - - var response = - new Object() { - Optional accountStateTrieNode = Optional.empty(); - }; - - new Thread( - () -> - targetPeer.respondWhileOtherThreadsWork( - respondToGetNodeDataRequest(targetPeer, nodeValue), - () -> response.accountStateTrieNode.isEmpty())) - .start(); - - response.accountStateTrieNode = - worldStatePeerTrieNodeFinder.getAccountStorageTrieNode(accountHash, Bytes.EMPTY, nodeHash); - - Assertions.assertThat(response.accountStateTrieNode).contains(nodeValue); - } - - @Test - public void getAccountStorageTrieNodeShouldReturnValueFromGetTrieNodeRequest() { - - final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); - when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); - when(ethProtocolManager.getHighestProtocolVersion()).thenReturn(EthProtocolVersion.V66); - - final Hash accountHash = Hash.wrap(Bytes32.random()); - final Bytes32 nodeValue = Bytes32.random(); - final Bytes32 nodeHash = Hash.hash(nodeValue); - - final RespondingEthPeer targetPeer = - SnapProtocolManagerTestUtil.createPeer( - ethProtocolManager, snapProtocolManager, blockHeader.getNumber()); - - var response = - new Object() { - Optional accountStateTrieNode = Optional.empty(); - }; - - new Thread( - () -> - targetPeer.respondWhileOtherThreadsWork( - respondToGetTrieNodeRequest(targetPeer, nodeValue), - () -> response.accountStateTrieNode.isEmpty())) - .start(); - - response.accountStateTrieNode = - worldStatePeerTrieNodeFinder.getAccountStorageTrieNode(accountHash, Bytes.EMPTY, nodeHash); - Assertions.assertThat(response.accountStateTrieNode).contains(nodeValue); - } - - @Test - public void getAccountStorageTrieNodeShouldReturnEmptyWhenFoundNothing() { - - final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); - when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); - - final Hash accountHash = Hash.wrap(Bytes32.random()); - final Bytes32 nodeValue = Bytes32.random(); - final Bytes32 nodeHash = Hash.hash(nodeValue); - - var response = - new Object() { - Optional accountStateTrieNode = Optional.empty(); - }; - - response.accountStateTrieNode = - worldStatePeerTrieNodeFinder.getAccountStorageTrieNode(accountHash, Bytes.EMPTY, nodeHash); - Assertions.assertThat(response.accountStateTrieNode).isEmpty(); - } - - @Test - public void getNodeDataRequestShouldBeCalled_IfProtocolIsEth66() { - final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); - when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); - when(ethProtocolManager.getHighestProtocolVersion()).thenReturn(EthProtocolVersion.V66); - - worldStatePeerTrieNodeFinder.getAccountStorageTrieNode( - Hash.wrap(Bytes32.random()), Bytes.EMPTY, Hash.wrap(Bytes32.random())); - - // assert findByGetNodeData is called once - verify(worldStatePeerTrieNodeFinder, times(1)).findByGetNodeData(any()); - - // assert findByGetTrieNodeData is called once - verify(worldStatePeerTrieNodeFinder, times(1)).findByGetTrieNodeData(any(), any(), any()); - } - - @Test - public void getNodeDataRequestShouldNotBeCalled_IfProtocolIsEth67() { - final BlockHeader blockHeader = blockHeaderBuilder.number(1000).buildHeader(); - when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); - when(ethProtocolManager.getHighestProtocolVersion()).thenReturn(EthProtocolVersion.V67); - - worldStatePeerTrieNodeFinder.getAccountStorageTrieNode( - Hash.wrap(Bytes32.random()), Bytes.EMPTY, Hash.wrap(Bytes32.random())); - - // assert findByGetNodeData is never called - verify(worldStatePeerTrieNodeFinder, never()).findByGetNodeData(any()); - - // assert findByGetTrieNodeData is called once - verify(worldStatePeerTrieNodeFinder, times(1)).findByGetTrieNodeData(any(), any(), any()); - } -} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java index 9c45d60e604..cd17fd07adf 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java @@ -20,9 +20,7 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Hash; @@ -37,13 +35,11 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; -import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.testutil.TestClock; @@ -52,6 +48,7 @@ import java.util.Collections; import java.util.Optional; +import org.assertj.core.api.Condition; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -98,43 +95,6 @@ public void setup() { when(ethContext.getScheduler()).thenReturn(ethScheduler); } - @Test - public void disconnectNotInvokedBeforeInitialSyncIsDone() { - setupInitialSyncPhase(true); - final RespondingEthPeer ethPeer = - RespondingEthPeer.builder().ethProtocolManager(ethProtocolManager).build(); - assertThat(ethPeer.getEthPeer()).isNotNull(); - assertThat(ethPeer.getEthPeer().isDisconnected()).isFalse(); - ethPeer.disconnect(DisconnectMessage.DisconnectReason.CLIENT_QUITTING); - verifyNoInteractions(peerTransactionTracker); - } - - @Test - public void disconnectInvokedAfterInitialSyncIsDone() { - setupInitialSyncPhase(true); - final RespondingEthPeer ethPeer = - RespondingEthPeer.builder().ethProtocolManager(ethProtocolManager).build(); - assertThat(ethPeer.getEthPeer()).isNotNull(); - assertThat(ethPeer.getEthPeer().isDisconnected()).isFalse(); - - syncState.markInitialSyncPhaseAsDone(); - - ethPeer.disconnect(DisconnectMessage.DisconnectReason.CLIENT_QUITTING); - verify(peerTransactionTracker, times(1)).onDisconnect(ethPeer.getEthPeer()); - } - - @Test - public void disconnectInvokedIfNoInitialSync() { - setupInitialSyncPhase(false); - final RespondingEthPeer ethPeer = - RespondingEthPeer.builder().ethProtocolManager(ethProtocolManager).build(); - assertThat(ethPeer.getEthPeer()).isNotNull(); - assertThat(ethPeer.getEthPeer().isDisconnected()).isFalse(); - - ethPeer.disconnect(DisconnectMessage.DisconnectReason.CLIENT_QUITTING); - verify(peerTransactionTracker, times(1)).onDisconnect(ethPeer.getEthPeer()); - } - @Test public void notRegisteredToBlockAddedEventBeforeInitialSyncIsDone() { setupInitialSyncPhase(true); @@ -142,7 +102,7 @@ public void notRegisteredToBlockAddedEventBeforeInitialSyncIsDone() { ArgumentCaptor.forClass(BlockAddedObserver.class); verify(blockchain, atLeastOnce()).observeBlockAdded(blockAddedListeners.capture()); - assertThat(blockAddedListeners.getAllValues()).doesNotContain(pool); + assertThat(pool.isEnabled()).isFalse(); } @Test @@ -155,6 +115,7 @@ public void registeredToBlockAddedEventAfterInitialSyncIsDone() { verify(blockchain, atLeastOnce()).observeBlockAdded(blockAddedListeners.capture()); assertThat(blockAddedListeners.getAllValues()).contains(pool); + assertThat(pool.isEnabled()).isTrue(); } @Test @@ -166,18 +127,31 @@ public void registeredToBlockAddedEventIfNoInitialSync() { verify(blockchain, atLeastOnce()).observeBlockAdded(blockAddedListeners.capture()); assertThat(blockAddedListeners.getAllValues()).contains(pool); + assertThat(pool.isEnabled()).isTrue(); } @Test - public void incomingTransactionMessageHandlersNotRegisteredBeforeInitialSyncIsDone() { + public void incomingTransactionMessageHandlersDisabledBeforeInitialSyncIsDone() { setupInitialSyncPhase(true); ArgumentCaptor messageHandlers = ArgumentCaptor.forClass(EthMessages.MessageCallback.class); - verify(ethMessages, atLeast(0)).subscribe(anyInt(), messageHandlers.capture()); + verify(ethMessages, atLeast(2)).subscribe(anyInt(), messageHandlers.capture()); assertThat(messageHandlers.getAllValues()) - .doesNotHaveAnyElementsOfTypes( - TransactionsMessageHandler.class, NewPooledTransactionHashesMessageHandler.class); + .haveAtLeastOne( + new Condition<>( + h -> + h instanceof NewPooledTransactionHashesMessageHandler + && !((NewPooledTransactionHashesMessageHandler) h).isEnabled(), + "pooled transaction hashes handler should be disabled")); + + assertThat(messageHandlers.getAllValues()) + .haveAtLeastOne( + new Condition<>( + h -> + h instanceof TransactionsMessageHandler + && !((TransactionsMessageHandler) h).isEnabled(), + "transaction messages handler should be disabled")); } @Test @@ -187,12 +161,23 @@ public void incomingTransactionMessageHandlersRegisteredAfterInitialSyncIsDone() ArgumentCaptor messageHandlers = ArgumentCaptor.forClass(EthMessages.MessageCallback.class); - verify(ethMessages, atLeast(0)).subscribe(anyInt(), messageHandlers.capture()); + verify(ethMessages, atLeast(2)).subscribe(anyInt(), messageHandlers.capture()); assertThat(messageHandlers.getAllValues()) - .hasAtLeastOneElementOfType(TransactionsMessageHandler.class); + .haveAtLeastOne( + new Condition<>( + h -> + h instanceof NewPooledTransactionHashesMessageHandler + && ((NewPooledTransactionHashesMessageHandler) h).isEnabled(), + "pooled transaction hashes handler should be enabled")); + assertThat(messageHandlers.getAllValues()) - .hasAtLeastOneElementOfType(NewPooledTransactionHashesMessageHandler.class); + .haveAtLeastOne( + new Condition<>( + h -> + h instanceof TransactionsMessageHandler + && ((TransactionsMessageHandler) h).isEnabled(), + "transaction messages handler should be enabled")); } @Test @@ -204,9 +189,20 @@ public void incomingTransactionMessageHandlersRegisteredIfNoInitialSync() { verify(ethMessages, atLeast(0)).subscribe(anyInt(), messageHandlers.capture()); assertThat(messageHandlers.getAllValues()) - .hasAtLeastOneElementOfType(TransactionsMessageHandler.class); + .haveAtLeastOne( + new Condition<>( + h -> + h instanceof NewPooledTransactionHashesMessageHandler + && ((NewPooledTransactionHashesMessageHandler) h).isEnabled(), + "pooled transaction hashes handler should be enabled")); + assertThat(messageHandlers.getAllValues()) - .hasAtLeastOneElementOfType(NewPooledTransactionHashesMessageHandler.class); + .haveAtLeastOne( + new Condition<>( + h -> + h instanceof TransactionsMessageHandler + && ((TransactionsMessageHandler) h).isEnabled(), + "transaction messages handler should be enabled")); } private void setupInitialSyncPhase(final boolean hasInitialSyncPhase) { diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/DummySynchronizer.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/DummySynchronizer.java index 1f94d49e246..cfce9e79133 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/DummySynchronizer.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/DummySynchronizer.java @@ -16,12 +16,15 @@ package org.hyperledger.besu.ethereum.retesteth; import org.hyperledger.besu.ethereum.core.Synchronizer; +import org.hyperledger.besu.plugin.data.Address; import org.hyperledger.besu.plugin.data.SyncStatus; import org.hyperledger.besu.plugin.services.BesuEvents; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import org.apache.tuweni.bytes.Bytes; + /** * Naive implementation of Synchronizer used by retesteth. Because retesteth is not implemented in * the test module, it has no access to mockito. This class provides a minimum implementation needed @@ -44,6 +47,17 @@ public Optional getSyncStatus() { return Optional.empty(); } + @Override + public boolean resyncWorldState() { + return false; + } + + @Override + public boolean healWorldState( + final Optional
maybeAccountToRepair, final Bytes location) { + return false; + } + @Override public long subscribeSyncStatus(final BesuEvents.SyncStatusListener listener) { return 0; diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrieException.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrieException.java index 3ad6d766e2a..517dec3413f 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrieException.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrieException.java @@ -14,6 +14,10 @@ */ package org.hyperledger.besu.ethereum.trie; +import org.hyperledger.besu.plugin.data.Address; + +import java.util.Optional; + import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -23,6 +27,7 @@ */ public class MerkleTrieException extends RuntimeException { + private Optional
maybeAddress; private Bytes32 hash; private Bytes location; @@ -34,12 +39,28 @@ public MerkleTrieException(final String message, final Bytes32 hash, final Bytes super(message); this.hash = hash; this.location = location; + this.maybeAddress = Optional.empty(); } public MerkleTrieException(final String message, final Exception cause) { super(message, cause); } + public MerkleTrieException( + final String message, + final Optional
maybeAddress, + final Bytes32 hash, + final Bytes location) { + super(message); + this.hash = hash; + this.location = location; + this.maybeAddress = maybeAddress; + } + + public Optional
getMaybeAddress() { + return maybeAddress; + } + public Bytes32 getHash() { return hash; } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java index 58afbd259af..a59a39ce309 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java @@ -32,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.DoubleSupplier; import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.google.common.collect.ImmutableSet; @@ -165,10 +166,28 @@ public void addCollector( } private void addCollectorUnchecked(final MetricCategory category, final Collector metric) { - metric.register(registry); - collectors - .computeIfAbsent(category, key -> Collections.newSetFromMap(new ConcurrentHashMap<>())) - .add(metric); + final Collection metrics = + this.collectors.computeIfAbsent( + category, key -> Collections.newSetFromMap(new ConcurrentHashMap<>())); + + final List newSamples = + metric.collect().stream() + .map(metricFamilySamples -> metricFamilySamples.name) + .collect(Collectors.toList()); + + metrics.stream() + .filter( + collector -> + collector.collect().stream() + .anyMatch(metricFamilySamples -> newSamples.contains(metricFamilySamples.name))) + .findFirst() + .ifPresent( + collector -> { + metrics.remove(collector); + registry.unregister(collector); + }); + + metrics.add(metric.register(registry)); } @Override diff --git a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java index 3c2454f397b..a1913b6574b 100644 --- a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java +++ b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java @@ -217,13 +217,13 @@ public void shouldCreateObservationFromGauge() { } @Test - public void shouldNotAllowDuplicateGaugeCreation() { - // Gauges have a reference to the source of their data so creating it twice will still only - // pull data from the first instance, possibly leaking memory and likely returning the wrong - // results. + public void shouldAllowDuplicateGaugeCreation() { + // When we are pushing the same gauge, the first one will be unregistered and the new one will + // be used metricsSystem.createGauge(JVM, "myValue", "Help", () -> 7.0); - assertThatThrownBy(() -> metricsSystem.createGauge(JVM, "myValue", "Help", () -> 7.0)) - .isInstanceOf(IllegalArgumentException.class); + metricsSystem.createGauge(JVM, "myValue", "Help", () -> 7.0); + assertThat(metricsSystem.streamObservations()) + .containsExactlyInAnyOrder(new Observation(JVM, "myValue", 7.0, emptyList())); } @Test diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 648fd64349d..4a31cb83470 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -66,7 +66,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'nBDCEeFH318uhGZEBmuTGOfYLI1+9tLDyjn/RDe5saI=' + knownHash = 'vFf6OIL506w5+DvYxs7btZcCnVpkuRw5WjzWUkrPeu4=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java index 79fa42d5fda..3e60d244c7d 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java @@ -253,5 +253,8 @@ interface InitialSyncCompletionListener { /** Emitted when initial sync finishes */ void onInitialSyncCompleted(); + + /** Emitted when initial sync restarts */ + void onInitialSyncRestart(); } } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java index d4811f6278c..f7140fb7f2d 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java @@ -54,6 +54,7 @@ import org.rocksdb.Env; import org.rocksdb.LRUCache; import org.rocksdb.OptimisticTransactionDB; +import org.rocksdb.ReadOptions; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.Statistics; @@ -86,6 +87,7 @@ public class RocksDBColumnarKeyValueStorage private final RocksDBMetrics metrics; private final WriteOptions tryDeleteOptions = new WriteOptions().setNoSlowdown(true).setIgnoreMissingColumnFamilies(true); + private final ReadOptions readOptions = new ReadOptions().setVerifyChecksums(false); public RocksDBColumnarKeyValueStorage( final RocksDBConfiguration configuration, @@ -206,7 +208,7 @@ public Optional get(final RocksDbSegmentIdentifier segment, final byte[] throwIfClosed(); try (final OperationTimer.TimingContext ignored = metrics.getReadLatency().startTimer()) { - return Optional.ofNullable(db.get(segment.get(), key)); + return Optional.ofNullable(db.get(segment.get(), readOptions, key)); } catch (final RocksDBException e) { throw new StorageException(e); } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java index c7a528d669f..f4376e402a6 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java @@ -59,7 +59,8 @@ public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, A this.snapshot = new RocksDBSnapshot(db); this.writeOptions = new WriteOptions(); this.snapTx = db.beginTransaction(writeOptions); - this.readOptions = new ReadOptions().setSnapshot(snapshot.markAndUseSnapshot()); + this.readOptions = + new ReadOptions().setVerifyChecksums(false).setSnapshot(snapshot.markAndUseSnapshot()); } private RocksDBSnapshotTransaction( @@ -140,8 +141,7 @@ public Stream streamKeys() { @Override public void commit() throws StorageException { - // no-op or throw? - throw new UnsupportedOperationException("RocksDBSnapshotTransaction does not support commit"); + // no-op } @Override diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/unsegmented/RocksDBKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/unsegmented/RocksDBKeyValueStorage.java index 7b8d8aa3a1d..e4f8733c4a6 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/unsegmented/RocksDBKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/unsegmented/RocksDBKeyValueStorage.java @@ -39,6 +39,7 @@ import org.rocksdb.LRUCache; import org.rocksdb.OptimisticTransactionDB; import org.rocksdb.Options; +import org.rocksdb.ReadOptions; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.Statistics; @@ -61,6 +62,7 @@ public class RocksDBKeyValueStorage implements KeyValueStorage { private final RocksDBMetrics rocksDBMetrics; private final WriteOptions tryDeleteOptions = new WriteOptions().setNoSlowdown(true).setIgnoreMissingColumnFamilies(true); + private final ReadOptions readOptions = new ReadOptions().setVerifyChecksums(false); public RocksDBKeyValueStorage( final RocksDBConfiguration configuration, @@ -114,7 +116,7 @@ public Optional get(final byte[] key) throws StorageException { try (final OperationTimer.TimingContext ignored = rocksDBMetrics.getReadLatency().startTimer()) { - return Optional.ofNullable(db.get(key)); + return Optional.ofNullable(db.get(readOptions, key)); } catch (final RocksDBException e) { throw new StorageException(e); } diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java index f496813c39c..a1729bdc594 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java @@ -19,6 +19,8 @@ import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; +import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage; import java.io.PrintStream; import java.util.HashMap; @@ -36,7 +38,9 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.tuweni.bytes.Bytes; -public class InMemoryKeyValueStorage implements KeyValueStorage { +/** The In memory key value storage. */ +public class InMemoryKeyValueStorage + implements SnappedKeyValueStorage, SnappableKeyValueStorage, KeyValueStorage { private final Map hashValueStore; private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); @@ -142,6 +146,21 @@ public Set keySet() { return Set.copyOf(hashValueStore.keySet()); } + @Override + public SnappedKeyValueStorage takeSnapshot() { + return new InMemoryKeyValueStorage(new HashMap<>(hashValueStore)); + } + + @Override + public KeyValueStorageTransaction getSnapshotTransaction() { + return startTransaction(); + } + + @Override + public SnappedKeyValueStorage cloneFromSnapshot() { + return takeSnapshot(); + } + private class InMemoryTransaction implements KeyValueStorageTransaction { private Map updatedValues = new HashMap<>();