From dfa45d856dcac5558e3e27cbc093f577bd93b767 Mon Sep 17 00:00:00 2001 From: matkt Date: Sun, 22 Jan 2023 10:27:55 +0100 Subject: [PATCH 01/13] Fix transaction pool issue (#4964) * fix transaction pool issue * add block replay * support in-memory snapshots Signed-off-by: Karim TAAM Signed-off-by: garyschulte Signed-off-by: Justin Florentine --- .../besu/services/BesuEventsImplTest.java | 1 + .../internal/processor/BlockReplay.java | 13 +++- .../processor/TransactionTracerTest.java | 1 + ...nsaiSnapshotWorldStateKeyValueStorage.java | 63 ++++++++----------- .../eth/transactions/TransactionPool.java | 12 +++- .../segmented/RocksDBSnapshotTransaction.java | 3 +- .../kvstore/InMemoryKeyValueStorage.java | 21 ++++++- 7 files changed, 70 insertions(+), 44 deletions(-) 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/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/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/bonsai/BonsaiSnapshotWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotWorldStateKeyValueStorage.java index c060c5bda39..ed746663419 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 @@ -180,10 +180,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 +192,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 +212,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 +228,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 +258,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 +271,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 +289,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/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..5565511fbd4 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 @@ -316,10 +316,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( 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..da17b47c2dd 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 @@ -140,8 +140,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/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<>(); From f677bb23a268ff4e212ea5beeec25700a620851e Mon Sep 17 00:00:00 2001 From: garyschulte Date: Fri, 3 Feb 2023 19:34:58 -0800 Subject: [PATCH 02/13] Keep Worldstate Storage open for Bonsai archive latest layer (#5039) * bonsai layered worldstate subscription * unsubscribe from worldstatestorage on close of BonsaiLayeredWorldState * minor txpool logging improvements Signed-off-by: garyschulte Signed-off-by: Justin Florentine --- CHANGELOG.md | 43 ++++++++++++++++++- .../bonsai/BonsaiInMemoryWorldState.java | 7 ++- .../bonsai/BonsaiLayeredWorldState.java | 20 +++++++++ .../AbstractPendingTransactionsSorter.java | 6 ++- 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77e7cdfd6a0..4b9a5dbc539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,47 @@ ### Breaking Changes ### Additions and Improvements -- Use safe block as snap sync pivot block [#4819](https://github.com/hyperledger/besu/pull/4912) -- Disconnect worst peer when retrying p2p tasks fails [#4888](https://github.com/hyperledger/besu/pull/4888) +- Support for new DATAHASH opcode as part of EIP-4844 [#4823](https://github.com/hyperledger/besu/issues/4823) +- Send only hash announcement for blob transaction type [#4940](https://github.com/hyperledger/besu/pull/4940) +- Add `excess_data_gas` field to block header [#4958](https://github.com/hyperledger/besu/pull/4958) +- Add `max_fee_per_data_gas` field to transaction [#4970](https://github.com/hyperledger/besu/pull/4970) + +### Bug Fixes +- Mitigation fix for stale bonsai code storage leading to log rolling issues on contract recreates [#4906](https://github.com/hyperledger/besu/pull/4906) +- Ensure latest cached layered worldstate is subscribed to storage, fix problem with RPC calls using 'latest' [#5039](https://github.com/hyperledger/besu/pull/5039) + + +## 23.1.0-RC1 + +### Breaking Changes +- Default configurations for the deprecated Ropsten, Kiln, Shandong, and Astor networks have been removed from the CLI network list. These networks can currently be accessed but will require a user-provided genesis configuration. [#4869](https://github.com/hyperledger/besu/pull/4869) + +### Additions and Improvements + +- Improve SLOAD and SSTORE performance by caching empty slots [#4874](https://github.com/hyperledger/besu/pull/4874) +- RPC methods that lookup block by hash will now return an error response if no block found [#4582](https://github.com/hyperledger/besu/pull/4582) +- Added support for `safe` and `finalized` strings for the RPC methods using defaultBlock parameter [#4902](https://github.com/hyperledger/besu/pull/4902) + +### Bug Fixes + +### Download Links +https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/23.1.0-RC1/besu-23.1.0-RC1.tar.gz / sha256: 30906891e528b3b4e3ce8e2313550a1da066b31ea10b05456dd0ad026792b46d +https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/23.1.0-RC1/besu-23.1.0-RC1.zip / sha256: 9067d1929079ae4a7c165e6f1e2bae08834939ed191f976d26544dc93352c306 + +## 23.1.0-beta + +### Breaking Changes +- GoQuorum-compatible privacy is deprecated and will be removed in 23.4 +- IBFT 1.0 is deprecated and will be removed in 23.4 +- Optimize SSTORE Operation execution time (memoize current and original value) [#4836](https://github.com/hyperledger/besu/pull/4836) + +### Additions and Improvements +- Added post-execution state logging option to EVM Tool [#4709](https://github.com/hyperledger/besu/pull/4709) +- Add access list to Transaction Call Object [#4802](https://github.com/hyperledger/besu/issues/4801) +- Add timestamp fork support, including shanghaiTime and cancunTime forks [#4743](https://github.com/hyperledger/besu/pull/4743) +- Optimization: Memoize transaction size and hash at the same time [#4812](https://github.com/hyperledger/besu/pull/4812) +- Add chain data pruning feature with three experimental CLI options: `--Xchain-pruning-enabled`, `--Xchain-pruning-blocks-retained` and `--Xchain-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) + - Note that chain pruning is hidden and disabled by default. Once you choose to enable chain pruning, a new column family will be added to the db and you cannot roll back to a previous version of Besu. ### Bug Fixes - Fix for segmentation faults on worldstate truncation, snap-sync starts [#4786](https://github.com/hyperledger/besu/pull/4786) 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..f54be522202 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 @@ -150,7 +150,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/BonsaiLayeredWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java index 222aef38ee6..dc64bc756c6 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 @@ -75,9 +75,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; } 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..3c6ff7aa8c1 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 @@ -402,7 +402,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; } From 85b0b043900417e44561d3bd66affc9348ace262 Mon Sep 17 00:00:00 2001 From: Justin Florentine Date: Sat, 4 Feb 2023 20:16:43 -0500 Subject: [PATCH 03/13] restores changelog Signed-off-by: Justin Florentine --- CHANGELOG.md | 43 ++----------------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b9a5dbc539..77e7cdfd6a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,47 +5,8 @@ ### Breaking Changes ### Additions and Improvements -- Support for new DATAHASH opcode as part of EIP-4844 [#4823](https://github.com/hyperledger/besu/issues/4823) -- Send only hash announcement for blob transaction type [#4940](https://github.com/hyperledger/besu/pull/4940) -- Add `excess_data_gas` field to block header [#4958](https://github.com/hyperledger/besu/pull/4958) -- Add `max_fee_per_data_gas` field to transaction [#4970](https://github.com/hyperledger/besu/pull/4970) - -### Bug Fixes -- Mitigation fix for stale bonsai code storage leading to log rolling issues on contract recreates [#4906](https://github.com/hyperledger/besu/pull/4906) -- Ensure latest cached layered worldstate is subscribed to storage, fix problem with RPC calls using 'latest' [#5039](https://github.com/hyperledger/besu/pull/5039) - - -## 23.1.0-RC1 - -### Breaking Changes -- Default configurations for the deprecated Ropsten, Kiln, Shandong, and Astor networks have been removed from the CLI network list. These networks can currently be accessed but will require a user-provided genesis configuration. [#4869](https://github.com/hyperledger/besu/pull/4869) - -### Additions and Improvements - -- Improve SLOAD and SSTORE performance by caching empty slots [#4874](https://github.com/hyperledger/besu/pull/4874) -- RPC methods that lookup block by hash will now return an error response if no block found [#4582](https://github.com/hyperledger/besu/pull/4582) -- Added support for `safe` and `finalized` strings for the RPC methods using defaultBlock parameter [#4902](https://github.com/hyperledger/besu/pull/4902) - -### Bug Fixes - -### Download Links -https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/23.1.0-RC1/besu-23.1.0-RC1.tar.gz / sha256: 30906891e528b3b4e3ce8e2313550a1da066b31ea10b05456dd0ad026792b46d -https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/23.1.0-RC1/besu-23.1.0-RC1.zip / sha256: 9067d1929079ae4a7c165e6f1e2bae08834939ed191f976d26544dc93352c306 - -## 23.1.0-beta - -### Breaking Changes -- GoQuorum-compatible privacy is deprecated and will be removed in 23.4 -- IBFT 1.0 is deprecated and will be removed in 23.4 -- Optimize SSTORE Operation execution time (memoize current and original value) [#4836](https://github.com/hyperledger/besu/pull/4836) - -### Additions and Improvements -- Added post-execution state logging option to EVM Tool [#4709](https://github.com/hyperledger/besu/pull/4709) -- Add access list to Transaction Call Object [#4802](https://github.com/hyperledger/besu/issues/4801) -- Add timestamp fork support, including shanghaiTime and cancunTime forks [#4743](https://github.com/hyperledger/besu/pull/4743) -- Optimization: Memoize transaction size and hash at the same time [#4812](https://github.com/hyperledger/besu/pull/4812) -- Add chain data pruning feature with three experimental CLI options: `--Xchain-pruning-enabled`, `--Xchain-pruning-blocks-retained` and `--Xchain-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) - - Note that chain pruning is hidden and disabled by default. Once you choose to enable chain pruning, a new column family will be added to the db and you cannot roll back to a previous version of Besu. +- Use safe block as snap sync pivot block [#4819](https://github.com/hyperledger/besu/pull/4912) +- Disconnect worst peer when retrying p2p tasks fails [#4888](https://github.com/hyperledger/besu/pull/4888) ### Bug Fixes - Fix for segmentation faults on worldstate truncation, snap-sync starts [#4786](https://github.com/hyperledger/besu/pull/4786) From 809fdbdd76d5ff3a66cb0712166593579032662c Mon Sep 17 00:00:00 2001 From: ahamlat Date: Fri, 3 Feb 2023 17:10:12 +0100 Subject: [PATCH 04/13] Disable RocksDB checksum verification on reads (#5042) * Add RocksDB readOptions with checksum verification set to false for RocksDB get methods * Spotless + apply disabling checksum verification for unsegmented RocksDB reads Signed-off-by: Ameziane H Signed-off-by: Justin Florentine --- .../rocksdb/segmented/RocksDBColumnarKeyValueStorage.java | 6 +++++- .../rocksdb/segmented/RocksDBSnapshotTransaction.java | 3 ++- .../storage/rocksdb/unsegmented/RocksDBKeyValueStorage.java | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) 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..ff7281773a4 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,9 @@ import org.rocksdb.Env; import org.rocksdb.LRUCache; import org.rocksdb.OptimisticTransactionDB; +import org.rocksdb.Options; +import org.rocksdb.ReadOptions; +import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.Statistics; @@ -86,6 +89,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 +210,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 da17b47c2dd..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( 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); } From 33ba164ae4ca4aefe1dbd4e58c615bb0e136d992 Mon Sep 17 00:00:00 2001 From: Justin Florentine Date: Sat, 4 Feb 2023 21:31:12 -0500 Subject: [PATCH 05/13] wires in patched version of rocksdbjni Signed-off-by: Justin Florentine --- build.gradle | 4 ++++ gradle/verification-metadata.xml | 8 ++++++++ gradle/versions.gradle | 2 +- .../rocksdb/segmented/RocksDBColumnarKeyValueStorage.java | 2 -- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 2052788a3e7..7870da1d1f6 100644 --- a/build.gradle +++ b/build.gradle @@ -115,6 +115,10 @@ allprojects { url 'https://dl.cloudsmith.io/public/consensys/quorum-mainnet-launcher/maven/' content { includeGroupByRegex('net\\.consensys\\..*') } } + maven { + url "https://artifacts.consensys.net/public/besu-rocksdb/maven/" + //content { includeGroup('org\\.rocksdb') } + } maven { url 'https://splunk.jfrog.io/splunk/ext-releases-local' content { includeGroupByRegex('com\\.splunk\\..*') } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 96ce1603b67..b604324facf 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -4951,6 +4951,14 @@ + + + + + + + + diff --git a/gradle/versions.gradle b/gradle/versions.gradle index c0b4409d71f..c4d508ac1d0 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -189,7 +189,7 @@ dependencyManagement { dependency 'org.openjdk.jmh:jmh-core:1.34' dependency 'org.openjdk.jmh:jmh-generator-annprocess:1.34' - dependency 'org.rocksdb:rocksdbjni:7.7.3' + dependency 'org.rocksdb:rocksdbjni:7.9.2-a1e92bd' dependency 'org.slf4j:slf4j-api:1.7.36' 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 ff7281773a4..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,9 +54,7 @@ import org.rocksdb.Env; import org.rocksdb.LRUCache; import org.rocksdb.OptimisticTransactionDB; -import org.rocksdb.Options; import org.rocksdb.ReadOptions; -import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.Statistics; From c0155673a81d90567cd0955bc8ceb8f099505044 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Sun, 5 Feb 2023 13:52:15 +1000 Subject: [PATCH 06/13] Fix maven repo content include Signed-off-by: Simon Dudley --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7870da1d1f6..314c2d11aab 100644 --- a/build.gradle +++ b/build.gradle @@ -117,7 +117,7 @@ allprojects { } maven { url "https://artifacts.consensys.net/public/besu-rocksdb/maven/" - //content { includeGroup('org\\.rocksdb') } + content { includeGroup('org.rocksdb') } } maven { url 'https://splunk.jfrog.io/splunk/ext-releases-local' From 34ea399b6afb886948e1bfa11ded738637348fc6 Mon Sep 17 00:00:00 2001 From: ahamlat Date: Wed, 11 Jan 2023 23:14:27 +0100 Subject: [PATCH 07/13] Cache empty slots (#4874) * Cache empty slots. Signed-off-by: Ameziane H * clear after each block and copy during clone Signed-off-by: Karim TAAM * add changelog Signed-off-by: Ameziane H * Avoid triggering a calculate root hash when empty slot cache is not empty. Signed-off-by: Ameziane H Signed-off-by: Ameziane H Signed-off-by: Karim TAAM Signed-off-by: ahamlat Signed-off-by: Simon Dudley Co-authored-by: Karim TAAM --- .../bonsai/BonsaiWorldStateUpdater.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) 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..32429437180 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 @@ -55,6 +55,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,6 +91,7 @@ void cloneFromUpdater(final BonsaiWorldStateUpdater source) { storageToUpdate.putAll(source.storageToUpdate); updatedAccounts.putAll(source.updatedAccounts); deletedAccounts.addAll(source.deletedAccounts); + emptySlot.addAll(source.emptySlot); } @Override @@ -352,18 +354,26 @@ 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 { + final Optional valueUInt = + 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; + } } @Override @@ -706,6 +716,7 @@ public void reset() { storageToUpdate.clear(); codeToUpdate.clear(); accountsToUpdate.clear(); + emptySlot.clear(); super.reset(); } From 7f07c5c1647678734d01436492685d7a4934ea0f Mon Sep 17 00:00:00 2001 From: matkt Date: Thu, 19 Jan 2023 17:48:36 +0100 Subject: [PATCH 08/13] use the updater cache to get an account during block processing (#4897) Use WorldStateBonsaiUpdater accounts cache each time we try to get an account from storage. Currently, we use this cache only in some code paths. Signed-off-by: Karim TAAM Signed-off-by: Simon Dudley Co-authored-by: Ameziane H --- .../bonsai/BonsaiPersistedWorldState.java | 10 +++++++ .../BonsaiWorldStateKeyValueStorage.java | 26 ++++++++++++++----- .../bonsai/BonsaiWorldStateUpdater.java | 21 ++++++++++++--- 3 files changed, 48 insertions(+), 9 deletions(-) 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..fa14be03b70 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 @@ -35,6 +35,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; @@ -427,6 +428,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/BonsaiWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java index 46ba2e5f197..240f84c5e63 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 @@ -33,6 +33,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; @@ -181,24 +182,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))); } 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 32429437180..301c452493d 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 @@ -37,6 +37,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; @@ -96,7 +97,7 @@ void cloneFromUpdater(final BonsaiWorldStateUpdater source) { @Override public Account get(final Address address) { - return super.get(address); + return super.getAccount(address); } @Override @@ -151,6 +152,12 @@ Map>> getStorageToUpdate() { @Override protected BonsaiAccount getForMutation(final Address address) { + return loadAccount(address, BonsaiValue::getUpdated); + } + + protected BonsaiAccount loadAccount( + final Address address, + final Function, BonsaiAccount> bonsaiAccountFunction) { final BonsaiValue bonsaiValue = accountsToUpdate.get(address); if (bonsaiValue == null) { final Account account = wrappedWorldView().get(address); @@ -162,7 +169,7 @@ protected BonsaiAccount getForMutation(final Address address) { return null; } } else { - return bonsaiValue.getUpdated(); + return bonsaiAccountFunction.apply(bonsaiValue); } } @@ -359,7 +366,15 @@ public Optional getStorageValueBySlotHash(final Address address, final return Optional.empty(); } else { final Optional valueUInt = - wrappedWorldView().getStorageValueBySlotHash(address, slotHash); + (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 From 35cff5552b7f8426892c9cd956d6b57972665597 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Wed, 11 Jan 2023 20:40:58 -0800 Subject: [PATCH 09/13] Worldstate-only resync behavior (#4875) * use debug rpc endpoint to resync worldstate * Reset transaction pool state every time the initial sync is done Signed-off-by: garyschulte Signed-off-by: Simon Dudley Co-authored-by: Fabio Di Fabio --- .../besu/ethereum/api/jsonrpc/RpcMethod.java | 1 + .../methods/DebugResyncWorldstate.java | 53 +++++++++ .../jsonrpc/methods/DebugJsonRpcMethods.java | 6 + .../methods/JsonRpcMethodsFactory.java | 1 + .../besu/ethereum/chain/BadBlockManager.java | 6 + .../besu/ethereum/core/Synchronizer.java | 2 + .../eth/sync/DefaultSynchronizer.java | 105 +++++++++++------- .../backwardsync/BackwardsSyncAlgorithm.java | 33 ++++-- .../CheckpointDownloaderFactory.java | 12 +- .../worldstate/FastDownloaderFactory.java | 9 +- .../sync/snapsync/SnapDownloaderFactory.java | 12 +- .../ethereum/eth/sync/state/SyncState.java | 5 + ...PooledTransactionHashesMessageHandler.java | 24 +++- .../transactions/PeerTransactionTracker.java | 5 + .../eth/transactions/TransactionPool.java | 26 ++++- .../transactions/TransactionPoolFactory.java | 56 ++++++---- .../TransactionsMessageHandler.java | 24 +++- .../AbstractPendingTransactionsSorter.java | 6 + .../BaseFeePendingTransactionsSorter.java | 7 ++ .../GasPricePendingTransactionsSorter.java | 6 + .../sorter/LowestInvalidNonceCache.java | 5 + .../fastsync/FastDownloaderFactoryTest.java | 15 ++- .../TransactionPoolFactoryTest.java | 98 ++++++++-------- .../ethereum/retesteth/DummySynchronizer.java | 5 + plugin-api/build.gradle | 2 +- .../besu/plugin/services/BesuEvents.java | 3 + 26 files changed, 382 insertions(+), 145 deletions(-) create mode 100644 ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugResyncWorldstate.java 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/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/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..1ca313d5303 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 @@ -40,6 +40,8 @@ public interface Synchronizer { */ Optional getSyncStatus(); + boolean resyncWorldState(); + long subscribeSyncStatus(final BesuEvents.SyncStatusListener listener); boolean unsubscribeSyncStatus(long observerId); 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..3816fbeba2d 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 @@ -50,6 +50,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,7 +63,8 @@ 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 Function>> fastSyncFactory; + private Optional> fastSyncDownloader; private final Optional fullSyncDownloader; private final EthContext ethContext; private final ProtocolContext protocolContext; @@ -133,48 +135,57 @@ 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 = + (isResync) -> + FastDownloaderFactory.create( + pivotBlockSelector, + syncConfig, + dataDirectory, + protocolSchedule, + protocolContext, + metricsSystem, + ethContext, + worldStateStorage, + syncState, + clock, + isResync); } 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 = + (isResync) -> + CheckpointDownloaderFactory.createCheckpointDownloader( + new SnapPersistedContext(storageProvider), + pivotBlockSelector, + syncConfig, + dataDirectory, + protocolSchedule, + protocolContext, + metricsSystem, + ethContext, + worldStateStorage, + syncState, + clock, + isResync); } else { - this.fastSyncDownloader = - SnapDownloaderFactory.createSnapDownloader( - new SnapPersistedContext(storageProvider), - pivotBlockSelector, - syncConfig, - dataDirectory, - protocolSchedule, - protocolContext, - metricsSystem, - ethContext, - worldStateStorage, - syncState, - clock); + this.fastSyncFactory = + (isResync) -> + SnapDownloaderFactory.createSnapDownloader( + new SnapPersistedContext(storageProvider), + pivotBlockSelector, + syncConfig, + dataDirectory, + protocolSchedule, + protocolContext, + metricsSystem, + ethContext, + worldStateStorage, + syncState, + clock, + isResync); } + // create a non-resync fast sync downloader: + this.fastSyncDownloader = this.fastSyncFactory.apply(false); + metricsSystem.createLongGauge( BesuMetricCategory.ETHEREUM, "best_known_block_number", @@ -209,7 +220,6 @@ public CompletableFuture start() { CompletableFuture future; if (fastSyncDownloader.isPresent()) { future = fastSyncDownloader.get().start().thenCompose(this::handleSyncResult); - } else { syncState.markInitialSyncPhaseAsDone(); enableFallbackNodeFinder(); @@ -305,6 +315,21 @@ 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.fastSyncDownloader = this.fastSyncFactory.apply(true); + 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/backwardsync/BackwardsSyncAlgorithm.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardsSyncAlgorithm.java index 648c706f1f9..7bcb8a8b8dd 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( @@ -125,19 +129,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 +157,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 +168,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/checkpointsync/CheckpointDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java index 781162a36f2..3627b358e02 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 @@ -60,7 +60,8 @@ public static Optional> createCheckpointDownloader( final EthContext ethContext, final WorldStateStorage worldStateStorage, final SyncState syncState, - final Clock clock) { + final Clock clock, + final boolean isResync) { final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); final FastSyncStateStorage fastSyncStateStorage = @@ -79,7 +80,14 @@ public static Optional> createCheckpointDownloader( final FastSyncState fastSyncState = fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); - if (fastSyncState.getPivotBlockHeader().isEmpty() + + if (isResync) { + snapContext.clear(); + worldStateStorage.clear(); + } + + if (!isResync + && 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/fastsync/worldstate/FastDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastDownloaderFactory.java index b7e2bbf4a76..81e0b4d8bbb 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 @@ -61,7 +61,8 @@ public static Optional> create( final EthContext ethContext, final WorldStateStorage worldStateStorage, final SyncState syncState, - final Clock clock) { + final Clock clock, + final boolean isResync) { final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); final FastSyncStateStorage fastSyncStateStorage = @@ -80,13 +81,17 @@ public static Optional> create( final FastSyncState fastSyncState = fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); - if (fastSyncState.getPivotBlockHeader().isEmpty() + + if (isResync) { + worldStateStorage.clear(); + } else if (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..9c85a743e7c 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 @@ -56,7 +56,8 @@ public static Optional> createSnapDownloader( final EthContext ethContext, final WorldStateStorage worldStateStorage, final SyncState syncState, - final Clock clock) { + final Clock clock, + final boolean isResync) { final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); final FastSyncStateStorage fastSyncStateStorage = @@ -75,7 +76,14 @@ public static Optional> createSnapDownloader( final FastSyncState fastSyncState = fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); - if (fastSyncState.getPivotBlockHeader().isEmpty() + + if (isResync) { + snapContext.clear(); + worldStateStorage.clear(); + } + + if (!isResync + && 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/state/SyncState.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/state/SyncState.java index 026453c1bee..3a6ea374892 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 @@ -319,4 +319,9 @@ public void markInitialSyncPhaseAsDone() { public boolean isInitialSyncPhaseDone() { return isInitialSyncPhaseDone; } + + public void markInitialSyncRestart() { + isInitialSyncPhaseDone = false; + completionListenerSubscribers.forEach(InitialSyncCompletionListener::onInitialSyncRestart); + } } 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 5565511fbd4..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) { @@ -434,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 3c6ff7aa8c1..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); 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/fastsync/FastDownloaderFactoryTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java index 7cdcd8d3dc4..8f95a0be90d 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java @@ -80,7 +80,8 @@ public void shouldThrowIfSyncModeChangedWhileFastSyncIncomplete() { ethContext, worldStateStorage, syncState, - clock)) + clock, + false)) .isInstanceOf(IllegalStateException.class); } @@ -101,7 +102,8 @@ public void shouldNotThrowIfSyncModeChangedWhileFastSyncComplete() { ethContext, worldStateStorage, syncState, - clock); + clock, + false); assertThat(result).isEmpty(); } @@ -125,7 +127,8 @@ public void shouldNotThrowWhenFastSyncModeRequested() throws NoSuchFieldExceptio ethContext, worldStateStorage, syncState, - clock); + clock, + false); verify(mutableBlockchain).getChainHeadBlockNumber(); } @@ -155,7 +158,8 @@ public void shouldClearWorldStateDuringFastSyncWhenStateQueDirectoryExists() thr ethContext, worldStateStorage, syncState, - clock); + clock, + false); verify(worldStateStorage).clear(); assertThat(Files.exists(stateQueueDir)).isFalse(); @@ -187,7 +191,8 @@ public void shouldCrashWhenStateQueueIsNotDirectory() throws IOException { ethContext, worldStateStorage, syncState, - clock)) + clock, + false)) .isInstanceOf(IllegalStateException.class); } 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..b523d2ba542 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 @@ -44,6 +44,11 @@ public Optional getSyncStatus() { return Optional.empty(); } + @Override + public boolean resyncWorldState() { + return false; + } + @Override public long subscribeSyncStatus(final BesuEvents.SyncStatusListener listener) { return 0; 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(); } } From 23bd28c072cbc9796de7dd794d9b2c51120d9d66 Mon Sep 17 00:00:00 2001 From: Karim TAAM Date: Mon, 6 Feb 2023 08:22:13 +0100 Subject: [PATCH 10/13] init heal code Signed-off-by: Karim TAAM Signed-off-by: Simon Dudley --- .../controller/BesuControllerBuilder.java | 3 +- .../besu/ethereum/MainnetBlockValidator.java | 11 +- .../besu/ethereum/ProtocolContext.java | 12 + .../besu/ethereum/bonsai/BonsaiAccount.java | 2 +- .../bonsai/BonsaiInMemoryWorldState.java | 39 ++- ...nsaiInMemoryWorldStateKeyValueStorage.java | 14 +- .../bonsai/BonsaiLayeredWorldState.java | 7 +- .../bonsai/BonsaiPersistedWorldState.java | 85 +++-- ...nsaiSnapshotWorldStateKeyValueStorage.java | 37 +-- .../bonsai/BonsaiWorldStateArchive.java | 74 ++++- .../BonsaiWorldStateKeyValueStorage.java | 93 ++---- .../bonsai/BonsaiWorldStateUpdater.java | 136 +++++--- .../besu/ethereum/bonsai/BonsaiWorldView.java | 2 +- .../bonsai/SnapshotTrieLogManager.java | 5 + .../besu/ethereum/core/Synchronizer.java | 5 + .../mainnet/AbstractBlockProcessor.java | 7 + .../mainnet/MainnetTransactionProcessor.java | 4 + .../keyvalue/WorldStateKeyValueStorage.java | 5 + .../worldstate/WorldStateStorage.java | 2 + .../BonsaiWorldStateKeyValueStorageTest.java | 72 +--- .../eth/sync/DefaultSynchronizer.java | 89 ++--- .../eth/sync/PipelineChainDownloader.java | 1 + .../sync/backwardsync/BackwardSyncStep.java | 11 +- .../backwardsync/BackwardsSyncAlgorithm.java | 6 +- .../backwardsync/FinalBlockConfirmation.java | 4 +- .../sync/backwardsync/ForwardSyncStep.java | 4 +- .../ProcessKnownAncestorsStep.java | 32 +- .../CheckpointDownloaderFactory.java | 25 +- .../eth/sync/fastsync/FastSyncDownloader.java | 1 + .../sync/fastsync/FastSyncTargetManager.java | 14 +- .../worldstate/FastDownloaderFactory.java | 8 +- .../sync/snapsync/SnapDownloaderFactory.java | 15 +- .../sync/snapsync/SnapPersistedContext.java | 16 +- .../sync/snapsync/SnapWorldDownloadState.java | 1 + .../snapsync/SnapWorldStateDownloader.java | 11 +- .../ethereum/eth/sync/state/SyncState.java | 26 +- .../WorldStatePeerTrieNodeFinder.java | 172 ---------- .../backwardsync/BackwardSyncStepTest.java | 9 +- .../fastsync/FastDownloaderFactoryTest.java | 15 +- .../WorldStatePeerTrieNodeFinderTest.java | 307 ------------------ .../ethereum/retesteth/DummySynchronizer.java | 9 + .../ethereum/trie/MerkleTrieException.java | 21 ++ .../prometheus/PrometheusMetricsSystem.java | 27 +- .../PrometheusMetricsSystemTest.java | 12 +- 44 files changed, 581 insertions(+), 870 deletions(-) delete mode 100644 ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinder.java delete mode 100644 ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStatePeerTrieNodeFinderTest.java 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/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 f54be522202..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()); } } 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 dc64bc756c6..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; @@ -107,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); @@ -124,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(); @@ -291,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 fa14be03b70..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; @@ -92,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) { @@ -148,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) { @@ -157,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 : @@ -215,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()); } } @@ -259,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()); } } } 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 ed746663419..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 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 240f84c5e63..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; @@ -53,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( @@ -71,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) { @@ -128,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)); } } @@ -148,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)); } } @@ -228,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 @@ -245,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); @@ -277,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); } @@ -456,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 301c452493d..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; @@ -158,18 +159,25 @@ protected BonsaiAccount getForMutation(final Address address) { protected BonsaiAccount loadAccount( final Address address, final Function, BonsaiAccount> bonsaiAccountFunction) { - 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; + 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 bonsaiAccountFunction.apply(bonsaiValue); + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); } } @@ -201,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))); @@ -251,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) { @@ -281,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()); } @@ -330,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()); } @@ -365,29 +387,35 @@ public Optional getStorageValueBySlotHash(final Address address, final if (emptySlot.contains(slot)) { return Optional.empty(); } else { - 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; + 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()); + } } } @@ -579,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()); } } @@ -599,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); 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/core/Synchronizer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Synchronizer.java index 1ca313d5303..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 { @@ -42,6 +45,8 @@ public interface Synchronizer { 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 3816fbeba2d..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,25 +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.Function; +import java.util.function.Supplier; +import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,14 +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 Function>> fastSyncFactory; + 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; @@ -88,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( @@ -136,7 +129,7 @@ public DefaultSynchronizer( if (SyncMode.FAST.equals(syncConfig.getSyncMode())) { this.fastSyncFactory = - (isResync) -> + () -> FastDownloaderFactory.create( pivotBlockSelector, syncConfig, @@ -147,11 +140,10 @@ public DefaultSynchronizer( ethContext, worldStateStorage, syncState, - clock, - isResync); + clock); } else if (SyncMode.X_CHECKPOINT.equals(syncConfig.getSyncMode())) { this.fastSyncFactory = - (isResync) -> + () -> CheckpointDownloaderFactory.createCheckpointDownloader( new SnapPersistedContext(storageProvider), pivotBlockSelector, @@ -163,11 +155,10 @@ public DefaultSynchronizer( ethContext, worldStateStorage, syncState, - clock, - isResync); + clock); } else { this.fastSyncFactory = - (isResync) -> + () -> SnapDownloaderFactory.createSnapDownloader( new SnapPersistedContext(storageProvider), pivotBlockSelector, @@ -179,12 +170,11 @@ public DefaultSynchronizer( ethContext, worldStateStorage, syncState, - clock, - isResync); + clock); } // create a non-resync fast sync downloader: - this.fastSyncDownloader = this.fastSyncFactory.apply(false); + this.fastSyncDownloader = this.fastSyncFactory.get(); metricsSystem.createLongGauge( BesuMetricCategory.ETHEREUM, @@ -222,7 +212,6 @@ public CompletableFuture start() { future = fastSyncDownloader.get().start().thenCompose(this::handleSyncResult); } else { syncState.markInitialSyncPhaseAsDone(); - enableFallbackNodeFinder(); future = startFullSync(); } return future.thenApply(this::finalizeSync); @@ -271,8 +260,6 @@ private CompletableFuture handleSyncResult(final FastSyncState result) { pivotBlockSelector.close(); syncState.markInitialSyncPhaseAsDone(); - enableFallbackNodeFinder(); - if (terminationCondition.shouldContinueDownload()) { return startFullSync(); } else { @@ -281,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 @@ -322,10 +296,41 @@ public boolean resyncWorldState() { 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.fastSyncDownloader = this.fastSyncFactory.apply(true); + 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; } 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 7bcb8a8b8dd..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 @@ -84,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 {} : {}", 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 3627b358e02..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; @@ -60,8 +61,7 @@ public static Optional> createCheckpointDownloader( final EthContext ethContext, final WorldStateStorage worldStateStorage, final SyncState syncState, - final Clock clock, - final boolean isResync) { + final Clock clock) { final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); final FastSyncStateStorage fastSyncStateStorage = @@ -81,13 +81,12 @@ public static Optional> createCheckpointDownloader( final FastSyncState fastSyncState = fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); - if (isResync) { + if (syncState.isResyncNeeded()) { snapContext.clear(); - worldStateStorage.clear(); - } - - if (!isResync - && fastSyncState.getPivotBlockHeader().isEmpty() + syncState + .getAccountToRepair() + .ifPresent(address -> snapContext.addInconsistentAccount(Hash.hash(address))); + } else if (fastSyncState.getPivotBlockHeader().isEmpty() && protocolContext.getBlockchain().getChainHeadBlockNumber() != BlockHeader.GENESIS_BLOCK_NUMBER) { LOG.info( @@ -109,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 81e0b4d8bbb..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 @@ -61,8 +61,7 @@ public static Optional> create( final EthContext ethContext, final WorldStateStorage worldStateStorage, final SyncState syncState, - final Clock clock, - final boolean isResync) { + final Clock clock) { final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); final FastSyncStateStorage fastSyncStateStorage = @@ -82,9 +81,8 @@ public static Optional> create( final FastSyncState fastSyncState = fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); - if (isResync) { - worldStateStorage.clear(); - } else if (fastSyncState.getPivotBlockHeader().isEmpty() + if (!syncState.isResyncNeeded() + && 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/SnapDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java index 9c85a743e7c..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; @@ -56,8 +57,7 @@ public static Optional> createSnapDownloader( final EthContext ethContext, final WorldStateStorage worldStateStorage, final SyncState syncState, - final Clock clock, - final boolean isResync) { + final Clock clock) { final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); final FastSyncStateStorage fastSyncStateStorage = @@ -77,13 +77,12 @@ public static Optional> createSnapDownloader( final FastSyncState fastSyncState = fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); - if (isResync) { + if (syncState.isResyncNeeded()) { snapContext.clear(); - worldStateStorage.clear(); - } - - if (!isResync - && fastSyncState.getPivotBlockHeader().isEmpty() + 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 3a6ea374892..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,13 +316,30 @@ 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() { 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/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/fastsync/FastDownloaderFactoryTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java index 8f95a0be90d..7cdcd8d3dc4 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java @@ -80,8 +80,7 @@ public void shouldThrowIfSyncModeChangedWhileFastSyncIncomplete() { ethContext, worldStateStorage, syncState, - clock, - false)) + clock)) .isInstanceOf(IllegalStateException.class); } @@ -102,8 +101,7 @@ public void shouldNotThrowIfSyncModeChangedWhileFastSyncComplete() { ethContext, worldStateStorage, syncState, - clock, - false); + clock); assertThat(result).isEmpty(); } @@ -127,8 +125,7 @@ public void shouldNotThrowWhenFastSyncModeRequested() throws NoSuchFieldExceptio ethContext, worldStateStorage, syncState, - clock, - false); + clock); verify(mutableBlockchain).getChainHeadBlockNumber(); } @@ -158,8 +155,7 @@ public void shouldClearWorldStateDuringFastSyncWhenStateQueDirectoryExists() thr ethContext, worldStateStorage, syncState, - clock, - false); + clock); verify(worldStateStorage).clear(); assertThat(Files.exists(stateQueueDir)).isFalse(); @@ -191,8 +187,7 @@ public void shouldCrashWhenStateQueueIsNotDirectory() throws IOException { ethContext, worldStateStorage, syncState, - clock, - false)) + clock)) .isInstanceOf(IllegalStateException.class); } 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/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 b523d2ba542..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 @@ -49,6 +52,12 @@ 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 From e330b3466544aab2adae9a8b751769ee2965796c Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 7 Feb 2023 21:12:15 +1000 Subject: [PATCH 11/13] Revert "Fix maven repo content include" This reverts commit c0155673a81d90567cd0955bc8ceb8f099505044. Signed-off-by: Simon Dudley --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 314c2d11aab..7870da1d1f6 100644 --- a/build.gradle +++ b/build.gradle @@ -117,7 +117,7 @@ allprojects { } maven { url "https://artifacts.consensys.net/public/besu-rocksdb/maven/" - content { includeGroup('org.rocksdb') } + //content { includeGroup('org\\.rocksdb') } } maven { url 'https://splunk.jfrog.io/splunk/ext-releases-local' From 52bb5570ebafc16edd39fdf4e6d50a4faf92685e Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 7 Feb 2023 21:12:25 +1000 Subject: [PATCH 12/13] Revert "wires in patched version of rocksdbjni" This reverts commit 33ba164ae4ca4aefe1dbd4e58c615bb0e136d992. Signed-off-by: Simon Dudley --- build.gradle | 4 ---- gradle/verification-metadata.xml | 8 -------- gradle/versions.gradle | 2 +- .../rocksdb/segmented/RocksDBColumnarKeyValueStorage.java | 2 ++ 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 7870da1d1f6..2052788a3e7 100644 --- a/build.gradle +++ b/build.gradle @@ -115,10 +115,6 @@ allprojects { url 'https://dl.cloudsmith.io/public/consensys/quorum-mainnet-launcher/maven/' content { includeGroupByRegex('net\\.consensys\\..*') } } - maven { - url "https://artifacts.consensys.net/public/besu-rocksdb/maven/" - //content { includeGroup('org\\.rocksdb') } - } maven { url 'https://splunk.jfrog.io/splunk/ext-releases-local' content { includeGroupByRegex('com\\.splunk\\..*') } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index b604324facf..96ce1603b67 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -4951,14 +4951,6 @@ - - - - - - - - diff --git a/gradle/versions.gradle b/gradle/versions.gradle index c4d508ac1d0..c0b4409d71f 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -189,7 +189,7 @@ dependencyManagement { dependency 'org.openjdk.jmh:jmh-core:1.34' dependency 'org.openjdk.jmh:jmh-generator-annprocess:1.34' - dependency 'org.rocksdb:rocksdbjni:7.9.2-a1e92bd' + dependency 'org.rocksdb:rocksdbjni:7.7.3' dependency 'org.slf4j:slf4j-api:1.7.36' 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 f7140fb7f2d..ff7281773a4 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,7 +54,9 @@ import org.rocksdb.Env; import org.rocksdb.LRUCache; import org.rocksdb.OptimisticTransactionDB; +import org.rocksdb.Options; import org.rocksdb.ReadOptions; +import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.Statistics; From 3df0293cd46b2fb1431dbec4c65df62c3ecaa16f Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 7 Feb 2023 21:19:15 +1000 Subject: [PATCH 13/13] Spotless Signed-off-by: Simon Dudley --- .../rocksdb/segmented/RocksDBColumnarKeyValueStorage.java | 2 -- 1 file changed, 2 deletions(-) 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 ff7281773a4..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,9 +54,7 @@ import org.rocksdb.Env; import org.rocksdb.LRUCache; import org.rocksdb.OptimisticTransactionDB; -import org.rocksdb.Options; import org.rocksdb.ReadOptions; -import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.rocksdb.Statistics;