Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit 02dc53e

Browse files
siladugaryschulte
authored andcommitted
Log calculated world state contents upon state root mismatch (#8099)
Collect trielog rolling exceptions and display upon state root mismatch Signed-off-by: Simon Dudley <[email protected]>
1 parent e64d0d8 commit 02dc53e

File tree

4 files changed

+86
-25
lines changed

4 files changed

+86
-25
lines changed

ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoOpBonsaiCachedWorldStorageManager;
2323
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiPreImageProxy;
2424
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage;
25-
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateLayerStorage;
2625
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState;
2726
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
2827
import org.hyperledger.besu.ethereum.trie.diffbased.common.cache.DiffBasedCachedWorldStorageManager;
@@ -38,6 +37,8 @@
3837
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
3938
import org.hyperledger.besu.plugin.services.trielogs.TrieLog;
4039

40+
import java.util.ArrayList;
41+
import java.util.Collection;
4142
import java.util.Map;
4243
import java.util.Optional;
4344
import java.util.stream.Stream;
@@ -47,13 +48,18 @@
4748
import com.google.common.cache.CacheBuilder;
4849
import org.apache.tuweni.bytes.Bytes;
4950
import org.apache.tuweni.bytes.Bytes32;
51+
import org.slf4j.Logger;
52+
import org.slf4j.LoggerFactory;
5053

5154
public class BonsaiReferenceTestWorldState extends BonsaiWorldState
5255
implements ReferenceTestWorldState {
5356

57+
private static final Logger LOG = LoggerFactory.getLogger(BonsaiReferenceTestWorldState.class);
58+
5459
private final BonsaiReferenceTestWorldStateStorage refTestStorage;
5560
private final BonsaiPreImageProxy preImageProxy;
5661
private final EvmConfiguration evmConfiguration;
62+
private final Collection<Exception> exceptionCollector = new ArrayList<>();
5763

5864
protected BonsaiReferenceTestWorldState(
5965
final BonsaiReferenceTestWorldStateStorage worldStateKeyValueStorage,
@@ -110,7 +116,8 @@ protected void verifyWorldStateRoot(final Hash calculatedStateRoot, final BlockH
110116
}
111117

112118
@Override
113-
public void processExtraStateStorageFormatValidation(final BlockHeader blockHeader) {
119+
public Collection<Exception> processExtraStateStorageFormatValidation(
120+
final BlockHeader blockHeader) {
114121
if (blockHeader != null) {
115122
final Hash parentStateRoot = getWorldStateRootHash();
116123
final BonsaiReferenceTestUpdateAccumulator originalUpdater =
@@ -121,6 +128,7 @@ public void processExtraStateStorageFormatValidation(final BlockHeader blockHead
121128
// validate trielog generation with frozen state
122129
validateStateRolling(parentStateRoot, originalUpdater, blockHeader, true);
123130
}
131+
return exceptionCollector;
124132
}
125133

126134
/**
@@ -156,9 +164,12 @@ private void validateTrieLog(
156164
bonsaiWorldState.persist(blockHeader);
157165
Hash generatedRootHash = bonsaiWorldState.rootHash();
158166
if (!bonsaiWorldState.rootHash().equals(blockHeader.getStateRoot())) {
159-
throw new RuntimeException(
167+
final String msg =
160168
"state root becomes invalid following a rollForward %s != %s"
161-
.formatted(blockHeader.getStateRoot(), generatedRootHash));
169+
.formatted(blockHeader.getStateRoot(), generatedRootHash);
170+
final RuntimeException e = new RuntimeException(msg);
171+
exceptionCollector.add(e);
172+
LOG.atError().setMessage(msg).setCause(e).log();
162173
}
163174

164175
updaterForState = (BonsaiWorldStateUpdateAccumulator) bonsaiWorldState.updater();
@@ -167,9 +178,12 @@ private void validateTrieLog(
167178
bonsaiWorldState.persist(null);
168179
generatedRootHash = bonsaiWorldState.rootHash();
169180
if (!bonsaiWorldState.rootHash().equals(parentStateRoot)) {
170-
throw new RuntimeException(
181+
final String msg =
171182
"state root becomes invalid following a rollBackward %s != %s"
172-
.formatted(parentStateRoot, generatedRootHash));
183+
.formatted(parentStateRoot, generatedRootHash);
184+
final RuntimeException e = new RuntimeException(msg);
185+
exceptionCollector.add(e);
186+
LOG.atError().setMessage(msg).setCause(e).log();
173187
}
174188
}
175189
}
@@ -189,19 +203,11 @@ private void generateTrieLogFromState(
189203
}
190204

191205
private BonsaiWorldState createBonsaiWorldState(final boolean isFrozen) {
192-
BonsaiWorldState bonsaiWorldState =
193-
new BonsaiWorldState(
194-
new BonsaiWorldStateLayerStorage(
195-
(BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage),
196-
bonsaiCachedMerkleTrieLoader,
197-
cachedWorldStorageManager,
198-
trieLogManager,
199-
evmConfiguration,
200-
new DiffBasedWorldStateConfig());
206+
final BonsaiReferenceTestWorldState copy = (BonsaiReferenceTestWorldState) this.copy();
201207
if (isFrozen) {
202-
bonsaiWorldState.freeze(); // freeze state
208+
copy.freeze();
203209
}
204-
return bonsaiWorldState;
210+
return copy;
205211
}
206212

207213
@JsonCreator

ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ForestReferenceTestWorldState.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
2525
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
2626

27+
import java.util.Collection;
28+
import java.util.Collections;
2729
import java.util.Map;
2830

2931
import com.fasterxml.jackson.annotation.JsonCreator;
@@ -56,8 +58,10 @@ public ReferenceTestWorldState copy() {
5658
* root has been validated, to ensure the integrity of other aspects of the state.
5759
*/
5860
@Override
59-
public void processExtraStateStorageFormatValidation(final BlockHeader blockHeader) {
61+
public Collection<Exception> processExtraStateStorageFormatValidation(
62+
final BlockHeader blockHeader) {
6063
// nothing more to verify with forest
64+
return Collections.emptyList();
6165
}
6266

6367
@JsonCreator

ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestWorldState.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.hyperledger.besu.evm.internal.EvmConfiguration;
2323
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
2424

25+
import java.util.Collection;
2526
import java.util.HashMap;
2627
import java.util.Map;
2728

@@ -91,7 +92,7 @@ static void insertAccount(
9192

9293
ReferenceTestWorldState copy();
9394

94-
void processExtraStateStorageFormatValidation(final BlockHeader blockHeader);
95+
Collection<Exception> processExtraStateStorageFormatValidation(final BlockHeader blockHeader);
9596

9697
@JsonCreator
9798
static ReferenceTestWorldState create(final Map<String, AccountMock> accounts) {

ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
import java.util.Map;
2323
import java.util.Optional;
2424

25+
import com.fasterxml.jackson.databind.ObjectMapper;
26+
import com.fasterxml.jackson.databind.node.ObjectNode;
27+
import org.apache.tuweni.bytes.Bytes;
28+
import org.apache.tuweni.bytes.Bytes32;
29+
import org.apache.tuweni.units.bigints.UInt256;
30+
import org.assertj.core.api.SoftAssertions;
31+
import org.hyperledger.besu.datatypes.Address;
2532
import org.hyperledger.besu.datatypes.BlobGas;
2633
import org.hyperledger.besu.datatypes.Hash;
2734
import org.hyperledger.besu.datatypes.Wei;
@@ -42,8 +49,12 @@
4249
import org.hyperledger.besu.evm.log.Log;
4350
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
4451
import org.hyperledger.besu.testutil.JsonTestParameters;
52+
import org.slf4j.Logger;
53+
import org.slf4j.LoggerFactory;
4554

4655
public class GeneralStateReferenceTestTools {
56+
private static final Logger LOG = LoggerFactory.getLogger(GeneralStateReferenceTestTools.class);
57+
4758
private static final List<String> SPECS_PRIOR_TO_DELETING_EMPTY_ACCOUNTS =
4859
Arrays.asList("Frontier", "Homestead", "EIP150");
4960

@@ -179,16 +190,26 @@ public static void executeTest(final GeneralStateTestCaseEipSpec spec) {
179190
worldStateUpdater.deleteAccount(coinbase.getAddress());
180191
}
181192
worldStateUpdater.commit();
182-
worldState.processExtraStateStorageFormatValidation(blockHeader);
193+
Collection<Exception> additionalExceptions = worldState.processExtraStateStorageFormatValidation(blockHeader);
183194
worldState.persist(blockHeader);
184195

185196
// Check the world state root hash.
186197
final Hash expectedRootHash = spec.getExpectedRootHash();
187-
assertThat(worldState.rootHash())
188-
.withFailMessage(
189-
"Unexpected world state root hash; expected state: %s, computed state: %s",
190-
spec.getExpectedRootHash(), worldState.rootHash())
191-
.isEqualTo(expectedRootHash);
198+
// If the root hash doesn't match, first dump the world state for debugging.
199+
if (!expectedRootHash.equals(worldState.rootHash())) {
200+
logWorldState(worldState);
201+
}
202+
SoftAssertions.assertSoftly(
203+
softly -> {
204+
softly.assertThat(worldState.rootHash())
205+
.withFailMessage(
206+
"Unexpected world state root hash; expected state: %s, computed state: %s",
207+
spec.getExpectedRootHash(), worldState.rootHash())
208+
.isEqualTo(expectedRootHash);
209+
additionalExceptions.forEach(
210+
e -> softly.fail("Additional exception during state validation: " + e.getMessage()));
211+
212+
});
192213

193214
// Check the logs.
194215
final Hash expectedLogsHash = spec.getExpectedLogsHash();
@@ -206,4 +227,33 @@ public static void executeTest(final GeneralStateTestCaseEipSpec spec) {
206227
private static boolean shouldClearEmptyAccounts(final String eip) {
207228
return !SPECS_PRIOR_TO_DELETING_EMPTY_ACCOUNTS.contains(eip);
208229
}
230+
231+
private static void logWorldState(final ReferenceTestWorldState worldState) {
232+
ObjectMapper mapper = new ObjectMapper();
233+
ObjectNode worldStateJson = mapper.createObjectNode();
234+
worldState.streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE)
235+
.forEach(
236+
account -> {
237+
ObjectNode accountJson = mapper.createObjectNode();
238+
accountJson.put("nonce", Bytes.ofUnsignedLong(account.getNonce()).toShortHexString());
239+
accountJson.put("balance", account.getBalance().toShortHexString());
240+
accountJson.put("code", account.getCode().toHexString());
241+
ObjectNode storageJson = mapper.createObjectNode();
242+
var storageEntries = account.storageEntriesFrom(Bytes32.ZERO, Integer.MAX_VALUE);
243+
storageEntries.values().stream()
244+
.map(
245+
e ->
246+
Map.entry(
247+
e.getKey().orElse(UInt256.fromBytes(Bytes.EMPTY)),
248+
account.getStorageValue(UInt256.fromBytes(e.getKey().get()))))
249+
.sorted(Map.Entry.comparingByKey())
250+
.forEach(e -> storageJson.put(e.getKey().toQuantityHexString(), e.getValue().toQuantityHexString()));
251+
252+
if (!storageEntries.isEmpty()) {
253+
accountJson.set("storage", storageJson);
254+
}
255+
worldStateJson.set(account.getAddress().orElse(Address.wrap(Bytes.EMPTY)).toHexString(), accountJson);
256+
});
257+
LOG.error("Calculated world state: \n{}", worldStateJson.toPrettyString());
258+
}
209259
}

0 commit comments

Comments
 (0)