From addd0f1b22eca94f600da4a076d43ed6e03e6ff4 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Wed, 27 Nov 2024 16:49:45 -0800 Subject: [PATCH 01/15] initial commit for rebuild state trie command, compiles, untested. Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 193 ++++++++++++++++++ .../storage/flat/BonsaiFlatDbStrategy.java | 6 +- 2 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java new file mode 100644 index 00000000000..29b1631ba02 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -0,0 +1,193 @@ +/* + * 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.cli.subcommands.storage; + +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; + +import org.hyperledger.besu.cli.util.VersionProvider; +import org.hyperledger.besu.controller.BesuController; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.MiningConfiguration; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.trie.patricia.StoredNodeFactory; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.ParentCommand; + +/** The revert metadata to v1 subcommand. */ +@Command( + name = "rebuild-bonsai-state-trie", + description = "Rebuilds bonsai state trie from flat database", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class RebuildBonsaiStateTrieSubCommand implements Runnable { + private static final Logger LOG = LoggerFactory.getLogger(RebuildBonsaiStateTrieSubCommand.class); + private static final Hash HASH_LAST = + Hash.wrap(Bytes32.leftPad(Bytes.fromHexString("FF"), (byte) 0xFF)); + + @SuppressWarnings("unused") + @ParentCommand + private StorageSubCommand parentCommand; + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + /** Default Constructor. */ + public RebuildBonsaiStateTrieSubCommand() {} + + @Override + public void run() { + // spec.commandLine().usage(System.out); + try (final BesuController controller = createController()) { + + var storageProvider = controller.getStorageProvider(); + + // TODO: assuming bonsai, fix or qualify + var worldStateStorage = + (BonsaiWorldStateKeyValueStorage) + storageProvider.createWorldStateStorage( + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + + var header = controller.getProtocolContext().getBlockchain().getChainHeadHeader(); + // TODO: add a check to ensure the flat db state root and block hash either match or are + // absent from state trie + + // truncate the TRIE_BRANCH_STORAGE column family, + // subsequently rewrite blockhash and state root after we rebuild the trie + worldStateStorage.clearTrie(); + + // rebuild the trie by inserting everything into a StoredMerklePatriciaTrie + // and incrementally (naively) commit after each account while streaming + // TODO: optimize to incrementally commit tx after a certain threshold + + final var wss = worldStateStorage.getComposedWorldStateStorage(); + final var accountTrie = + new StoredMerklePatriciaTrie<>( + new StoredNodeFactory<>( + (location, hash) -> worldStateStorage.getAccount(Hash.wrap(hash)), + Function.identity(), + Function.identity()), + MerkleTrie.EMPTY_TRIE_NODE_HASH); + + final var flatdb = worldStateStorage.getFlatDbStrategy(); + flatdb + .accountsToPairStream(wss, Bytes32.ZERO) + .forEach( + accountPair -> { + final SegmentedKeyValueStorageTransaction perAccountTx = + worldStateStorage.getComposedWorldStateStorage().startTransaction(); + + // if the account has storage, write the account storage trie values + if (isNonEmptyStorage(accountPair.getSecond())) { + // create account storage trie + var accountStorageTrie = + new StoredMerklePatriciaTrie<>( + new StoredNodeFactory<>( + (location, hash) -> + worldStateStorage.getAccountStorageTrieNode( + Hash.wrap(accountPair.getFirst()), location, hash), + Function.identity(), + Function.identity()), + MerkleTrie.EMPTY_TRIE_NODE_HASH); + + // put into account trie + flatdb + .storageToPairStream( + wss, + Hash.wrap(accountPair.getFirst()), + Bytes32.ZERO, + HASH_LAST, + Function.identity()) + .forEach( + storagePair -> { + accountStorageTrie.put(storagePair.getFirst(), storagePair.getSecond()); + }); + + // commit the account storage trie + accountStorageTrie.commit( + (location, hash, value) -> + perAccountTx.put( + TRIE_BRANCH_STORAGE, + Bytes.concatenate(accountPair.getFirst(), location).toArrayUnsafe(), + value.toArrayUnsafe())); + } + + // write the account info + accountTrie.put(accountPair.getFirst(), accountPair.getSecond()); + + // commit the account trie + accountTrie.commit( + (location, hash, value) -> + perAccountTx.put( + TRIE_BRANCH_STORAGE, location.toArrayUnsafe(), value.toArrayUnsafe())); + + LOG.info("committing accountHash {}", accountPair.getFirst()); + perAccountTx.commit(); + }); + + // rewrite state root and blockhash + var tx = worldStateStorage.getComposedWorldStateStorage().startTransaction(); + tx.put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, header.getStateRoot().toArrayUnsafe()); + tx.put(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, header.getBlockHash().toArrayUnsafe()); + LOG.info("committing blockhash and stateroot {}", header.toLogString()); + tx.commit(); + } + } + + private boolean isNonEmptyStorage(final Bytes accountRLP) { + final RLPInput in = RLP.input(accountRLP); + in.enterList(); + + // nonce + in.readLongScalar(); + // balance + in.readUInt256Scalar(); + // storageRoot + final Hash storageRoot = Hash.wrap(in.readBytes32()); + // final Hash codeHash = Hash.wrap(in.readBytes32()); + + in.leaveList(); + return !storageRoot.equals(Hash.EMPTY_TRIE_HASH); + } + + private BesuController createController() { + try { + // Set some defaults + return parentCommand + .besuCommand + .setupControllerBuilder() + .miningParameters(MiningConfiguration.MINING_DISABLED) + .build(); + } catch (final Exception e) { + throw new CommandLine.ExecutionException(spec.commandLine(), e.getMessage(), e); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/BonsaiFlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/BonsaiFlatDbStrategy.java index d0330ed2442..d7404005b40 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/BonsaiFlatDbStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/BonsaiFlatDbStrategy.java @@ -136,7 +136,8 @@ protected Stream> storageToPairStream( } @Override - protected Stream> storageToPairStream( + // TODO: this should probably remain protected, public for now to enable trie rebuilding + public Stream> storageToPairStream( final SegmentedKeyValueStorage storage, final Hash accountHash, final Bytes startKeyHash, @@ -164,7 +165,8 @@ protected Stream> accountsToPairStream( } @Override - protected Stream> accountsToPairStream( + // TODO: this should probably remain protected, public for now to enable trie rebuilding + public Stream> accountsToPairStream( final SegmentedKeyValueStorage storage, final Bytes startKeyHash) { return storage .streamFromKey(ACCOUNT_INFO_STATE, startKeyHash.toArrayUnsafe()) From 30c421f4cf556cb6dafae8a413008a1aef794b56 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Mon, 2 Dec 2024 14:21:31 -0800 Subject: [PATCH 02/15] added unit test for state rebuild command Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 222 +++++++++++------- .../RebuildBonsaiStateTrieSubCommandTest.java | 77 ++++++ 2 files changed, 211 insertions(+), 88 deletions(-) create mode 100644 besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index 29b1631ba02..e940c9e417e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.cli.util.VersionProvider; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningConfiguration; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPInput; @@ -28,7 +29,8 @@ import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.patricia.StoredNodeFactory; -import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; import java.util.function.Function; @@ -68,100 +70,144 @@ public void run() { // spec.commandLine().usage(System.out); try (final BesuController controller = createController()) { - var storageProvider = controller.getStorageProvider(); + var storageConfig = controller.getDataStorageConfiguration(); + + if (!storageConfig.getDataStorageFormat().equals(DataStorageFormat.BONSAI)) { + LOG.error("Data Storage format is not BONSAI. Refusing to rebuild state trie."); + System.exit(-1); + } - // TODO: assuming bonsai, fix or qualify var worldStateStorage = (BonsaiWorldStateKeyValueStorage) - storageProvider.createWorldStateStorage( - DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); - - var header = controller.getProtocolContext().getBlockchain().getChainHeadHeader(); - // TODO: add a check to ensure the flat db state root and block hash either match or are - // absent from state trie - - // truncate the TRIE_BRANCH_STORAGE column family, - // subsequently rewrite blockhash and state root after we rebuild the trie - worldStateStorage.clearTrie(); - - // rebuild the trie by inserting everything into a StoredMerklePatriciaTrie - // and incrementally (naively) commit after each account while streaming - // TODO: optimize to incrementally commit tx after a certain threshold - - final var wss = worldStateStorage.getComposedWorldStateStorage(); - final var accountTrie = - new StoredMerklePatriciaTrie<>( - new StoredNodeFactory<>( - (location, hash) -> worldStateStorage.getAccount(Hash.wrap(hash)), - Function.identity(), - Function.identity()), - MerkleTrie.EMPTY_TRIE_NODE_HASH); - - final var flatdb = worldStateStorage.getFlatDbStrategy(); - flatdb - .accountsToPairStream(wss, Bytes32.ZERO) - .forEach( - accountPair -> { - final SegmentedKeyValueStorageTransaction perAccountTx = - worldStateStorage.getComposedWorldStateStorage().startTransaction(); - - // if the account has storage, write the account storage trie values - if (isNonEmptyStorage(accountPair.getSecond())) { - // create account storage trie - var accountStorageTrie = - new StoredMerklePatriciaTrie<>( - new StoredNodeFactory<>( - (location, hash) -> - worldStateStorage.getAccountStorageTrieNode( - Hash.wrap(accountPair.getFirst()), location, hash), - Function.identity(), - Function.identity()), - MerkleTrie.EMPTY_TRIE_NODE_HASH); - - // put into account trie - flatdb - .storageToPairStream( - wss, - Hash.wrap(accountPair.getFirst()), - Bytes32.ZERO, - HASH_LAST, - Function.identity()) - .forEach( - storagePair -> { - accountStorageTrie.put(storagePair.getFirst(), storagePair.getSecond()); - }); - - // commit the account storage trie - accountStorageTrie.commit( - (location, hash, value) -> - perAccountTx.put( - TRIE_BRANCH_STORAGE, - Bytes.concatenate(accountPair.getFirst(), location).toArrayUnsafe(), - value.toArrayUnsafe())); - } - - // write the account info - accountTrie.put(accountPair.getFirst(), accountPair.getSecond()); - - // commit the account trie - accountTrie.commit( - (location, hash, value) -> - perAccountTx.put( - TRIE_BRANCH_STORAGE, location.toArrayUnsafe(), value.toArrayUnsafe())); - - LOG.info("committing accountHash {}", accountPair.getFirst()); - perAccountTx.commit(); + controller + .getStorageProvider() + .createWorldStateStorage(controller.getDataStorageConfiguration()); + + if (!worldStateStorage.getFlatDbMode().equals(FlatDbMode.FULL)) { + LOG.error("Database is not fully flattened. Refusing to rebuild state trie."); + System.exit(-1); + } + + final BlockHeader header = + controller.getProtocolContext().getBlockchain().getChainHeadHeader(); + + worldStateStorage + .getWorldStateRootHash() + // we want state root hash to either be empty or same the same as chain head + .filter(root -> !root.equals(header.getStateRoot())) + .ifPresent( + foundRoot -> { + LOG.error( + "Chain head {} does not match state root {}. Refusing to rebuild state trie.", + header.getStateRoot(), + foundRoot); + System.exit(-1); }); - // rewrite state root and blockhash - var tx = worldStateStorage.getComposedWorldStateStorage().startTransaction(); - tx.put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, header.getStateRoot().toArrayUnsafe()); - tx.put(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, header.getBlockHash().toArrayUnsafe()); - LOG.info("committing blockhash and stateroot {}", header.toLogString()); - tx.commit(); + // rebuild trie: + var newHash = rebuildTrie(worldStateStorage); + + // write state root and block hash from the header: + if (!header.getStateRoot().equals(newHash)) { + LOG.error( + "Catastrophic: calculated state root {} after state rebuild, was expecting {}.", + newHash, + header.getStateRoot()); + System.exit(-1); + } + writeStateRootAndBlockHash(header, worldStateStorage); } } + Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { + // truncate the TRIE_BRANCH_STORAGE column family, + // subsequently rewrite blockhash and state root after we rebuild the trie + worldStateStorage.clearTrie(); + + // rebuild the trie by inserting everything into a StoredMerklePatriciaTrie + // and incrementally (naively) commit after each account while streaming + // TODO: optimize to incrementally commit tx after a certain threshold + + final var wss = worldStateStorage.getComposedWorldStateStorage(); + final var accountTrie = + new StoredMerklePatriciaTrie<>( + new StoredNodeFactory<>( + // this may be inefficient, and we can read through an incrementally committing tx + // instead + worldStateStorage::getAccountStateTrieNode, + Function.identity(), + Function.identity()), + MerkleTrie.EMPTY_TRIE_NODE_HASH); + + final var flatdb = worldStateStorage.getFlatDbStrategy(); + flatdb + .accountsToPairStream(wss, Bytes32.ZERO) + .forEach( + accountPair -> { + final SegmentedKeyValueStorageTransaction perAccountTx = + worldStateStorage.getComposedWorldStateStorage().startTransaction(); + + // if the account has storage, write the account storage trie values + if (isNonEmptyStorage(accountPair.getSecond())) { + // create account storage trie + var accountStorageTrie = + new StoredMerklePatriciaTrie<>( + new StoredNodeFactory<>( + (location, hash) -> + worldStateStorage.getAccountStorageTrieNode( + Hash.wrap(accountPair.getFirst()), location, hash), + Function.identity(), + Function.identity()), + MerkleTrie.EMPTY_TRIE_NODE_HASH); + + // put into account trie + flatdb + .storageToPairStream( + wss, + Hash.wrap(accountPair.getFirst()), + Bytes32.ZERO, + HASH_LAST, + Function.identity()) + .forEach( + storagePair -> { + accountStorageTrie.put(storagePair.getFirst(), storagePair.getSecond()); + }); + + // commit the account storage trie + accountStorageTrie.commit( + (location, hash, value) -> + perAccountTx.put( + TRIE_BRANCH_STORAGE, + Bytes.concatenate(accountPair.getFirst(), location).toArrayUnsafe(), + value.toArrayUnsafe())); + } + + // write the account info + accountTrie.put(accountPair.getFirst(), accountPair.getSecond()); + + // commit the account trie + accountTrie.commit( + (location, hash, value) -> + perAccountTx.put( + TRIE_BRANCH_STORAGE, location.toArrayUnsafe(), value.toArrayUnsafe())); + + LOG.info("committing accountHash {}", accountPair.getFirst()); + perAccountTx.commit(); + }); + + // perhaps not the safest cast + return Hash.wrap(accountTrie.getRootHash()); + } + + void writeStateRootAndBlockHash( + final BlockHeader header, final BonsaiWorldStateKeyValueStorage worldStateStorage) { + var tx = worldStateStorage.getComposedWorldStateStorage().startTransaction(); + tx.put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, header.getStateRoot().toArrayUnsafe()); + tx.put(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, header.getBlockHash().toArrayUnsafe()); + LOG.info("committing blockhash and stateroot {}", header.toLogString()); + tx.commit(); + } + private boolean isNonEmptyStorage(final Bytes accountRLP) { final RLPInput in = RLP.input(accountRLP); in.enterList(); @@ -174,7 +220,7 @@ private boolean isNonEmptyStorage(final Bytes accountRLP) { final Hash storageRoot = Hash.wrap(in.readBytes32()); // final Hash codeHash = Hash.wrap(in.readBytes32()); - in.leaveList(); + // in.leaveList(); return !storageRoot.equals(Hash.EMPTY_TRIE_HASH); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java new file mode 100644 index 00000000000..d500840d9dd --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java @@ -0,0 +1,77 @@ +/* + * Copyright contributors to 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.cli.subcommands.storage; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.testutil.BlockTestUtil; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * This test only exercises bonsai worldstate since forest is essentially a no-op for moving the + * worldstate. + */ +public class RebuildBonsaiStateTrieSubCommandTest { + + Blockchain blockchain; + WorldStateArchive archive; + ProtocolContext protocolContext; + ProtocolSchedule protocolSchedule; + BlockchainSetupUtil blockchainSetupUtil; + RebuildBonsaiStateTrieSubCommand command = new RebuildBonsaiStateTrieSubCommand(); + + @BeforeEach + public void setup() throws Exception { + setupBonsaiBlockchain(); + blockchain = blockchainSetupUtil.getBlockchain(); + protocolContext = blockchainSetupUtil.getProtocolContext(); + protocolSchedule = blockchainSetupUtil.getProtocolSchedule(); + archive = blockchainSetupUtil.getWorldArchive(); + } + + void setupBonsaiBlockchain() { + blockchainSetupUtil = + BlockchainSetupUtil.createForEthashChain( + // Mainnet is a more robust test resource, but it takes upwards of 1 minute to generate + // BlockTestUtil.getMainnetResources(), + BlockTestUtil.getSnapTestChainResources(), DataStorageFormat.BONSAI); + blockchainSetupUtil.importAllBlocks( + HeaderValidationMode.LIGHT_DETACHED_ONLY, HeaderValidationMode.LIGHT); + } + + @Test + public void assertStateRootMatchesAfterRebuild() { + var worldstate = (BonsaiWorldState) archive.getMutable(); + var headRootHash = worldstate.rootHash(); + + // drop the trie: + worldstate.getWorldStateStorage().clearTrie(); + + // rebuild the trie: + var newHash = command.rebuildTrie(worldstate.getWorldStateStorage()); + + assertThat(newHash).isEqualTo(headRootHash); + } +} From 58829097b6d73d8d8f8b8974c9f9b1ae1d898026 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Tue, 3 Dec 2024 14:20:24 -0800 Subject: [PATCH 03/15] add to the storage subcommand Signed-off-by: garyschulte --- .../besu/cli/subcommands/storage/StorageSubCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java index e06e24f0c28..c04fae541f3 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java @@ -50,7 +50,8 @@ StorageSubCommand.RevertVariablesStorage.class, RocksDbSubCommand.class, TrieLogSubCommand.class, - RevertMetadataSubCommand.class + RevertMetadataSubCommand.class, + RebuildBonsaiStateTrieSubCommand.class }) public class StorageSubCommand implements Runnable { From 294149d580f7756190ccfe1ffa58bb4c541ef4aa Mon Sep 17 00:00:00 2001 From: garyschulte Date: Wed, 4 Dec 2024 10:42:55 -0800 Subject: [PATCH 04/15] add batching to prevent heap overflow Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 251 +++++++++++------- .../RebuildBonsaiStateTrieSubCommandTest.java | 7 +- 2 files changed, 159 insertions(+), 99 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index e940c9e417e..783b4c24e58 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -27,10 +27,14 @@ import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.flat.BonsaiFlatDbStrategy; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.patricia.StoredNodeFactory; import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; import java.util.function.Function; @@ -44,15 +48,14 @@ import picocli.CommandLine.ParentCommand; /** The revert metadata to v1 subcommand. */ -@Command( - name = "rebuild-bonsai-state-trie", - description = "Rebuilds bonsai state trie from flat database", - mixinStandardHelpOptions = true, +@Command(name = "rebuild-bonsai-state-trie", + description = "Rebuilds bonsai state trie from flat database", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) public class RebuildBonsaiStateTrieSubCommand implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(RebuildBonsaiStateTrieSubCommand.class); private static final Hash HASH_LAST = Hash.wrap(Bytes32.leftPad(Bytes.fromHexString("FF"), (byte) 0xFF)); + static final long FORCED_COMMIT_INTERVAL = 50_000L; @SuppressWarnings("unused") @ParentCommand @@ -63,7 +66,8 @@ public class RebuildBonsaiStateTrieSubCommand implements Runnable { private CommandLine.Model.CommandSpec spec; /** Default Constructor. */ - public RebuildBonsaiStateTrieSubCommand() {} + public RebuildBonsaiStateTrieSubCommand() { + } @Override public void run() { @@ -77,11 +81,9 @@ public void run() { System.exit(-1); } - var worldStateStorage = - (BonsaiWorldStateKeyValueStorage) - controller - .getStorageProvider() - .createWorldStateStorage(controller.getDataStorageConfiguration()); + var worldStateStorage = (BonsaiWorldStateKeyValueStorage) controller + .getStorageProvider() + .createWorldStateStorage(controller.getDataStorageConfiguration()); if (!worldStateStorage.getFlatDbMode().equals(FlatDbMode.FULL)) { LOG.error("Database is not fully flattened. Refusing to rebuild state trie."); @@ -91,28 +93,21 @@ public void run() { final BlockHeader header = controller.getProtocolContext().getBlockchain().getChainHeadHeader(); - worldStateStorage - .getWorldStateRootHash() + worldStateStorage.getWorldStateRootHash() // we want state root hash to either be empty or same the same as chain head - .filter(root -> !root.equals(header.getStateRoot())) - .ifPresent( - foundRoot -> { - LOG.error( - "Chain head {} does not match state root {}. Refusing to rebuild state trie.", - header.getStateRoot(), - foundRoot); - System.exit(-1); - }); + .filter(root -> !root.equals(header.getStateRoot())).ifPresent(foundRoot -> { + LOG.error("Chain head {} does not match state root {}. Refusing to rebuild state trie.", + header.getStateRoot(), foundRoot); + System.exit(-1); + }); // rebuild trie: var newHash = rebuildTrie(worldStateStorage); // write state root and block hash from the header: if (!header.getStateRoot().equals(newHash)) { - LOG.error( - "Catastrophic: calculated state root {} after state rebuild, was expecting {}.", - newHash, - header.getStateRoot()); + LOG.error("Catastrophic: calculated state root {} after state rebuild, was expecting {}.", + newHash, header.getStateRoot()); System.exit(-1); } writeStateRootAndBlockHash(header, worldStateStorage); @@ -129,78 +124,106 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { // TODO: optimize to incrementally commit tx after a certain threshold final var wss = worldStateStorage.getComposedWorldStateStorage(); - final var accountTrie = - new StoredMerklePatriciaTrie<>( - new StoredNodeFactory<>( - // this may be inefficient, and we can read through an incrementally committing tx - // instead - worldStateStorage::getAccountStateTrieNode, - Function.identity(), - Function.identity()), - MerkleTrie.EMPTY_TRIE_NODE_HASH); + final var accountTrie = new StoredMerklePatriciaTrie<>(new StoredNodeFactory<>( + // this may be inefficient, and we can read through an incrementally committing tx + // instead + worldStateStorage::getAccountStateTrieNode, Function.identity(), Function.identity()), + MerkleTrie.EMPTY_TRIE_NODE_HASH); + + final var accountsTx = new WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); + final Runnable accountTrieCommit = () -> accountTrie.commit( + (loc, hash, value) -> accountsTx.put(TRIE_BRANCH_STORAGE, loc.toArrayUnsafe(), + value.toArrayUnsafe())); final var flatdb = worldStateStorage.getFlatDbStrategy(); - flatdb - .accountsToPairStream(wss, Bytes32.ZERO) - .forEach( - accountPair -> { - final SegmentedKeyValueStorageTransaction perAccountTx = - worldStateStorage.getComposedWorldStateStorage().startTransaction(); - - // if the account has storage, write the account storage trie values - if (isNonEmptyStorage(accountPair.getSecond())) { - // create account storage trie - var accountStorageTrie = - new StoredMerklePatriciaTrie<>( - new StoredNodeFactory<>( - (location, hash) -> - worldStateStorage.getAccountStorageTrieNode( - Hash.wrap(accountPair.getFirst()), location, hash), - Function.identity(), - Function.identity()), - MerkleTrie.EMPTY_TRIE_NODE_HASH); - - // put into account trie - flatdb - .storageToPairStream( - wss, - Hash.wrap(accountPair.getFirst()), - Bytes32.ZERO, - HASH_LAST, - Function.identity()) - .forEach( - storagePair -> { - accountStorageTrie.put(storagePair.getFirst(), storagePair.getSecond()); - }); - - // commit the account storage trie - accountStorageTrie.commit( - (location, hash, value) -> - perAccountTx.put( - TRIE_BRANCH_STORAGE, - Bytes.concatenate(accountPair.getFirst(), location).toArrayUnsafe(), - value.toArrayUnsafe())); - } - - // write the account info - accountTrie.put(accountPair.getFirst(), accountPair.getSecond()); - - // commit the account trie - accountTrie.commit( - (location, hash, value) -> - perAccountTx.put( - TRIE_BRANCH_STORAGE, location.toArrayUnsafe(), value.toArrayUnsafe())); - - LOG.info("committing accountHash {}", accountPair.getFirst()); - perAccountTx.commit(); - }); - - // perhaps not the safest cast + final var accountsIterator = flatdb.accountsToPairStream(wss, Bytes32.ZERO).iterator(); + + long accountsCount = 0L; + while (accountsIterator.hasNext()) { + var accountPair = accountsIterator.next(); + + // if the account has non-empty storage, write the account storage trie values + Hash stateTrieHash = extractStateRootHash(accountPair.getSecond()); + if (! Hash.EMPTY_TRIE_HASH.equals(stateTrieHash)) { + var newStateTrieHash = rebuildAccountTrie( + flatdb, worldStateStorage, Hash.wrap(accountPair.getFirst())); + if (!newStateTrieHash.equals(stateTrieHash)) { + throw new RuntimeException("calculated state trie hash does not match account state hash"); + } + + } + + // write the account info + accountTrie.put(accountPair.getFirst(), accountPair.getSecond()); + + if (accountsCount++ % FORCED_COMMIT_INTERVAL == 0) { + LOG.info("committing account trie at account {}", accountPair.getFirst()); + + // commit the account trie if we have exceeded the forced commit interval + accountTrieCommit.run(); + accountsTx.commitAndReopen(); + + } + } + + // final commit + accountTrieCommit.run(); + accountsTx.commit(); + + // return the new state trie root hash return Hash.wrap(accountTrie.getRootHash()); } - void writeStateRootAndBlockHash( - final BlockHeader header, final BonsaiWorldStateKeyValueStorage worldStateStorage) { + /** + * Rebuild the account storage trie for a single account hash + * @param flatdb reference to the flat db + * @param worldStateStorage reference to the worldstate storage + * @param accountHash the hash of the account we need to rebuild contract storage for + */ + Hash rebuildAccountTrie( + final BonsaiFlatDbStrategy flatdb, + final BonsaiWorldStateKeyValueStorage worldStateStorage, + final Hash accountHash) { + + var accountStorageTx = + new WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); + var wss = worldStateStorage.getComposedWorldStateStorage(); + + // create account storage trie + var accountStorageTrie = new StoredMerklePatriciaTrie<>(new StoredNodeFactory<>( + (location, hash) -> worldStateStorage.getAccountStorageTrieNode( + accountHash, location, hash), Function.identity(), + Function.identity()), MerkleTrie.EMPTY_TRIE_NODE_HASH); + + Runnable accountStorageCommit = () -> accountStorageTrie.commit( + (location, hash, value) -> accountStorageTx.put(TRIE_BRANCH_STORAGE, + Bytes.concatenate(accountHash, location).toArrayUnsafe(), + value.toArrayUnsafe())); + + // put into account trie + var accountStorageIterator = flatdb + .storageToPairStream(wss, Hash.wrap(accountHash), Bytes32.ZERO, HASH_LAST, + Function.identity()) + .iterator(); + long accountStorageCount = 0L; + while (accountStorageIterator.hasNext()) { + var storagePair = accountStorageIterator.next(); + accountStorageTrie.put(storagePair.getFirst(), storagePair.getSecond()); + + // commit the account storage trie + if (accountStorageCount++ % FORCED_COMMIT_INTERVAL == 0) { + accountStorageCommit.run(); + accountStorageTx.commitAndReopen(); + } + } + accountStorageCommit.run(); + accountStorageTx.commit(); + + return Hash.wrap(accountStorageTrie.getRootHash()); + } + + void writeStateRootAndBlockHash(final BlockHeader header, + final BonsaiWorldStateKeyValueStorage worldStateStorage) { var tx = worldStateStorage.getComposedWorldStateStorage().startTransaction(); tx.put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, header.getStateRoot().toArrayUnsafe()); tx.put(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, header.getBlockHash().toArrayUnsafe()); @@ -208,7 +231,7 @@ void writeStateRootAndBlockHash( tx.commit(); } - private boolean isNonEmptyStorage(final Bytes accountRLP) { + private Hash extractStateRootHash(final Bytes accountRLP) { final RLPInput in = RLP.input(accountRLP); in.enterList(); @@ -221,14 +244,13 @@ private boolean isNonEmptyStorage(final Bytes accountRLP) { // final Hash codeHash = Hash.wrap(in.readBytes32()); // in.leaveList(); - return !storageRoot.equals(Hash.EMPTY_TRIE_HASH); + return storageRoot; } private BesuController createController() { try { // Set some defaults - return parentCommand - .besuCommand + return parentCommand.besuCommand .setupControllerBuilder() .miningParameters(MiningConfiguration.MINING_DISABLED) .build(); @@ -236,4 +258,41 @@ private BesuController createController() { throw new CommandLine.ExecutionException(spec.commandLine(), e.getMessage(), e); } } + + static class WrappedTransaction implements SegmentedKeyValueStorageTransaction { + + private final SegmentedKeyValueStorage storage; + private SegmentedKeyValueStorageTransaction intervalTx; + + WrappedTransaction(final SegmentedKeyValueStorage storage) { + this.storage = storage; + this.intervalTx = storage.startTransaction(); + } + + @Override + public void put(final SegmentIdentifier segmentIdentifier, final byte[] key, + final byte[] value) { + intervalTx.put(segmentIdentifier, key, value); + } + + @Override + public void remove(final SegmentIdentifier segmentIdentifier, final byte[] key) { + intervalTx.remove(segmentIdentifier, key); + } + + @Override + public void commit() throws StorageException { + intervalTx.commit(); + } + + public void commitAndReopen() throws StorageException { + commit(); + intervalTx = storage.startTransaction(); + } + + @Override + public void rollback() { + throw new RuntimeException("WrappedTransaction can not completely rollback."); + } + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java index d500840d9dd..fb37ea9a1b4 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java @@ -55,10 +55,11 @@ void setupBonsaiBlockchain() { blockchainSetupUtil = BlockchainSetupUtil.createForEthashChain( // Mainnet is a more robust test resource, but it takes upwards of 1 minute to generate - // BlockTestUtil.getMainnetResources(), - BlockTestUtil.getSnapTestChainResources(), DataStorageFormat.BONSAI); + BlockTestUtil.getMainnetResources(), + //BlockTestUtil.getSnapTestChainResources(), /* snap only has a + DataStorageFormat.BONSAI); blockchainSetupUtil.importAllBlocks( - HeaderValidationMode.LIGHT_DETACHED_ONLY, HeaderValidationMode.LIGHT); + HeaderValidationMode.NONE, HeaderValidationMode.NONE); } @Test From 162e9db573ca9a9faf5be2a4a3229ee85a7129e9 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Wed, 4 Dec 2024 15:56:26 -0800 Subject: [PATCH 05/15] spotless and additional error detail Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 144 +++++++++++------- .../RebuildBonsaiStateTrieSubCommandTest.java | 5 +- 2 files changed, 90 insertions(+), 59 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index 783b4c24e58..b74057cab3f 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -41,6 +41,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import picocli.CommandLine; @@ -48,8 +49,10 @@ import picocli.CommandLine.ParentCommand; /** The revert metadata to v1 subcommand. */ -@Command(name = "rebuild-bonsai-state-trie", - description = "Rebuilds bonsai state trie from flat database", mixinStandardHelpOptions = true, +@Command( + name = "rebuild-bonsai-state-trie", + description = "Rebuilds bonsai state trie from flat database", + mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) public class RebuildBonsaiStateTrieSubCommand implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(RebuildBonsaiStateTrieSubCommand.class); @@ -66,8 +69,7 @@ public class RebuildBonsaiStateTrieSubCommand implements Runnable { private CommandLine.Model.CommandSpec spec; /** Default Constructor. */ - public RebuildBonsaiStateTrieSubCommand() { - } + public RebuildBonsaiStateTrieSubCommand() {} @Override public void run() { @@ -81,9 +83,11 @@ public void run() { System.exit(-1); } - var worldStateStorage = (BonsaiWorldStateKeyValueStorage) controller - .getStorageProvider() - .createWorldStateStorage(controller.getDataStorageConfiguration()); + var worldStateStorage = + (BonsaiWorldStateKeyValueStorage) + controller + .getStorageProvider() + .createWorldStateStorage(controller.getDataStorageConfiguration()); if (!worldStateStorage.getFlatDbMode().equals(FlatDbMode.FULL)) { LOG.error("Database is not fully flattened. Refusing to rebuild state trie."); @@ -93,21 +97,28 @@ public void run() { final BlockHeader header = controller.getProtocolContext().getBlockchain().getChainHeadHeader(); - worldStateStorage.getWorldStateRootHash() + worldStateStorage + .getWorldStateRootHash() // we want state root hash to either be empty or same the same as chain head - .filter(root -> !root.equals(header.getStateRoot())).ifPresent(foundRoot -> { - LOG.error("Chain head {} does not match state root {}. Refusing to rebuild state trie.", - header.getStateRoot(), foundRoot); - System.exit(-1); - }); + .filter(root -> !root.equals(header.getStateRoot())) + .ifPresent( + foundRoot -> { + LOG.error( + "Chain head {} does not match state root {}. Refusing to rebuild state trie.", + header.getStateRoot(), + foundRoot); + System.exit(-1); + }); // rebuild trie: var newHash = rebuildTrie(worldStateStorage); // write state root and block hash from the header: if (!header.getStateRoot().equals(newHash)) { - LOG.error("Catastrophic: calculated state root {} after state rebuild, was expecting {}.", - newHash, header.getStateRoot()); + LOG.error( + "Catastrophic: calculated state root {} after state rebuild, was expecting {}.", + newHash, + header.getStateRoot()); System.exit(-1); } writeStateRootAndBlockHash(header, worldStateStorage); @@ -124,16 +135,23 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { // TODO: optimize to incrementally commit tx after a certain threshold final var wss = worldStateStorage.getComposedWorldStateStorage(); - final var accountTrie = new StoredMerklePatriciaTrie<>(new StoredNodeFactory<>( - // this may be inefficient, and we can read through an incrementally committing tx - // instead - worldStateStorage::getAccountStateTrieNode, Function.identity(), Function.identity()), - MerkleTrie.EMPTY_TRIE_NODE_HASH); + final var accountTrie = + new StoredMerklePatriciaTrie<>( + new StoredNodeFactory<>( + // this may be inefficient, and we can read through an incrementally committing tx + // instead + worldStateStorage::getAccountStateTrieNode, + Function.identity(), + Function.identity()), + MerkleTrie.EMPTY_TRIE_NODE_HASH); final var accountsTx = new WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); - final Runnable accountTrieCommit = () -> accountTrie.commit( - (loc, hash, value) -> accountsTx.put(TRIE_BRANCH_STORAGE, loc.toArrayUnsafe(), - value.toArrayUnsafe())); + final Runnable accountTrieCommit = + () -> + accountTrie.commit( + (loc, hash, value) -> + accountsTx.put( + TRIE_BRANCH_STORAGE, loc.toArrayUnsafe(), value.toArrayUnsafe())); final var flatdb = worldStateStorage.getFlatDbStrategy(); final var accountsIterator = flatdb.accountsToPairStream(wss, Bytes32.ZERO).iterator(); @@ -143,14 +161,16 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { var accountPair = accountsIterator.next(); // if the account has non-empty storage, write the account storage trie values - Hash stateTrieHash = extractStateRootHash(accountPair.getSecond()); - if (! Hash.EMPTY_TRIE_HASH.equals(stateTrieHash)) { - var newStateTrieHash = rebuildAccountTrie( - flatdb, worldStateStorage, Hash.wrap(accountPair.getFirst())); - if (!newStateTrieHash.equals(stateTrieHash)) { - throw new RuntimeException("calculated state trie hash does not match account state hash"); + var acctState = extractStateRootHash(accountPair.getSecond()); + if (!Hash.EMPTY_TRIE_HASH.equals(acctState.storageRoot)) { + var newStateTrieHash = + rebuildAccountTrie(flatdb, worldStateStorage, Hash.wrap(accountPair.getFirst())); + if (!newStateTrieHash.equals(acctState.storageRoot)) { + throw new RuntimeException( + String.format( + "accountHash %s calculated state root %s does not match account state root %s", + accountPair.getFirst(), newStateTrieHash, acctState.storageRoot)); } - } // write the account info @@ -162,7 +182,6 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { // commit the account trie if we have exceeded the forced commit interval accountTrieCommit.run(); accountsTx.commitAndReopen(); - } } @@ -176,6 +195,7 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { /** * Rebuild the account storage trie for a single account hash + * * @param flatdb reference to the flat db * @param worldStateStorage reference to the worldstate storage * @param accountHash the hash of the account we need to rebuild contract storage for @@ -185,26 +205,34 @@ Hash rebuildAccountTrie( final BonsaiWorldStateKeyValueStorage worldStateStorage, final Hash accountHash) { - var accountStorageTx = - new WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); + var accountStorageTx = new WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); var wss = worldStateStorage.getComposedWorldStateStorage(); // create account storage trie - var accountStorageTrie = new StoredMerklePatriciaTrie<>(new StoredNodeFactory<>( - (location, hash) -> worldStateStorage.getAccountStorageTrieNode( - accountHash, location, hash), Function.identity(), - Function.identity()), MerkleTrie.EMPTY_TRIE_NODE_HASH); - - Runnable accountStorageCommit = () -> accountStorageTrie.commit( - (location, hash, value) -> accountStorageTx.put(TRIE_BRANCH_STORAGE, - Bytes.concatenate(accountHash, location).toArrayUnsafe(), - value.toArrayUnsafe())); + var accountStorageTrie = + new StoredMerklePatriciaTrie<>( + new StoredNodeFactory<>( + (location, hash) -> + worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + Function.identity(), + Function.identity()), + MerkleTrie.EMPTY_TRIE_NODE_HASH); + + Runnable accountStorageCommit = + () -> + accountStorageTrie.commit( + (location, hash, value) -> + accountStorageTx.put( + TRIE_BRANCH_STORAGE, + Bytes.concatenate(accountHash, location).toArrayUnsafe(), + value.toArrayUnsafe())); // put into account trie - var accountStorageIterator = flatdb - .storageToPairStream(wss, Hash.wrap(accountHash), Bytes32.ZERO, HASH_LAST, - Function.identity()) - .iterator(); + var accountStorageIterator = + flatdb + .storageToPairStream( + wss, Hash.wrap(accountHash), Bytes32.ZERO, HASH_LAST, Function.identity()) + .iterator(); long accountStorageCount = 0L; while (accountStorageIterator.hasNext()) { var storagePair = accountStorageIterator.next(); @@ -222,8 +250,8 @@ Hash rebuildAccountTrie( return Hash.wrap(accountStorageTrie.getRootHash()); } - void writeStateRootAndBlockHash(final BlockHeader header, - final BonsaiWorldStateKeyValueStorage worldStateStorage) { + void writeStateRootAndBlockHash( + final BlockHeader header, final BonsaiWorldStateKeyValueStorage worldStateStorage) { var tx = worldStateStorage.getComposedWorldStateStorage().startTransaction(); tx.put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, header.getStateRoot().toArrayUnsafe()); tx.put(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, header.getBlockHash().toArrayUnsafe()); @@ -231,26 +259,30 @@ void writeStateRootAndBlockHash(final BlockHeader header, tx.commit(); } - private Hash extractStateRootHash(final Bytes accountRLP) { + record SimpleAccountState(long nonce, UInt256 balance, Hash storageRoot, Hash codeHash) {} + + private SimpleAccountState extractStateRootHash(final Bytes accountRLP) { final RLPInput in = RLP.input(accountRLP); in.enterList(); // nonce - in.readLongScalar(); + final long nonce = in.readLongScalar(); // balance - in.readUInt256Scalar(); + final UInt256 balance = in.readUInt256Scalar(); // storageRoot final Hash storageRoot = Hash.wrap(in.readBytes32()); - // final Hash codeHash = Hash.wrap(in.readBytes32()); + // code hash + final Hash codeHash = Hash.wrap(in.readBytes32()); // in.leaveList(); - return storageRoot; + return new SimpleAccountState(nonce, balance, storageRoot, codeHash); } private BesuController createController() { try { // Set some defaults - return parentCommand.besuCommand + return parentCommand + .besuCommand .setupControllerBuilder() .miningParameters(MiningConfiguration.MINING_DISABLED) .build(); @@ -270,8 +302,8 @@ static class WrappedTransaction implements SegmentedKeyValueStorageTransaction { } @Override - public void put(final SegmentIdentifier segmentIdentifier, final byte[] key, - final byte[] value) { + public void put( + final SegmentIdentifier segmentIdentifier, final byte[] key, final byte[] value) { intervalTx.put(segmentIdentifier, key, value); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java index fb37ea9a1b4..6c418669c88 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java @@ -56,10 +56,9 @@ void setupBonsaiBlockchain() { BlockchainSetupUtil.createForEthashChain( // Mainnet is a more robust test resource, but it takes upwards of 1 minute to generate BlockTestUtil.getMainnetResources(), - //BlockTestUtil.getSnapTestChainResources(), /* snap only has a + // BlockTestUtil.getSnapTestChainResources(), /* snap only has a DataStorageFormat.BONSAI); - blockchainSetupUtil.importAllBlocks( - HeaderValidationMode.NONE, HeaderValidationMode.NONE); + blockchainSetupUtil.importAllBlocks(HeaderValidationMode.NONE, HeaderValidationMode.NONE); } @Test From 69c4518dfd0b2bacdbb42a3852e47f71f961ef83 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Wed, 4 Dec 2024 17:22:19 -0800 Subject: [PATCH 06/15] fix encode trie value for account state Signed-off-by: garyschulte --- .../subcommands/storage/RebuildBonsaiStateTrieSubCommand.java | 3 ++- .../storage/RebuildBonsaiStateTrieSubCommandTest.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index b74057cab3f..5d1e1d90c67 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -17,6 +17,7 @@ import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY; import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldView.encodeTrieValue; import org.hyperledger.besu.cli.util.VersionProvider; import org.hyperledger.besu.controller.BesuController; @@ -236,7 +237,7 @@ Hash rebuildAccountTrie( long accountStorageCount = 0L; while (accountStorageIterator.hasNext()) { var storagePair = accountStorageIterator.next(); - accountStorageTrie.put(storagePair.getFirst(), storagePair.getSecond()); + accountStorageTrie.put(storagePair.getFirst(), encodeTrieValue(storagePair.getSecond())); // commit the account storage trie if (accountStorageCount++ % FORCED_COMMIT_INTERVAL == 0) { diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java index 6c418669c88..c844884b7fc 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java @@ -55,8 +55,8 @@ void setupBonsaiBlockchain() { blockchainSetupUtil = BlockchainSetupUtil.createForEthashChain( // Mainnet is a more robust test resource, but it takes upwards of 1 minute to generate - BlockTestUtil.getMainnetResources(), - // BlockTestUtil.getSnapTestChainResources(), /* snap only has a + //BlockTestUtil.getMainnetResources(), + BlockTestUtil.getSnapTestChainResources(), DataStorageFormat.BONSAI); blockchainSetupUtil.importAllBlocks(HeaderValidationMode.NONE, HeaderValidationMode.NONE); } From e54addc13520a0d30575d837aef3593afe47fb1a Mon Sep 17 00:00:00 2001 From: garyschulte Date: Wed, 4 Dec 2024 17:49:25 -0800 Subject: [PATCH 07/15] GC merkle tries on interim commits, spotless. Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 57 ++++++++++++------- .../RebuildBonsaiStateTrieSubCommandTest.java | 5 +- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index 5d1e1d90c67..3fb0b2a9f27 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -38,6 +38,7 @@ import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; +import java.util.function.Consumer; import java.util.function.Function; import org.apache.tuweni.bytes.Bytes; @@ -133,10 +134,9 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { // rebuild the trie by inserting everything into a StoredMerklePatriciaTrie // and incrementally (naively) commit after each account while streaming - // TODO: optimize to incrementally commit tx after a certain threshold final var wss = worldStateStorage.getComposedWorldStateStorage(); - final var accountTrie = + var accountTrie = new StoredMerklePatriciaTrie<>( new StoredNodeFactory<>( // this may be inefficient, and we can read through an incrementally committing tx @@ -147,9 +147,9 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { MerkleTrie.EMPTY_TRIE_NODE_HASH); final var accountsTx = new WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); - final Runnable accountTrieCommit = - () -> - accountTrie.commit( + final Consumer> accountTrieCommit = + (trie) -> + trie.commit( (loc, hash, value) -> accountsTx.put( TRIE_BRANCH_STORAGE, loc.toArrayUnsafe(), value.toArrayUnsafe())); @@ -181,13 +181,25 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { LOG.info("committing account trie at account {}", accountPair.getFirst()); // commit the account trie if we have exceeded the forced commit interval - accountTrieCommit.run(); + accountTrieCommit.accept(accountTrie); accountsTx.commitAndReopen(); + + // new trie with new root, GC trie nodes + accountTrie = + new StoredMerklePatriciaTrie<>( + new StoredNodeFactory<>( + // this may be inefficient, and we can read through an incrementally committing + // tx + // instead + worldStateStorage::getAccountStateTrieNode, + Function.identity(), + Function.identity()), + accountTrie.getRootHash()); } } // final commit - accountTrieCommit.run(); + accountTrieCommit.accept(accountTrie); accountsTx.commit(); // return the new state trie root hash @@ -209,19 +221,22 @@ Hash rebuildAccountTrie( var accountStorageTx = new WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); var wss = worldStateStorage.getComposedWorldStateStorage(); + Function> newAccountStorageTrie = + (rootHash) -> + new StoredMerklePatriciaTrie<>( + new StoredNodeFactory<>( + (location, hash) -> + worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + Function.identity(), + Function.identity()), + rootHash); + // create account storage trie - var accountStorageTrie = - new StoredMerklePatriciaTrie<>( - new StoredNodeFactory<>( - (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), - Function.identity(), - Function.identity()), - MerkleTrie.EMPTY_TRIE_NODE_HASH); + var accountStorageTrie = newAccountStorageTrie.apply(MerkleTrie.EMPTY_TRIE_NODE_HASH); - Runnable accountStorageCommit = - () -> - accountStorageTrie.commit( + Consumer> accountStorageCommit = + (trie) -> + trie.commit( (location, hash, value) -> accountStorageTx.put( TRIE_BRANCH_STORAGE, @@ -241,11 +256,13 @@ Hash rebuildAccountTrie( // commit the account storage trie if (accountStorageCount++ % FORCED_COMMIT_INTERVAL == 0) { - accountStorageCommit.run(); + accountStorageCommit.accept(accountStorageTrie); accountStorageTx.commitAndReopen(); + // new trie with new root, GC trie nodes + accountStorageTrie = newAccountStorageTrie.apply(accountStorageTrie.getRootHash()); } } - accountStorageCommit.run(); + accountStorageCommit.accept(accountStorageTrie); accountStorageTx.commit(); return Hash.wrap(accountStorageTrie.getRootHash()); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java index c844884b7fc..3a74e249f1e 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java @@ -55,9 +55,8 @@ void setupBonsaiBlockchain() { blockchainSetupUtil = BlockchainSetupUtil.createForEthashChain( // Mainnet is a more robust test resource, but it takes upwards of 1 minute to generate - //BlockTestUtil.getMainnetResources(), - BlockTestUtil.getSnapTestChainResources(), - DataStorageFormat.BONSAI); + // BlockTestUtil.getMainnetResources(), + BlockTestUtil.getSnapTestChainResources(), DataStorageFormat.BONSAI); blockchainSetupUtil.importAllBlocks(HeaderValidationMode.NONE, HeaderValidationMode.NONE); } From 187e884c761abf22f359131a8755ea07216b4634 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Thu, 5 Dec 2024 10:20:10 -0800 Subject: [PATCH 08/15] add more logging, drop forced commit interval to 5k Signed-off-by: garyschulte --- .../subcommands/storage/RebuildBonsaiStateTrieSubCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index 3fb0b2a9f27..fc30eda3197 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -60,7 +60,7 @@ public class RebuildBonsaiStateTrieSubCommand implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(RebuildBonsaiStateTrieSubCommand.class); private static final Hash HASH_LAST = Hash.wrap(Bytes32.leftPad(Bytes.fromHexString("FF"), (byte) 0xFF)); - static final long FORCED_COMMIT_INTERVAL = 50_000L; + static final long FORCED_COMMIT_INTERVAL = 5_000L; @SuppressWarnings("unused") @ParentCommand @@ -256,6 +256,7 @@ Hash rebuildAccountTrie( // commit the account storage trie if (accountStorageCount++ % FORCED_COMMIT_INTERVAL == 0) { + LOG.info("interim commit for account hash {}, at {}", accountHash, storagePair.getFirst()); accountStorageCommit.accept(accountStorageTrie); accountStorageTx.commitAndReopen(); // new trie with new root, GC trie nodes From 210fdae2819bae792f3112dcbcf27914c263b73a Mon Sep 17 00:00:00 2001 From: garyschulte Date: Thu, 5 Dec 2024 10:22:55 -0800 Subject: [PATCH 09/15] prefix rather than postfix eval for counter increment Signed-off-by: garyschulte --- .../subcommands/storage/RebuildBonsaiStateTrieSubCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index fc30eda3197..f2d6375e5da 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -177,7 +177,7 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { // write the account info accountTrie.put(accountPair.getFirst(), accountPair.getSecond()); - if (accountsCount++ % FORCED_COMMIT_INTERVAL == 0) { + if (++accountsCount % FORCED_COMMIT_INTERVAL == 0) { LOG.info("committing account trie at account {}", accountPair.getFirst()); // commit the account trie if we have exceeded the forced commit interval @@ -255,7 +255,7 @@ Hash rebuildAccountTrie( accountStorageTrie.put(storagePair.getFirst(), encodeTrieValue(storagePair.getSecond())); // commit the account storage trie - if (accountStorageCount++ % FORCED_COMMIT_INTERVAL == 0) { + if (++accountStorageCount % FORCED_COMMIT_INTERVAL == 0) { LOG.info("interim commit for account hash {}, at {}", accountHash, storagePair.getFirst()); accountStorageCommit.accept(accountStorageTrie); accountStorageTx.commitAndReopen(); From 7559bfdc097fd8085e8d6ba43c6f77bd034908af Mon Sep 17 00:00:00 2001 From: garyschulte Date: Thu, 5 Dec 2024 16:38:19 -0800 Subject: [PATCH 10/15] use single transaction for both accounts and account storage Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 48 +++++++------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index f2d6375e5da..b1b71b30c15 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -60,7 +60,7 @@ public class RebuildBonsaiStateTrieSubCommand implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(RebuildBonsaiStateTrieSubCommand.class); private static final Hash HASH_LAST = Hash.wrap(Bytes32.leftPad(Bytes.fromHexString("FF"), (byte) 0xFF)); - static final long FORCED_COMMIT_INTERVAL = 5_000L; + static final long FORCED_COMMIT_INTERVAL = 50_000L; @SuppressWarnings("unused") @ParentCommand @@ -136,7 +136,7 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { // and incrementally (naively) commit after each account while streaming final var wss = worldStateStorage.getComposedWorldStateStorage(); - var accountTrie = + final var accountTrie = new StoredMerklePatriciaTrie<>( new StoredNodeFactory<>( // this may be inefficient, and we can read through an incrementally committing tx @@ -165,7 +165,8 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { var acctState = extractStateRootHash(accountPair.getSecond()); if (!Hash.EMPTY_TRIE_HASH.equals(acctState.storageRoot)) { var newStateTrieHash = - rebuildAccountTrie(flatdb, worldStateStorage, Hash.wrap(accountPair.getFirst())); + rebuildAccountTrie( + accountsTx, flatdb, worldStateStorage, Hash.wrap(accountPair.getFirst())); if (!newStateTrieHash.equals(acctState.storageRoot)) { throw new RuntimeException( String.format( @@ -183,18 +184,6 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { // commit the account trie if we have exceeded the forced commit interval accountTrieCommit.accept(accountTrie); accountsTx.commitAndReopen(); - - // new trie with new root, GC trie nodes - accountTrie = - new StoredMerklePatriciaTrie<>( - new StoredNodeFactory<>( - // this may be inefficient, and we can read through an incrementally committing - // tx - // instead - worldStateStorage::getAccountStateTrieNode, - Function.identity(), - Function.identity()), - accountTrie.getRootHash()); } } @@ -214,31 +203,28 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { * @param accountHash the hash of the account we need to rebuild contract storage for */ Hash rebuildAccountTrie( + final WrappedTransaction accountsTx, final BonsaiFlatDbStrategy flatdb, final BonsaiWorldStateKeyValueStorage worldStateStorage, final Hash accountHash) { - var accountStorageTx = new WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); var wss = worldStateStorage.getComposedWorldStateStorage(); - Function> newAccountStorageTrie = - (rootHash) -> - new StoredMerklePatriciaTrie<>( - new StoredNodeFactory<>( - (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), - Function.identity(), - Function.identity()), - rootHash); - // create account storage trie - var accountStorageTrie = newAccountStorageTrie.apply(MerkleTrie.EMPTY_TRIE_NODE_HASH); + final var accountStorageTrie = + new StoredMerklePatriciaTrie<>( + new StoredNodeFactory<>( + (location, hash) -> + worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + Function.identity(), + Function.identity()), + MerkleTrie.EMPTY_TRIE_NODE_HASH); Consumer> accountStorageCommit = (trie) -> trie.commit( (location, hash, value) -> - accountStorageTx.put( + accountsTx.put( TRIE_BRANCH_STORAGE, Bytes.concatenate(accountHash, location).toArrayUnsafe(), value.toArrayUnsafe())); @@ -258,13 +244,11 @@ Hash rebuildAccountTrie( if (++accountStorageCount % FORCED_COMMIT_INTERVAL == 0) { LOG.info("interim commit for account hash {}, at {}", accountHash, storagePair.getFirst()); accountStorageCommit.accept(accountStorageTrie); - accountStorageTx.commitAndReopen(); - // new trie with new root, GC trie nodes - accountStorageTrie = newAccountStorageTrie.apply(accountStorageTrie.getRootHash()); + accountsTx.commitAndReopen(); } } accountStorageCommit.accept(accountStorageTrie); - accountStorageTx.commit(); + accountsTx.commitAndReopen(); return Hash.wrap(accountStorageTrie.getRootHash()); } From 8fa268a29f69d97cdb826cda1b05b0a424392f33 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Thu, 5 Dec 2024 17:00:14 -0800 Subject: [PATCH 11/15] don't use wrapped tx interim commits Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index b1b71b30c15..d0f7bad360c 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -38,7 +38,7 @@ import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import java.util.function.Function; import org.apache.tuweni.bytes.Bytes; @@ -146,13 +146,15 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { Function.identity()), MerkleTrie.EMPTY_TRIE_NODE_HASH); - final var accountsTx = new WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); - final Consumer> accountTrieCommit = - (trie) -> - trie.commit( - (loc, hash, value) -> - accountsTx.put( - TRIE_BRANCH_STORAGE, loc.toArrayUnsafe(), value.toArrayUnsafe())); + // final var accountsTx = new + // WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); + var accountsTx = wss.startTransaction(); + final BiConsumer, SegmentedKeyValueStorageTransaction> + accountTrieCommit = + (trie, tx) -> + trie.commit( + (loc, hash, value) -> + tx.put(TRIE_BRANCH_STORAGE, loc.toArrayUnsafe(), value.toArrayUnsafe())); final var flatdb = worldStateStorage.getFlatDbStrategy(); final var accountsIterator = flatdb.accountsToPairStream(wss, Bytes32.ZERO).iterator(); @@ -165,8 +167,7 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { var acctState = extractStateRootHash(accountPair.getSecond()); if (!Hash.EMPTY_TRIE_HASH.equals(acctState.storageRoot)) { var newStateTrieHash = - rebuildAccountTrie( - accountsTx, flatdb, worldStateStorage, Hash.wrap(accountPair.getFirst())); + rebuildAccountTrie(flatdb, worldStateStorage, Hash.wrap(accountPair.getFirst())); if (!newStateTrieHash.equals(acctState.storageRoot)) { throw new RuntimeException( String.format( @@ -182,13 +183,14 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { LOG.info("committing account trie at account {}", accountPair.getFirst()); // commit the account trie if we have exceeded the forced commit interval - accountTrieCommit.accept(accountTrie); - accountsTx.commitAndReopen(); + accountTrieCommit.accept(accountTrie, accountsTx); + accountsTx.commit(); + accountsTx = wss.startTransaction(); } } // final commit - accountTrieCommit.accept(accountTrie); + accountTrieCommit.accept(accountTrie, accountsTx); accountsTx.commit(); // return the new state trie root hash @@ -203,13 +205,12 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { * @param accountHash the hash of the account we need to rebuild contract storage for */ Hash rebuildAccountTrie( - final WrappedTransaction accountsTx, final BonsaiFlatDbStrategy flatdb, final BonsaiWorldStateKeyValueStorage worldStateStorage, final Hash accountHash) { var wss = worldStateStorage.getComposedWorldStateStorage(); - + var accountsStorageTx = wss.startTransaction(); // create account storage trie final var accountStorageTrie = new StoredMerklePatriciaTrie<>( @@ -220,14 +221,15 @@ Hash rebuildAccountTrie( Function.identity()), MerkleTrie.EMPTY_TRIE_NODE_HASH); - Consumer> accountStorageCommit = - (trie) -> - trie.commit( - (location, hash, value) -> - accountsTx.put( - TRIE_BRANCH_STORAGE, - Bytes.concatenate(accountHash, location).toArrayUnsafe(), - value.toArrayUnsafe())); + BiConsumer, SegmentedKeyValueStorageTransaction> + accountStorageCommit = + (trie, tx) -> + trie.commit( + (location, hash, value) -> + tx.put( + TRIE_BRANCH_STORAGE, + Bytes.concatenate(accountHash, location).toArrayUnsafe(), + value.toArrayUnsafe())); // put into account trie var accountStorageIterator = @@ -243,12 +245,13 @@ Hash rebuildAccountTrie( // commit the account storage trie if (++accountStorageCount % FORCED_COMMIT_INTERVAL == 0) { LOG.info("interim commit for account hash {}, at {}", accountHash, storagePair.getFirst()); - accountStorageCommit.accept(accountStorageTrie); - accountsTx.commitAndReopen(); + accountStorageCommit.accept(accountStorageTrie, accountsStorageTx); + accountsStorageTx.commit(); + accountsStorageTx = wss.startTransaction(); } } - accountStorageCommit.accept(accountStorageTrie); - accountsTx.commitAndReopen(); + accountStorageCommit.accept(accountStorageTrie, accountsStorageTx); + accountsStorageTx.commit(); return Hash.wrap(accountStorageTrie.getRootHash()); } From 31141eadf4d2147a555014ec18b3ad2b45a2bceb Mon Sep 17 00:00:00 2001 From: garyschulte Date: Thu, 5 Dec 2024 17:45:39 -0800 Subject: [PATCH 12/15] explicit stream close Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index d0f7bad360c..feec9e46986 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -157,7 +157,8 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { tx.put(TRIE_BRANCH_STORAGE, loc.toArrayUnsafe(), value.toArrayUnsafe())); final var flatdb = worldStateStorage.getFlatDbStrategy(); - final var accountsIterator = flatdb.accountsToPairStream(wss, Bytes32.ZERO).iterator(); + var accountsStream = flatdb.accountsToPairStream(wss, Bytes32.ZERO); + var accountsIterator = accountsStream.iterator(); long accountsCount = 0L; while (accountsIterator.hasNext()) { @@ -186,6 +187,11 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { accountTrieCommit.accept(accountTrie, accountsTx); accountsTx.commit(); accountsTx = wss.startTransaction(); + + // close and reopen the iterator ? + // accountsStream.close(); + // accountsStream = flatdb.accountsToPairStream(wss, accountPair.getFirst()); + // accountsIterator = accountsStream.iterator(); } } @@ -193,6 +199,9 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { accountTrieCommit.accept(accountTrie, accountsTx); accountsTx.commit(); + // explicit close of stream + accountsStream.close(); + // return the new state trie root hash return Hash.wrap(accountTrie.getRootHash()); } @@ -232,11 +241,11 @@ Hash rebuildAccountTrie( value.toArrayUnsafe())); // put into account trie - var accountStorageIterator = - flatdb - .storageToPairStream( - wss, Hash.wrap(accountHash), Bytes32.ZERO, HASH_LAST, Function.identity()) - .iterator(); + var accountStorageStream = flatdb + .storageToPairStream( + wss, Hash.wrap(accountHash), Bytes32.ZERO, HASH_LAST, Function.identity()); + var accountStorageIterator = accountStorageStream.iterator(); + long accountStorageCount = 0L; while (accountStorageIterator.hasNext()) { var storagePair = accountStorageIterator.next(); @@ -253,6 +262,7 @@ Hash rebuildAccountTrie( accountStorageCommit.accept(accountStorageTrie, accountsStorageTx); accountsStorageTx.commit(); + accountStorageStream.close(); return Hash.wrap(accountStorageTrie.getRootHash()); } From 3ccd531e015151607820a541aa28729af3ee481e Mon Sep 17 00:00:00 2001 From: garyschulte Date: Wed, 22 Jan 2025 13:01:19 -0800 Subject: [PATCH 13/15] add blockhash and stateroot override for rebuild bonsai trie subcommand Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 135 +++++++++--------- .../RebuildBonsaiStateTrieSubCommandTest.java | 13 ++ 2 files changed, 81 insertions(+), 67 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index feec9e46986..4890ccbf481 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -19,6 +19,7 @@ import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; import static org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldView.encodeTrieValue; +import org.hyperledger.besu.cli.DefaultCommandValues; import org.hyperledger.besu.cli.util.VersionProvider; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.datatypes.Hash; @@ -32,10 +33,7 @@ import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.patricia.StoredNodeFactory; import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; -import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; -import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; import java.util.function.BiConsumer; @@ -62,6 +60,16 @@ public class RebuildBonsaiStateTrieSubCommand implements Runnable { Hash.wrap(Bytes32.leftPad(Bytes.fromHexString("FF"), (byte) 0xFF)); static final long FORCED_COMMIT_INTERVAL = 50_000L; + @CommandLine.Option( + names = "--override-blockhash-and-stateroot", + paramLabel = DefaultCommandValues.MANDATORY_LONG_FORMAT_HELP, + description = + "when rebuilding the state trie, force the usage of the specified blockhash:stateroot. " + + "This will bypass block header and worldstate checks. " + + "e.g. --override-blockhash-and-stateroot=0xdeadbeef..deadbeef:0xc0ffee..coffee", + arity = "1..1") + private String overrideHashes = null; + @SuppressWarnings("unused") @ParentCommand private StorageSubCommand parentCommand; @@ -75,9 +83,7 @@ public RebuildBonsaiStateTrieSubCommand() {} @Override public void run() { - // spec.commandLine().usage(System.out); try (final BesuController controller = createController()) { - var storageConfig = controller.getDataStorageConfiguration(); if (!storageConfig.getDataStorageFormat().equals(DataStorageFormat.BONSAI)) { @@ -96,34 +102,47 @@ public void run() { System.exit(-1); } - final BlockHeader header = + final BlockHeader headHeader = controller.getProtocolContext().getBlockchain().getChainHeadHeader(); - worldStateStorage - .getWorldStateRootHash() - // we want state root hash to either be empty or same the same as chain head - .filter(root -> !root.equals(header.getStateRoot())) - .ifPresent( - foundRoot -> { - LOG.error( - "Chain head {} does not match state root {}. Refusing to rebuild state trie.", - header.getStateRoot(), - foundRoot); - System.exit(-1); - }); + BlockHashAndStateRoot blockHashAndStateRoot = BlockHashAndStateRoot.create(overrideHashes); + + if (blockHashAndStateRoot == null) { + worldStateStorage + .getWorldStateRootHash() + // we want state root hash to either be empty or same the same as chain head + .filter(root -> !root.equals(headHeader.getStateRoot())) + .ifPresent( + foundRoot -> { + LOG.error( + "Chain head {} does not match state root {}. Refusing to rebuild state trie.", + headHeader.getStateRoot(), + foundRoot); + System.exit(-1); + }); + blockHashAndStateRoot = + new BlockHashAndStateRoot(headHeader.getBlockHash(), headHeader.getStateRoot()); + } // rebuild trie: var newHash = rebuildTrie(worldStateStorage); // write state root and block hash from the header: - if (!header.getStateRoot().equals(newHash)) { + if (!blockHashAndStateRoot.stateRoot().equals(newHash)) { LOG.error( "Catastrophic: calculated state root {} after state rebuild, was expecting {}.", newHash, - header.getStateRoot()); - System.exit(-1); + blockHashAndStateRoot.stateRoot()); + if (overrideHashes == null) { + LOG.error( + "Refusing to write mismatched block hash and state root. Node needs manual intervention."); + System.exit(-1); + } else { + LOG.error( + "Writing the override block hash and state root, but node likely needs manual intervention."); + } } - writeStateRootAndBlockHash(header, worldStateStorage); + writeStateRootAndBlockHash(blockHashAndStateRoot, worldStateStorage); } } @@ -139,15 +158,11 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { final var accountTrie = new StoredMerklePatriciaTrie<>( new StoredNodeFactory<>( - // this may be inefficient, and we can read through an incrementally committing tx - // instead worldStateStorage::getAccountStateTrieNode, Function.identity(), Function.identity()), MerkleTrie.EMPTY_TRIE_NODE_HASH); - // final var accountsTx = new - // WrappedTransaction(worldStateStorage.getComposedWorldStateStorage()); var accountsTx = wss.startTransaction(); final BiConsumer, SegmentedKeyValueStorageTransaction> accountTrieCommit = @@ -187,11 +202,6 @@ Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { accountTrieCommit.accept(accountTrie, accountsTx); accountsTx.commit(); accountsTx = wss.startTransaction(); - - // close and reopen the iterator ? - // accountsStream.close(); - // accountsStream = flatdb.accountsToPairStream(wss, accountPair.getFirst()); - // accountsIterator = accountsStream.iterator(); } } @@ -241,8 +251,8 @@ Hash rebuildAccountTrie( value.toArrayUnsafe())); // put into account trie - var accountStorageStream = flatdb - .storageToPairStream( + var accountStorageStream = + flatdb.storageToPairStream( wss, Hash.wrap(accountHash), Bytes32.ZERO, HASH_LAST, Function.identity()); var accountStorageIterator = accountStorageStream.iterator(); @@ -267,11 +277,18 @@ Hash rebuildAccountTrie( } void writeStateRootAndBlockHash( - final BlockHeader header, final BonsaiWorldStateKeyValueStorage worldStateStorage) { + final BlockHashAndStateRoot blockHashAndStateRoot, + final BonsaiWorldStateKeyValueStorage worldStateStorage) { var tx = worldStateStorage.getComposedWorldStateStorage().startTransaction(); - tx.put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, header.getStateRoot().toArrayUnsafe()); - tx.put(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, header.getBlockHash().toArrayUnsafe()); - LOG.info("committing blockhash and stateroot {}", header.toLogString()); + tx.put( + TRIE_BRANCH_STORAGE, + WORLD_ROOT_HASH_KEY, + blockHashAndStateRoot.stateRoot().toArrayUnsafe()); + tx.put( + TRIE_BRANCH_STORAGE, + WORLD_BLOCK_HASH_KEY, + blockHashAndStateRoot.blockHash().toArrayUnsafe()); + LOG.info("committing blockhash and stateroot {}", blockHashAndStateRoot); tx.commit(); } @@ -307,40 +324,24 @@ private BesuController createController() { } } - static class WrappedTransaction implements SegmentedKeyValueStorageTransaction { - - private final SegmentedKeyValueStorage storage; - private SegmentedKeyValueStorageTransaction intervalTx; - - WrappedTransaction(final SegmentedKeyValueStorage storage) { - this.storage = storage; - this.intervalTx = storage.startTransaction(); - } - - @Override - public void put( - final SegmentIdentifier segmentIdentifier, final byte[] key, final byte[] value) { - intervalTx.put(segmentIdentifier, key, value); - } - - @Override - public void remove(final SegmentIdentifier segmentIdentifier, final byte[] key) { - intervalTx.remove(segmentIdentifier, key); - } + record BlockHashAndStateRoot(Hash blockHash, Hash stateRoot) { - @Override - public void commit() throws StorageException { - intervalTx.commit(); - } - - public void commitAndReopen() throws StorageException { - commit(); - intervalTx = storage.startTransaction(); + static BlockHashAndStateRoot create(final String comboString) { + if (comboString != null) { + var hashArray = comboString.split(":", 2); + try { + return new BlockHashAndStateRoot(Hash.fromHexString(hashArray[0]), + Hash.fromHexString(hashArray[1])); + } catch (Exception ex) { + System.err.println("failed parsing supplied block hash and stateroot " + ex.getMessage()); + } + } + return null; } @Override - public void rollback() { - throw new RuntimeException("WrappedTransaction can not completely rollback."); + public String toString() { + return blockHash.toHexString() + ":" + stateRoot.toHexString(); } } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java index 3a74e249f1e..0f5c9184c73 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java @@ -16,6 +16,8 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import org.hyperledger.besu.cli.subcommands.storage.RebuildBonsaiStateTrieSubCommand.BlockHashAndStateRoot; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; @@ -73,4 +75,15 @@ public void assertStateRootMatchesAfterRebuild() { assertThat(newHash).isEqualTo(headRootHash); } + + @Test + public void assertBlockHashAndStateRootParsing() { + + assertThat(BlockHashAndStateRoot.create("0xdeadbeef:0xdeadbeef")).isNull(); + + var mockVal = BlockHashAndStateRoot.create(Hash.EMPTY + ":" + Hash.EMPTY_TRIE_HASH); + assertThat(mockVal).isNotNull(); + assertThat(mockVal.blockHash()).isEqualTo(Hash.EMPTY); + assertThat(mockVal.stateRoot()).isEqualTo(Hash.EMPTY_TRIE_HASH); + } } From 57f5ee6549dd3cfa72c89347b11444338d045463 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Wed, 22 Jan 2025 13:31:04 -0800 Subject: [PATCH 14/15] additional testing for override Signed-off-by: garyschulte --- .../RebuildBonsaiStateTrieSubCommand.java | 51 +++++++++++-------- .../RebuildBonsaiStateTrieSubCommandTest.java | 24 +++++++++ 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index 4890ccbf481..e8a6e51be8d 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -39,6 +39,7 @@ import java.util.function.BiConsumer; import java.util.function.Function; +import graphql.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; @@ -102,12 +103,12 @@ public void run() { System.exit(-1); } - final BlockHeader headHeader = - controller.getProtocolContext().getBlockchain().getChainHeadHeader(); - BlockHashAndStateRoot blockHashAndStateRoot = BlockHashAndStateRoot.create(overrideHashes); if (blockHashAndStateRoot == null) { + final BlockHeader headHeader = + controller.getProtocolContext().getBlockchain().getChainHeadHeader(); + worldStateStorage .getWorldStateRootHash() // we want state root hash to either be empty or same the same as chain head @@ -123,27 +124,33 @@ public void run() { blockHashAndStateRoot = new BlockHashAndStateRoot(headHeader.getBlockHash(), headHeader.getStateRoot()); } + verifyAndRebuild(blockHashAndStateRoot, worldStateStorage); + } + } - // rebuild trie: - var newHash = rebuildTrie(worldStateStorage); - - // write state root and block hash from the header: - if (!blockHashAndStateRoot.stateRoot().equals(newHash)) { + @VisibleForTesting + protected void verifyAndRebuild( + final BlockHashAndStateRoot blockHashAndStateRoot, + final BonsaiWorldStateKeyValueStorage worldStateStorage) { + // rebuild trie: + var newHash = rebuildTrie(worldStateStorage); + + // write state root and block hash from the header: + if (!blockHashAndStateRoot.stateRoot().equals(newHash)) { + LOG.error( + "Catastrophic: calculated state root {} after state rebuild, was expecting {}.", + newHash, + blockHashAndStateRoot.stateRoot()); + if (overrideHashes == null) { LOG.error( - "Catastrophic: calculated state root {} after state rebuild, was expecting {}.", - newHash, - blockHashAndStateRoot.stateRoot()); - if (overrideHashes == null) { - LOG.error( - "Refusing to write mismatched block hash and state root. Node needs manual intervention."); - System.exit(-1); - } else { - LOG.error( - "Writing the override block hash and state root, but node likely needs manual intervention."); - } + "Refusing to write mismatched block hash and state root. Node needs manual intervention."); + System.exit(-1); + } else { + LOG.error( + "Writing the override block hash and state root, but node likely needs manual intervention."); } - writeStateRootAndBlockHash(blockHashAndStateRoot, worldStateStorage); } + writeStateRootAndBlockHash(blockHashAndStateRoot, worldStateStorage); } Hash rebuildTrie(final BonsaiWorldStateKeyValueStorage worldStateStorage) { @@ -330,8 +337,8 @@ static BlockHashAndStateRoot create(final String comboString) { if (comboString != null) { var hashArray = comboString.split(":", 2); try { - return new BlockHashAndStateRoot(Hash.fromHexString(hashArray[0]), - Hash.fromHexString(hashArray[1])); + return new BlockHashAndStateRoot( + Hash.fromHexString(hashArray[0]), Hash.fromHexString(hashArray[1])); } catch (Exception ex) { System.err.println("failed parsing supplied block hash and stateroot " + ex.getMessage()); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java index 0f5c9184c73..e08042857a5 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommandTest.java @@ -76,10 +76,34 @@ public void assertStateRootMatchesAfterRebuild() { assertThat(newHash).isEqualTo(headRootHash); } + @Test + public void assertOverrideBlockHashAndStateRoot() { + var worldstate = (BonsaiWorldState) archive.getMutable(); + var headRootHash = worldstate.rootHash(); + var override = new BlockHashAndStateRoot(Hash.ZERO, headRootHash); + + // rebuild the trie, should not complain about block hash mismatch: + command.verifyAndRebuild(override, worldstate.getWorldStateStorage()); + + // assert new storage: + var newStorage = worldstate.getWorldStateStorage(); + var newRoot = newStorage.getWorldStateRootHash(); + var newBlockHash = newStorage.getWorldStateBlockHash(); + + assertThat(newRoot).isPresent(); + assertThat(newRoot.get()).isEqualTo(override.stateRoot()); + assertThat(newBlockHash).isPresent(); + assertThat(newBlockHash.get()).isEqualTo(override.blockHash()); + } + @Test public void assertBlockHashAndStateRootParsing() { assertThat(BlockHashAndStateRoot.create("0xdeadbeef:0xdeadbeef")).isNull(); + assertThat(BlockHashAndStateRoot.create(Hash.EMPTY + "::" + Hash.EMPTY_TRIE_HASH)).isNull(); + assertThat(BlockHashAndStateRoot.create("" + Hash.EMPTY + Hash.EMPTY_TRIE_HASH)).isNull(); + assertThat(BlockHashAndStateRoot.create(Hash.EMPTY + ":" + Hash.EMPTY_TRIE_HASH + ":")) + .isNull(); var mockVal = BlockHashAndStateRoot.create(Hash.EMPTY + ":" + Hash.EMPTY_TRIE_HASH); assertThat(mockVal).isNotNull(); From 11194a826d39ca9acf0e2ddf8e435441c35879d9 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Wed, 22 Jan 2025 14:20:34 -0800 Subject: [PATCH 15/15] doc stuff Signed-off-by: garyschulte --- .../storage/RebuildBonsaiStateTrieSubCommand.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java index e8a6e51be8d..b15f9f871aa 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RebuildBonsaiStateTrieSubCommand.java @@ -67,7 +67,7 @@ public class RebuildBonsaiStateTrieSubCommand implements Runnable { description = "when rebuilding the state trie, force the usage of the specified blockhash:stateroot. " + "This will bypass block header and worldstate checks. " - + "e.g. --override-blockhash-and-stateroot=0xdeadbeef..deadbeef:0xc0ffee..coffee", + + "e.g. --override-blockhash-and-stateroot=0xdeadbeef..deadbeef:0xc0ffee..c0ffee", arity = "1..1") private String overrideHashes = null; @@ -128,6 +128,12 @@ public void run() { } } + /** + * entry point for testing, verify the block hash and state root and rebuild the trie. + * + * @param blockHashAndStateRoot record tuple for block hash and state root. + * @param worldStateStorage bonsai worldstate storage. + */ @VisibleForTesting protected void verifyAndRebuild( final BlockHashAndStateRoot blockHashAndStateRoot,