mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-09 15:37:54 -05:00
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 <simon.dudley@consensys.net>
This commit is contained in:
@@ -22,7 +22,6 @@ import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMer
|
|||||||
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoOpBonsaiCachedWorldStorageManager;
|
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoOpBonsaiCachedWorldStorageManager;
|
||||||
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiPreImageProxy;
|
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiPreImageProxy;
|
||||||
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage;
|
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage;
|
||||||
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateLayerStorage;
|
|
||||||
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState;
|
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState;
|
||||||
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
|
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
|
||||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.cache.DiffBasedCachedWorldStorageManager;
|
import org.hyperledger.besu.ethereum.trie.diffbased.common.cache.DiffBasedCachedWorldStorageManager;
|
||||||
@@ -38,6 +37,8 @@ import org.hyperledger.besu.metrics.ObservableMetricsSystem;
|
|||||||
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
|
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
|
||||||
import org.hyperledger.besu.plugin.services.trielogs.TrieLog;
|
import org.hyperledger.besu.plugin.services.trielogs.TrieLog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -47,13 +48,18 @@ import com.google.common.cache.Cache;
|
|||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import org.apache.tuweni.bytes.Bytes;
|
import org.apache.tuweni.bytes.Bytes;
|
||||||
import org.apache.tuweni.bytes.Bytes32;
|
import org.apache.tuweni.bytes.Bytes32;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class BonsaiReferenceTestWorldState extends BonsaiWorldState
|
public class BonsaiReferenceTestWorldState extends BonsaiWorldState
|
||||||
implements ReferenceTestWorldState {
|
implements ReferenceTestWorldState {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(BonsaiReferenceTestWorldState.class);
|
||||||
|
|
||||||
private final BonsaiReferenceTestWorldStateStorage refTestStorage;
|
private final BonsaiReferenceTestWorldStateStorage refTestStorage;
|
||||||
private final BonsaiPreImageProxy preImageProxy;
|
private final BonsaiPreImageProxy preImageProxy;
|
||||||
private final EvmConfiguration evmConfiguration;
|
private final EvmConfiguration evmConfiguration;
|
||||||
|
private final Collection<Exception> exceptionCollector = new ArrayList<>();
|
||||||
|
|
||||||
protected BonsaiReferenceTestWorldState(
|
protected BonsaiReferenceTestWorldState(
|
||||||
final BonsaiReferenceTestWorldStateStorage worldStateKeyValueStorage,
|
final BonsaiReferenceTestWorldStateStorage worldStateKeyValueStorage,
|
||||||
@@ -110,7 +116,8 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processExtraStateStorageFormatValidation(final BlockHeader blockHeader) {
|
public Collection<Exception> processExtraStateStorageFormatValidation(
|
||||||
|
final BlockHeader blockHeader) {
|
||||||
if (blockHeader != null) {
|
if (blockHeader != null) {
|
||||||
final Hash parentStateRoot = getWorldStateRootHash();
|
final Hash parentStateRoot = getWorldStateRootHash();
|
||||||
final BonsaiReferenceTestUpdateAccumulator originalUpdater =
|
final BonsaiReferenceTestUpdateAccumulator originalUpdater =
|
||||||
@@ -121,6 +128,7 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState
|
|||||||
// validate trielog generation with frozen state
|
// validate trielog generation with frozen state
|
||||||
validateStateRolling(parentStateRoot, originalUpdater, blockHeader, true);
|
validateStateRolling(parentStateRoot, originalUpdater, blockHeader, true);
|
||||||
}
|
}
|
||||||
|
return exceptionCollector;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,9 +164,12 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState
|
|||||||
bonsaiWorldState.persist(blockHeader);
|
bonsaiWorldState.persist(blockHeader);
|
||||||
Hash generatedRootHash = bonsaiWorldState.rootHash();
|
Hash generatedRootHash = bonsaiWorldState.rootHash();
|
||||||
if (!bonsaiWorldState.rootHash().equals(blockHeader.getStateRoot())) {
|
if (!bonsaiWorldState.rootHash().equals(blockHeader.getStateRoot())) {
|
||||||
throw new RuntimeException(
|
final String msg =
|
||||||
"state root becomes invalid following a rollForward %s != %s"
|
"state root becomes invalid following a rollForward %s != %s"
|
||||||
.formatted(blockHeader.getStateRoot(), generatedRootHash));
|
.formatted(blockHeader.getStateRoot(), generatedRootHash);
|
||||||
|
final RuntimeException e = new RuntimeException(msg);
|
||||||
|
exceptionCollector.add(e);
|
||||||
|
LOG.atError().setMessage(msg).setCause(e).log();
|
||||||
}
|
}
|
||||||
|
|
||||||
updaterForState = (BonsaiWorldStateUpdateAccumulator) bonsaiWorldState.updater();
|
updaterForState = (BonsaiWorldStateUpdateAccumulator) bonsaiWorldState.updater();
|
||||||
@@ -167,9 +178,12 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState
|
|||||||
bonsaiWorldState.persist(null);
|
bonsaiWorldState.persist(null);
|
||||||
generatedRootHash = bonsaiWorldState.rootHash();
|
generatedRootHash = bonsaiWorldState.rootHash();
|
||||||
if (!bonsaiWorldState.rootHash().equals(parentStateRoot)) {
|
if (!bonsaiWorldState.rootHash().equals(parentStateRoot)) {
|
||||||
throw new RuntimeException(
|
final String msg =
|
||||||
"state root becomes invalid following a rollBackward %s != %s"
|
"state root becomes invalid following a rollBackward %s != %s"
|
||||||
.formatted(parentStateRoot, generatedRootHash));
|
.formatted(parentStateRoot, generatedRootHash);
|
||||||
|
final RuntimeException e = new RuntimeException(msg);
|
||||||
|
exceptionCollector.add(e);
|
||||||
|
LOG.atError().setMessage(msg).setCause(e).log();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,19 +203,11 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState
|
|||||||
}
|
}
|
||||||
|
|
||||||
private BonsaiWorldState createBonsaiWorldState(final boolean isFrozen) {
|
private BonsaiWorldState createBonsaiWorldState(final boolean isFrozen) {
|
||||||
BonsaiWorldState bonsaiWorldState =
|
final BonsaiReferenceTestWorldState copy = (BonsaiReferenceTestWorldState) this.copy();
|
||||||
new BonsaiWorldState(
|
|
||||||
new BonsaiWorldStateLayerStorage(
|
|
||||||
(BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage),
|
|
||||||
bonsaiCachedMerkleTrieLoader,
|
|
||||||
cachedWorldStorageManager,
|
|
||||||
trieLogManager,
|
|
||||||
evmConfiguration,
|
|
||||||
new DiffBasedWorldStateConfig());
|
|
||||||
if (isFrozen) {
|
if (isFrozen) {
|
||||||
bonsaiWorldState.freeze(); // freeze state
|
copy.freeze();
|
||||||
}
|
}
|
||||||
return bonsaiWorldState;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import org.hyperledger.besu.evm.worldstate.WorldState;
|
|||||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
||||||
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
|
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
@@ -56,8 +58,10 @@ public class ForestReferenceTestWorldState extends ForestMutableWorldState
|
|||||||
* root has been validated, to ensure the integrity of other aspects of the state.
|
* root has been validated, to ensure the integrity of other aspects of the state.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void processExtraStateStorageFormatValidation(final BlockHeader blockHeader) {
|
public Collection<Exception> processExtraStateStorageFormatValidation(
|
||||||
|
final BlockHeader blockHeader) {
|
||||||
// nothing more to verify with forest
|
// nothing more to verify with forest
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.hyperledger.besu.evm.account.MutableAccount;
|
|||||||
import org.hyperledger.besu.evm.internal.EvmConfiguration;
|
import org.hyperledger.besu.evm.internal.EvmConfiguration;
|
||||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ public interface ReferenceTestWorldState extends MutableWorldState {
|
|||||||
|
|
||||||
ReferenceTestWorldState copy();
|
ReferenceTestWorldState copy();
|
||||||
|
|
||||||
void processExtraStateStorageFormatValidation(final BlockHeader blockHeader);
|
Collection<Exception> processExtraStateStorageFormatValidation(final BlockHeader blockHeader);
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
static ReferenceTestWorldState create(final Map<String, AccountMock> accounts) {
|
static ReferenceTestWorldState create(final Map<String, AccountMock> accounts) {
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import org.apache.tuweni.bytes.Bytes;
|
||||||
|
import org.apache.tuweni.bytes.Bytes32;
|
||||||
|
import org.apache.tuweni.units.bigints.UInt256;
|
||||||
|
import org.assertj.core.api.SoftAssertions;
|
||||||
|
import org.hyperledger.besu.datatypes.Address;
|
||||||
import org.hyperledger.besu.datatypes.BlobGas;
|
import org.hyperledger.besu.datatypes.BlobGas;
|
||||||
import org.hyperledger.besu.datatypes.Hash;
|
import org.hyperledger.besu.datatypes.Hash;
|
||||||
import org.hyperledger.besu.datatypes.Wei;
|
import org.hyperledger.besu.datatypes.Wei;
|
||||||
@@ -42,8 +49,12 @@ import org.hyperledger.besu.evm.account.Account;
|
|||||||
import org.hyperledger.besu.evm.log.Log;
|
import org.hyperledger.besu.evm.log.Log;
|
||||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
||||||
import org.hyperledger.besu.testutil.JsonTestParameters;
|
import org.hyperledger.besu.testutil.JsonTestParameters;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class GeneralStateReferenceTestTools {
|
public class GeneralStateReferenceTestTools {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(GeneralStateReferenceTestTools.class);
|
||||||
|
|
||||||
private static final List<String> SPECS_PRIOR_TO_DELETING_EMPTY_ACCOUNTS =
|
private static final List<String> SPECS_PRIOR_TO_DELETING_EMPTY_ACCOUNTS =
|
||||||
Arrays.asList("Frontier", "Homestead", "EIP150");
|
Arrays.asList("Frontier", "Homestead", "EIP150");
|
||||||
|
|
||||||
@@ -179,16 +190,26 @@ public class GeneralStateReferenceTestTools {
|
|||||||
worldStateUpdater.deleteAccount(coinbase.getAddress());
|
worldStateUpdater.deleteAccount(coinbase.getAddress());
|
||||||
}
|
}
|
||||||
worldStateUpdater.commit();
|
worldStateUpdater.commit();
|
||||||
worldState.processExtraStateStorageFormatValidation(blockHeader);
|
Collection<Exception> additionalExceptions = worldState.processExtraStateStorageFormatValidation(blockHeader);
|
||||||
worldState.persist(blockHeader);
|
worldState.persist(blockHeader);
|
||||||
|
|
||||||
// Check the world state root hash.
|
// Check the world state root hash.
|
||||||
final Hash expectedRootHash = spec.getExpectedRootHash();
|
final Hash expectedRootHash = spec.getExpectedRootHash();
|
||||||
assertThat(worldState.rootHash())
|
// If the root hash doesn't match, first dump the world state for debugging.
|
||||||
.withFailMessage(
|
if (!expectedRootHash.equals(worldState.rootHash())) {
|
||||||
"Unexpected world state root hash; expected state: %s, computed state: %s",
|
logWorldState(worldState);
|
||||||
spec.getExpectedRootHash(), worldState.rootHash())
|
}
|
||||||
.isEqualTo(expectedRootHash);
|
SoftAssertions.assertSoftly(
|
||||||
|
softly -> {
|
||||||
|
softly.assertThat(worldState.rootHash())
|
||||||
|
.withFailMessage(
|
||||||
|
"Unexpected world state root hash; expected state: %s, computed state: %s",
|
||||||
|
spec.getExpectedRootHash(), worldState.rootHash())
|
||||||
|
.isEqualTo(expectedRootHash);
|
||||||
|
additionalExceptions.forEach(
|
||||||
|
e -> softly.fail("Additional exception during state validation: " + e.getMessage()));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
// Check the logs.
|
// Check the logs.
|
||||||
final Hash expectedLogsHash = spec.getExpectedLogsHash();
|
final Hash expectedLogsHash = spec.getExpectedLogsHash();
|
||||||
@@ -206,4 +227,33 @@ public class GeneralStateReferenceTestTools {
|
|||||||
private static boolean shouldClearEmptyAccounts(final String eip) {
|
private static boolean shouldClearEmptyAccounts(final String eip) {
|
||||||
return !SPECS_PRIOR_TO_DELETING_EMPTY_ACCOUNTS.contains(eip);
|
return !SPECS_PRIOR_TO_DELETING_EMPTY_ACCOUNTS.contains(eip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void logWorldState(final ReferenceTestWorldState worldState) {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
ObjectNode worldStateJson = mapper.createObjectNode();
|
||||||
|
worldState.streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE)
|
||||||
|
.forEach(
|
||||||
|
account -> {
|
||||||
|
ObjectNode accountJson = mapper.createObjectNode();
|
||||||
|
accountJson.put("nonce", Bytes.ofUnsignedLong(account.getNonce()).toShortHexString());
|
||||||
|
accountJson.put("balance", account.getBalance().toShortHexString());
|
||||||
|
accountJson.put("code", account.getCode().toHexString());
|
||||||
|
ObjectNode storageJson = mapper.createObjectNode();
|
||||||
|
var storageEntries = account.storageEntriesFrom(Bytes32.ZERO, Integer.MAX_VALUE);
|
||||||
|
storageEntries.values().stream()
|
||||||
|
.map(
|
||||||
|
e ->
|
||||||
|
Map.entry(
|
||||||
|
e.getKey().orElse(UInt256.fromBytes(Bytes.EMPTY)),
|
||||||
|
account.getStorageValue(UInt256.fromBytes(e.getKey().get()))))
|
||||||
|
.sorted(Map.Entry.comparingByKey())
|
||||||
|
.forEach(e -> storageJson.put(e.getKey().toQuantityHexString(), e.getValue().toQuantityHexString()));
|
||||||
|
|
||||||
|
if (!storageEntries.isEmpty()) {
|
||||||
|
accountJson.set("storage", storageJson);
|
||||||
|
}
|
||||||
|
worldStateJson.set(account.getAddress().orElse(Address.wrap(Bytes.EMPTY)).toHexString(), accountJson);
|
||||||
|
});
|
||||||
|
LOG.error("Calculated world state: \n{}", worldStateJson.toPrettyString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user