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 4890ccbf4812..e8a6e51be8de 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 0f5c9184c736..e08042857a5c 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();