From 094c8416df9cf003e0a7d1fb0651d34468137896 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Tue, 31 Oct 2023 10:10:48 -0600 Subject: [PATCH] Journaled world state (#6023) Introduce a new Journaled World State Updater. Within a transaction it keeps one copy of account and storage state, restoring previous revisions on reverts and exceptional halts. This updater only supports post-merge semantics with regard to empty accounts, namely that they do not exist in world state. Adds an EvmConfiguration option for stacked vs journaled updater, and wire it in where needed. The staked updater is default mode, which is the current behavior prior to this patch. Signed-off-by: Danno Ferrin --- .../org/hyperledger/besu/cli/BesuCommand.java | 1 + .../cli/ConfigurationOverviewBuilder.java | 15 + .../besu/cli/options/unstable/EvmOptions.java | 19 +- .../controller/BesuControllerBuilder.java | 71 ++-- .../cli/ConfigurationOverviewBuilderTest.java | 23 + build.gradle | 7 + .../jsonrpc/internal/methods/TraceBlock.java | 5 +- .../internal/processor/BlockTracer.java | 5 +- .../internal/processor/TransactionTracer.java | 3 +- .../tracing/diff/StateDiffGenerator.java | 8 +- .../worldstate/PrunerIntegrationTest.java | 7 +- .../besu/ethereum/bonsai/BonsaiAccount.java | 20 +- .../bonsai/BonsaiWorldStateProvider.java | 11 +- .../cache/CachedWorldStorageManager.java | 21 +- .../bonsai/worldview/BonsaiWorldState.java | 13 +- .../BonsaiWorldStateUpdateAccumulator.java | 13 +- .../besu/ethereum/chain/GenesisState.java | 3 +- .../besu/ethereum/core/PrivacyParameters.java | 4 +- .../worldstate/DefaultMutableWorldState.java | 59 +-- .../worldstate/DefaultWorldStateArchive.java | 11 +- .../core/InMemoryKeyValueStorageProvider.java | 15 +- .../core/InMemoryPrivacyStorageProvider.java | 12 +- .../BlockImportExceptionHandlingTest.java | 16 +- .../bonsai/AbstractIsolationTests.java | 4 +- .../bonsai/BonsaiWorldStateArchiveTest.java | 63 +-- .../besu/ethereum/bonsai/LogRollingTests.java | 22 +- .../besu/ethereum/bonsai/RollingImport.java | 5 +- .../bonsai/trielog/TrieLogManagerTests.java | 9 +- .../DefaultMutableWorldStateTest.java | 6 +- .../worldstate/MarkSweepPrunerTest.java | 17 +- .../FastWorldStateDownloaderTest.java | 103 +++-- .../besu/evmtool/BenchmarkSubCommand.java | 2 + .../besu/evmtool/BlockchainModule.java | 10 +- .../org/hyperledger/besu/evmtool/EvmTool.java | 3 + .../besu/evmtool/EvmToolCommand.java | 16 +- .../evmtool/EvmToolCommandOptionsModule.java | 31 ++ .../besu/evmtool/GenesisFileModule.java | 4 +- .../evmtool/MainnetGenesisFileModule.java | 5 +- .../besu/evmtool/T8nServerSubCommand.java | 92 ++-- .../besu/evmtool/T8nSubCommand.java | 7 +- .../BonsaiReferenceTestUpdateAccumulator.java | 6 +- .../BonsaiReferenceTestWorldState.java | 45 +- .../DefaultReferenceTestWorldState.java | 6 +- .../ReferenceTestWorldState.java | 9 +- .../vm/BlockchainReferenceTestTools.java | 26 +- .../vm/GeneralStateReferenceTestTools.java | 35 +- .../ethereum/retesteth/RetestethContext.java | 3 +- .../besu/collections/undo/UndoList.java | 9 +- .../besu/collections/undo/UndoMap.java | 18 +- .../collections/undo/UndoNavigableMap.java | 181 ++++++++ .../besu/collections/undo/UndoScalar.java | 126 ++++++ .../besu/collections/undo/UndoSet.java | 11 +- .../besu/collections/undo/UndoTable.java | 9 +- ...{UndoableCollection.java => Undoable.java} | 10 +- .../java/org/hyperledger/besu/evm/EVM.java | 12 + .../besu/evm/fluent/SimpleAccount.java | 30 +- .../besu/evm/internal/EvmConfiguration.java | 34 +- .../hyperledger/besu/evm/internal/Words.java | 19 - .../evm/worldstate/AbstractWorldUpdater.java | 14 +- .../besu/evm/worldstate/JournaledAccount.java | 397 ++++++++++++++++++ .../besu/evm/worldstate/JournaledUpdater.java | 184 ++++++++ .../besu/evm/worldstate/StackedUpdater.java | 8 +- .../evm/worldstate/UpdateTrackingAccount.java | 14 +- .../besu/evm/worldstate/WorldUpdater.java | 7 +- .../hyperledger/besu/evm/toy/ToyAccount.java | 14 +- 65 files changed, 1598 insertions(+), 390 deletions(-) create mode 100644 evm/src/main/java/org/hyperledger/besu/collections/undo/UndoNavigableMap.java create mode 100644 evm/src/main/java/org/hyperledger/besu/collections/undo/UndoScalar.java rename evm/src/main/java/org/hyperledger/besu/collections/undo/{UndoableCollection.java => Undoable.java} (92%) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledAccount.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 6d6bed2bfcf..5a6fc386716 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -3433,6 +3433,7 @@ private String generateConfigurationOverview() { } builder.setTxPoolImplementation(buildTransactionPoolConfiguration().getTxPoolImplementation()); + builder.setWorldStateUpdateMode(unstableEvmOptions.toDomainObject().worldUpdaterMode()); builder.setPluginContext(besuComponent.getBesuPluginContext()); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java b/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java index 508b195a8eb..8b25f6377eb 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.BesuInfo; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.services.BesuPluginContextImpl; import org.hyperledger.besu.util.log.FramedLogMessage; import org.hyperledger.besu.util.platform.PlatformDetector; @@ -50,6 +51,7 @@ public class ConfigurationOverviewBuilder { private String engineJwtFilePath; private boolean isHighSpec = false; private TransactionPoolConfiguration.Implementation txPoolImplementation; + private EvmConfiguration.WorldUpdaterMode worldStateUpdateMode; private Map environment; private BesuPluginContextImpl besuPluginContext; @@ -181,6 +183,18 @@ public ConfigurationOverviewBuilder setTxPoolImplementation( return this; } + /** + * Sets the world state updater mode + * + * @param worldStateUpdateMode the world state updater mode + * @return the builder + */ + public ConfigurationOverviewBuilder setWorldStateUpdateMode( + final EvmConfiguration.WorldUpdaterMode worldStateUpdateMode) { + this.worldStateUpdateMode = worldStateUpdateMode; + return this; + } + /** * Sets the engine jwt file path. * @@ -257,6 +271,7 @@ public String build() { } lines.add("Using " + txPoolImplementation + " transaction pool implementation"); + lines.add("Using " + worldStateUpdateMode + " worldstate update mode"); lines.add(""); lines.add("Host:"); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/EvmOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/EvmOptions.java index 7e787cf83b8..f91fcc880f7 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/EvmOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/EvmOptions.java @@ -18,7 +18,6 @@ import org.hyperledger.besu.cli.options.CLIOptions; import org.hyperledger.besu.evm.internal.EvmConfiguration; -import java.util.Arrays; import java.util.List; import picocli.CommandLine; @@ -29,6 +28,9 @@ public class EvmOptions implements CLIOptions { /** The constant JUMPDEST_CACHE_WEIGHT. */ public static final String JUMPDEST_CACHE_WEIGHT = "--Xevm-jumpdest-cache-weight-kb"; + /** The constant WORLDSTATE_UPDATE_MODE. */ + public static final String WORLDSTATE_UPDATE_MODE = "--Xevm-worldstate-update-mode"; + /** * Create evm options. * @@ -51,13 +53,24 @@ public static EvmOptions create() { private Long jumpDestCacheWeightKilobytes = 32_000L; // 10k contracts, (25k max contract size / 8 bit) + 32byte hash + @CommandLine.Option( + names = {WORLDSTATE_UPDATE_MODE}, + description = "How to handle worldstate updates within a transaction", + fallbackValue = "STACKED", + defaultValue = "STACKED", + hidden = true, + arity = "1") + private EvmConfiguration.WorldUpdaterMode worldstateUpdateMode = + EvmConfiguration.WorldUpdaterMode + .STACKED; // Stacked Updater. Years of battle tested correctness. + @Override public EvmConfiguration toDomainObject() { - return new EvmConfiguration(jumpDestCacheWeightKilobytes); + return new EvmConfiguration(jumpDestCacheWeightKilobytes, worldstateUpdateMode); } @Override public List getCLIOptions() { - return Arrays.asList(JUMPDEST_CACHE_WEIGHT); + return List.of(JUMPDEST_CACHE_WEIGHT, WORLDSTATE_UPDATE_MODE); } } diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 802be1215b5..1d3c96aea69 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -632,10 +632,7 @@ public BesuController build() { if (chainPrunerConfiguration.getChainPruningEnabled()) { protocolContext .safeConsensusContext(MergeContext.class) - .ifPresent( - mergeContext -> { - mergeContext.setIsChainPruningEnabled(true); - }); + .ifPresent(mergeContext -> mergeContext.setIsChainPruningEnabled(true)); final ChainDataPruner chainDataPruner = createChainPruner(blockchainStorage); blockchain.observeBlockAdded(chainDataPruner); LOG.info( @@ -776,7 +773,6 @@ public BesuController build() { final SubProtocolConfiguration subProtocolConfiguration = createSubProtocolConfiguration(ethProtocolManager, maybeSnapProtocolManager); - ; final JsonRpcMethods additionalJsonRpcMethodFactory = createAdditionalJsonRpcMethodFactory(protocolContext); @@ -831,24 +827,21 @@ protected Synchronizer createSynchronizer( final EthProtocolManager ethProtocolManager, final PivotBlockSelector pivotBlockSelector) { - final DefaultSynchronizer toUse = - new DefaultSynchronizer( - syncConfig, - protocolSchedule, - protocolContext, - worldStateStorage, - ethProtocolManager.getBlockBroadcaster(), - maybePruner, - ethContext, - syncState, - dataDirectory, - storageProvider, - clock, - metricsSystem, - getFullSyncTerminationCondition(protocolContext.getBlockchain()), - pivotBlockSelector); - - return toUse; + return new DefaultSynchronizer( + syncConfig, + protocolSchedule, + protocolContext, + worldStateStorage, + ethProtocolManager.getBlockBroadcaster(), + maybePruner, + ethContext, + syncState, + dataDirectory, + storageProvider, + clock, + metricsSystem, + getFullSyncTerminationCondition(protocolContext.getBlockchain()), + pivotBlockSelector); } private PivotBlockSelector createPivotSelector( @@ -930,9 +923,8 @@ protected SubProtocolConfiguration createSubProtocolConfiguration( final SubProtocolConfiguration subProtocolConfiguration = new SubProtocolConfiguration().withSubProtocol(EthProtocol.get(), ethProtocolManager); maybeSnapProtocolManager.ifPresent( - snapProtocolManager -> { - subProtocolConfiguration.withSubProtocol(SnapProtocol.get(), snapProtocolManager); - }); + snapProtocolManager -> + subProtocolConfiguration.withSubProtocol(SnapProtocol.get(), snapProtocolManager)); return subProtocolConfiguration; } @@ -1071,22 +1063,21 @@ WorldStateArchive createWorldStateArchive( final WorldStateStorage worldStateStorage, final Blockchain blockchain, final CachedMerkleTrieLoader cachedMerkleTrieLoader) { - switch (dataStorageConfiguration.getDataStorageFormat()) { - case BONSAI: - return new BonsaiWorldStateProvider( - (BonsaiWorldStateKeyValueStorage) worldStateStorage, - blockchain, - Optional.of(dataStorageConfiguration.getBonsaiMaxLayersToLoad()), - cachedMerkleTrieLoader, - metricsSystem, - besuComponent.map(BesuComponent::getBesuPluginContext).orElse(null)); - - case FOREST: - default: + return switch (dataStorageConfiguration.getDataStorageFormat()) { + case BONSAI -> new BonsaiWorldStateProvider( + (BonsaiWorldStateKeyValueStorage) worldStateStorage, + blockchain, + Optional.of(dataStorageConfiguration.getBonsaiMaxLayersToLoad()), + cachedMerkleTrieLoader, + metricsSystem, + besuComponent.map(BesuComponent::getBesuPluginContext).orElse(null), + evmConfiguration); + case FOREST -> { final WorldStatePreimageStorage preimageStorage = storageProvider.createWorldStatePreimageStorage(); - return new DefaultWorldStateArchive(worldStateStorage, preimageStorage); - } + yield new DefaultWorldStateArchive(worldStateStorage, preimageStorage, evmConfiguration); + } + }; } private ChainDataPruner createChainPruner(final BlockchainStorage blockchainStorage) { diff --git a/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java index 4fff2b36f58..7642e14c946 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java @@ -19,6 +19,8 @@ import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LEGACY; import static org.mockito.Mockito.mock; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; @@ -159,4 +161,25 @@ void setTxPoolImplementationLegacy() { final String legacyTxPoolSelected = builder.build(); assertThat(legacyTxPoolSelected).contains("Using LEGACY transaction pool implementation"); } + + @Test + void setWorldStateUpdateModeDefault() { + builder.setWorldStateUpdateMode(EvmConfiguration.DEFAULT.worldUpdaterMode()); + final String layeredTxPoolSelected = builder.build(); + assertThat(layeredTxPoolSelected).contains("Using STACKED worldstate update mode"); + } + + @Test + void setWorldStateUpdateModeStacked() { + builder.setWorldStateUpdateMode(EvmConfiguration.WorldUpdaterMode.STACKED); + final String layeredTxPoolSelected = builder.build(); + assertThat(layeredTxPoolSelected).contains("Using STACKED worldstate update mode"); + } + + @Test + void setWorldStateUpdateModeJournaled() { + builder.setWorldStateUpdateMode(EvmConfiguration.WorldUpdaterMode.JOURNALED); + final String layeredTxPoolSelected = builder.build(); + assertThat(layeredTxPoolSelected).contains("Using JOURNALED worldstate update mode"); + } } diff --git a/build.gradle b/build.gradle index a1950eb65db..63397b210e8 100644 --- a/build.gradle +++ b/build.gradle @@ -227,6 +227,13 @@ allprojects { options.encoding = 'UTF-8' } + // IntelliJ workaround to allow repeated debugging of unchanged code + tasks.withType(JavaExec) { + if (it.name.contains(".")) { + outputs.upToDateWhen { false } + } + } + /* * Pass some system properties provided on the gradle command line to test executions for * convenience. diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceBlock.java index 6ac03eb91a1..f4e12554738 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceBlock.java @@ -35,7 +35,6 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; -import org.hyperledger.besu.evm.worldstate.StackedUpdater; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -208,8 +207,8 @@ public WorldUpdater getNextUpdater() { // if we have no prior updater, it must be the first TX, so use the block's initial state if (updater == null) { updater = worldState.updater(); - } else if (updater instanceof StackedUpdater) { - ((StackedUpdater) updater).markTransactionBoundary(); + } else { + updater.markTransactionBoundary(); } updater = updater.updater(); return updater; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java index d7386453ff2..b6651093702 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java @@ -21,7 +21,6 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; -import org.hyperledger.besu.evm.worldstate.StackedUpdater; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.List; @@ -58,8 +57,8 @@ private BlockReplay.TransactionAction prepareReplayAction( // if we have no prior updater, it must be the first TX, so use the block's initial state if (chainedUpdater == null) { chainedUpdater = mutableWorldState.updater(); - } else if (chainedUpdater instanceof StackedUpdater stackedUpdater) { - stackedUpdater.markTransactionBoundary(); + } else { + chainedUpdater.markTransactionBoundary(); } // create an updater for just this tx chainedUpdater = chainedUpdater.updater(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java index 2811a01135b..5b9cdfc2fcc 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java @@ -33,7 +33,6 @@ import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.tracing.StandardJsonTracer; -import org.hyperledger.besu.evm.worldstate.StackedUpdater; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.io.File; @@ -123,7 +122,7 @@ public List traceTransactionToFile( .map(parent -> calculateExcessBlobGasForParent(protocolSpec, parent)) .orElse(BlobGas.ZERO)); for (int i = 0; i < body.getTransactions().size(); i++) { - ((StackedUpdater) stackedUpdater).markTransactionBoundary(); + stackedUpdater.markTransactionBoundary(); final Transaction transaction = body.getTransactions().get(i); if (selectedHash.isEmpty() || selectedHash.filter(isEqual(transaction.getHash())).isPresent()) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffGenerator.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffGenerator.java index e8b975e11b8..ffc816db736 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffGenerator.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffGenerator.java @@ -22,7 +22,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.TracingUtils; import org.hyperledger.besu.ethereum.debug.TraceFrame; import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; +import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Collections; @@ -39,7 +39,7 @@ public class StateDiffGenerator { public Stream generateStateDiff(final TransactionTrace transactionTrace) { final List traceFrames = transactionTrace.getTraceFrames(); - if (traceFrames.size() < 1) { + if (traceFrames.isEmpty()) { return Stream.empty(); } @@ -60,9 +60,7 @@ public Stream generateStateDiff(final TransactionTrace transactionTrace) // calculate storage diff final Map storageDiff = new TreeMap<>(); for (final Map.Entry entry : - ((UpdateTrackingAccount) updatedAccount) - .getUpdatedStorage() - .entrySet()) { // FIXME cast + ((MutableAccount) updatedAccount).getUpdatedStorage().entrySet()) { final UInt256 newValue = entry.getValue(); if (rootAccount == null) { if (!UInt256.ZERO.equals(newValue)) { diff --git a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/worldstate/PrunerIntegrationTest.java b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/worldstate/PrunerIntegrationTest.java index 86f2694c3c0..77f871f1002 100644 --- a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/worldstate/PrunerIntegrationTest.java +++ b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/worldstate/PrunerIntegrationTest.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.Pruner.PruningPhase; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -59,7 +60,9 @@ public class PrunerIntegrationTest { private final WorldStateStorage worldStateStorage = new WorldStateKeyValueStorage(stateStorage); private final WorldStateArchive worldStateArchive = new DefaultWorldStateArchive( - worldStateStorage, new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage())); + worldStateStorage, + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), + EvmConfiguration.DEFAULT); private final InMemoryKeyValueStorage markStorage = new InMemoryKeyValueStorage(); private final Block genesisBlock = gen.genesisBlock(); private final MutableBlockchain blockchain = createInMemoryBlockchain(genesisBlock); @@ -226,7 +229,7 @@ private Set collectWorldStateNodes(final Hash stateRootHash, final Set trie, final Set collector) { final Bytes32 rootHash = trie.getRootHash(); trie.visitAll( - (node) -> { + node -> { if (node.isReferencedByHash() || node.getHash().equals(rootHash)) { collector.add(node.getEncodedBytes()); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java index 7362d345ce4..476a0ab22e1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java @@ -42,7 +42,7 @@ public class BonsaiAccount implements MutableAccount, AccountValue { private final BonsaiWorldView context; - private boolean mutable; + private boolean immutable; private final Address address; private final Hash addressHash; @@ -71,7 +71,7 @@ public BonsaiAccount( this.storageRoot = storageRoot; this.codeHash = codeHash; - this.mutable = mutable; + this.immutable = !mutable; } public BonsaiAccount( @@ -106,7 +106,7 @@ public BonsaiAccount( this.code = toCopy.code; updatedStorage.putAll(toCopy.updatedStorage); - this.mutable = mutable; + this.immutable = !mutable; } public BonsaiAccount( @@ -121,7 +121,7 @@ public BonsaiAccount( this.code = tracked.getCode(); updatedStorage.putAll(tracked.getUpdatedStorage()); - this.mutable = true; + this.immutable = false; } public static BonsaiAccount fromRLP( @@ -161,7 +161,7 @@ public long getNonce() { @Override public void setNonce(final long value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } nonce = value; @@ -174,7 +174,7 @@ public Wei getBalance() { @Override public void setBalance(final Wei value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } balance = value; @@ -190,7 +190,7 @@ public Bytes getCode() { @Override public void setCode(final Bytes code) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } this.code = code; @@ -242,7 +242,7 @@ public void writeTo(final RLPOutput out) { @Override public void setStorageValue(final UInt256 key, final UInt256 value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } updatedStorage.put(key, value); @@ -264,7 +264,7 @@ public Hash getStorageRoot() { } public void setStorageRoot(final Hash storageRoot) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } this.storageRoot = storageRoot; @@ -272,7 +272,7 @@ public void setStorageRoot(final Hash storageRoot) { @Override public void becomeImmutable() { - mutable = false; + immutable = true; } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java index b64f3855a3e..f0869333ba9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.plugin.BesuContext; @@ -72,7 +73,8 @@ public BonsaiWorldStateProvider( final Optional maxLayersToLoad, final CachedMerkleTrieLoader cachedMerkleTrieLoader, final ObservableMetricsSystem metricsSystem, - final BesuContext pluginContext) { + final BesuContext pluginContext, + final EvmConfiguration evmConfiguration) { this.cachedWorldStorageManager = new CachedWorldStorageManager(this, worldStateStorage, metricsSystem); @@ -83,7 +85,7 @@ public BonsaiWorldStateProvider( this.blockchain = blockchain; this.worldStateStorage = worldStateStorage; this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; - this.persistedState = new BonsaiWorldState(this, worldStateStorage); + this.persistedState = new BonsaiWorldState(this, worldStateStorage, evmConfiguration); blockchain .getBlockHeader(persistedState.getWorldStateBlockHash()) .ifPresent( @@ -98,12 +100,13 @@ public BonsaiWorldStateProvider( final TrieLogManager trieLogManager, final BonsaiWorldStateKeyValueStorage worldStateStorage, final Blockchain blockchain, - final CachedMerkleTrieLoader cachedMerkleTrieLoader) { + final CachedMerkleTrieLoader cachedMerkleTrieLoader, + final EvmConfiguration evmConfiguration) { this.cachedWorldStorageManager = cachedWorldStorageManager; this.trieLogManager = trieLogManager; this.blockchain = blockchain; this.worldStateStorage = worldStateStorage; - this.persistedState = new BonsaiWorldState(this, worldStateStorage); + this.persistedState = new BonsaiWorldState(this, worldStateStorage, evmConfiguration); this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; blockchain .getBlockHeader(persistedState.getWorldStateBlockHash()) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java index f2434bbf912..bf205f05b72 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateLayerStorage; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import java.util.ArrayList; @@ -41,6 +42,7 @@ public class CachedWorldStorageManager implements BonsaiStorageSubscriber { private static final Logger LOG = LoggerFactory.getLogger(CachedWorldStorageManager.class); private final BonsaiWorldStateProvider archive; private final ObservableMetricsSystem metricsSystem; + private final EvmConfiguration evmConfiguration; private final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; private final Map cachedWorldStatesByHash; @@ -49,19 +51,26 @@ private CachedWorldStorageManager( final BonsaiWorldStateProvider archive, final BonsaiWorldStateKeyValueStorage worldStateStorage, final Map cachedWorldStatesByHash, - final ObservableMetricsSystem metricsSystem) { + final ObservableMetricsSystem metricsSystem, + final EvmConfiguration evmConfiguration) { worldStateStorage.subscribe(this); this.rootWorldStateStorage = worldStateStorage; this.cachedWorldStatesByHash = cachedWorldStatesByHash; this.archive = archive; this.metricsSystem = metricsSystem; + this.evmConfiguration = evmConfiguration; } public CachedWorldStorageManager( final BonsaiWorldStateProvider archive, final BonsaiWorldStateKeyValueStorage worldStateStorage, final ObservableMetricsSystem metricsSystem) { - this(archive, worldStateStorage, new ConcurrentHashMap<>(), metricsSystem); + this( + archive, + worldStateStorage, + new ConcurrentHashMap<>(), + metricsSystem, + EvmConfiguration.DEFAULT); } public synchronized void addCachedLayer( @@ -132,7 +141,9 @@ public Optional getWorldState(final Hash blockHash) { .map( cached -> new BonsaiWorldState( - archive, new BonsaiWorldStateLayerStorage(cached.getWorldStateStorage()))); + archive, + new BonsaiWorldStateLayerStorage(cached.getWorldStateStorage()), + evmConfiguration)); } LOG.atDebug() .setMessage("did not find worldstate in cache for {}") @@ -171,7 +182,7 @@ public Optional getNearestWorldState(final BlockHeader blockHe .map( storage -> new BonsaiWorldState( // wrap the state in a layered worldstate - archive, new BonsaiWorldStateLayerStorage(storage))); + archive, new BonsaiWorldStateLayerStorage(storage), evmConfiguration)); } public Optional getHeadWorldState( @@ -188,7 +199,7 @@ public Optional getHeadWorldState( addCachedLayer( blockHeader, blockHeader.getStateRoot(), - new BonsaiWorldState(archive, rootWorldStateStorage)); + new BonsaiWorldState(archive, rootWorldStateStorage, evmConfiguration)); return getWorldState(blockHeader.getHash()); }); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java index d4c4c223a24..7c7f7fd5516 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java @@ -43,6 +43,7 @@ import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; @@ -80,19 +81,22 @@ public class BonsaiWorldState public BonsaiWorldState( final BonsaiWorldStateProvider archive, - final BonsaiWorldStateKeyValueStorage worldStateStorage) { + final BonsaiWorldStateKeyValueStorage worldStateStorage, + final EvmConfiguration evmConfiguration) { this( worldStateStorage, archive.getCachedMerkleTrieLoader(), archive.getCachedWorldStorageManager(), - archive.getTrieLogManager()); + archive.getTrieLogManager(), + evmConfiguration); } protected BonsaiWorldState( final BonsaiWorldStateKeyValueStorage worldStateStorage, final CachedMerkleTrieLoader cachedMerkleTrieLoader, final CachedWorldStorageManager cachedWorldStorageManager, - final TrieLogManager trieLogManager) { + final TrieLogManager trieLogManager, + final EvmConfiguration evmConfiguration) { this.worldStateStorage = worldStateStorage; this.worldStateRootHash = Hash.wrap( @@ -106,7 +110,8 @@ protected BonsaiWorldState( cachedMerkleTrieLoader.preLoadAccount( getWorldStateStorage(), worldStateRootHash, addr), (addr, value) -> - cachedMerkleTrieLoader.preLoadStorageSlot(getWorldStateStorage(), addr, value)); + cachedMerkleTrieLoader.preLoadStorageSlot(getWorldStateStorage(), addr, value), + evmConfiguration); this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; this.cachedWorldStorageManager = cachedWorldStorageManager; this.trieLogManager = trieLogManager; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java index 23fd7625fa3..f66fcd0f56f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.AbstractWorldUpdater; import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; import org.hyperledger.besu.plugin.services.trielogs.TrieLog; @@ -65,6 +66,7 @@ public class BonsaiWorldStateUpdateAccumulator private final AccountConsumingMap> accountsToUpdate; private final Map> codeToUpdate = new ConcurrentHashMap<>(); private final Set
storageToClear = Collections.synchronizedSet(new HashSet<>()); + private final EvmConfiguration evmConfiguration; // storage sub mapped by _hashed_ key. This is because in self_destruct calls we need to // enumerate the old storage and delete it. Those are trie stored by hashed key by spec and the @@ -77,18 +79,20 @@ public class BonsaiWorldStateUpdateAccumulator public BonsaiWorldStateUpdateAccumulator( final BonsaiWorldView world, final Consumer> accountPreloader, - final Consumer storagePreloader) { - super(world); + final Consumer storagePreloader, + final EvmConfiguration evmConfiguration) { + super(world, evmConfiguration); this.accountsToUpdate = new AccountConsumingMap<>(new ConcurrentHashMap<>(), accountPreloader); this.accountPreloader = accountPreloader; this.storagePreloader = storagePreloader; this.isAccumulatorStateChanged = false; + this.evmConfiguration = evmConfiguration; } public BonsaiWorldStateUpdateAccumulator copy() { final BonsaiWorldStateUpdateAccumulator copy = new BonsaiWorldStateUpdateAccumulator( - wrappedWorldView(), accountPreloader, storagePreloader); + wrappedWorldView(), accountPreloader, storagePreloader, evmConfiguration); copy.cloneFromUpdater(this); return copy; } @@ -770,7 +774,8 @@ public void reset() { codeToUpdate.clear(); accountsToUpdate.clear(); resetAccumulatorStateChanged(); - super.reset(); + updatedAccounts.clear(); + deletedAccounts.clear(); } public static class AccountConsumingMap extends ForwardingMap { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java index f6c969d58b2..a69e920dd20 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.DefaultMutableWorldState; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.log.LogsBloomFilter; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -138,7 +139,7 @@ private static Hash calculateGenesisStateHash(final List genesis final WorldStatePreimageKeyValueStorage preimageStorage = new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()); final MutableWorldState worldState = - new DefaultMutableWorldState(stateStorage, preimageStorage); + new DefaultMutableWorldState(stateStorage, preimageStorage, EvmConfiguration.DEFAULT); writeAccountsTo(worldState, genesisAccounts, null); return worldState.rootHash(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java index 43bdff8aafa..1d4cfdf6c1b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.plugin.services.PrivacyPluginService; import org.hyperledger.besu.plugin.services.privacy.PrivacyGroupGenesisProvider; @@ -339,7 +340,8 @@ public PrivacyParameters build() { final WorldStatePreimageStorage privatePreimageStorage = storageProvider.createWorldStatePreimageStorage(); final WorldStateArchive privateWorldStateArchive = - new DefaultWorldStateArchive(privateWorldStateStorage, privatePreimageStorage); + new DefaultWorldStateArchive( + privateWorldStateStorage, privatePreimageStorage, EvmConfiguration.DEFAULT); final PrivateStateStorage privateStateStorage = storageProvider.createPrivateStateStorage(); final PrivateStateRootResolver privateStateRootResolver = diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java index b9283d50da0..d50f702af31 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.AccountStorageEntry; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.AbstractWorldUpdater; import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; import org.hyperledger.besu.evm.worldstate.WorldState; @@ -33,7 +34,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.NavigableMap; @@ -41,7 +41,6 @@ import java.util.Optional; import java.util.TreeMap; import java.util.TreeSet; -import java.util.function.Function; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; @@ -50,6 +49,7 @@ public class DefaultMutableWorldState implements MutableWorldState { + private final EvmConfiguration evmConfiguration; private final WorldStateStorage worldStateStorage; private final WorldStatePreimageStorage preimageStorage; @@ -60,31 +60,36 @@ public class DefaultMutableWorldState implements MutableWorldState { private final Map newAccountKeyPreimages = new HashMap<>(); public DefaultMutableWorldState( - final WorldStateStorage storage, final WorldStatePreimageStorage preimageStorage) { - this(MerkleTrie.EMPTY_TRIE_NODE_HASH, storage, preimageStorage); + final WorldStateStorage storage, + final WorldStatePreimageStorage preimageStorage, + final EvmConfiguration evmConfiguration) { + this(MerkleTrie.EMPTY_TRIE_NODE_HASH, storage, preimageStorage, evmConfiguration); } public DefaultMutableWorldState( final Bytes32 rootHash, final WorldStateStorage worldStateStorage, - final WorldStatePreimageStorage preimageStorage) { + final WorldStatePreimageStorage preimageStorage, + final EvmConfiguration evmConfiguration) { this.worldStateStorage = worldStateStorage; this.accountStateTrie = newAccountStateTrie(rootHash); this.preimageStorage = preimageStorage; + this.evmConfiguration = evmConfiguration; } - public DefaultMutableWorldState(final WorldState worldState) { + public DefaultMutableWorldState( + final WorldState worldState, final EvmConfiguration evmConfiguration) { // TODO: this is an abstraction leak (and kind of incorrect in that we reuse the underlying // storage), but the reason for this is that the accounts() method is unimplemented below and // can't be until NC-754. - if (!(worldState instanceof DefaultMutableWorldState)) { + if (!(worldState instanceof DefaultMutableWorldState other)) { throw new UnsupportedOperationException(); } - final DefaultMutableWorldState other = (DefaultMutableWorldState) worldState; this.worldStateStorage = other.worldStateStorage; this.preimageStorage = other.preimageStorage; this.accountStateTrie = newAccountStateTrie(other.accountStateTrie.getRootHash()); + this.evmConfiguration = evmConfiguration; } private MerkleTrie newAccountStateTrie(final Bytes32 rootHash) { @@ -126,16 +131,9 @@ private WorldStateAccount deserializeAccount( return new WorldStateAccount(address, addressHash, accountValue); } - private static Bytes serializeAccount( - final long nonce, final Wei balance, final Hash storageRoot, final Hash codeHash) { - final StateTrieAccountValue accountValue = - new StateTrieAccountValue(nonce, balance, storageRoot, codeHash); - return RLP.encode(accountValue::writeTo); - } - @Override public WorldUpdater updater() { - return new Updater(this); + return new Updater(this, evmConfiguration); } @Override @@ -158,11 +156,10 @@ public int hashCode() { @Override public final boolean equals(final Object other) { - if (!(other instanceof DefaultMutableWorldState)) { + if (!(other instanceof DefaultMutableWorldState that)) { return false; } - final DefaultMutableWorldState that = (DefaultMutableWorldState) other; return this.rootHash().equals(that.rootHash()); } @@ -197,11 +194,6 @@ public void persist(final BlockHeader blockHeader) { stateUpdater.commit(); } - private Optional getStorageTrieKeyPreimage(final Bytes32 trieKey) { - return Optional.ofNullable(newStorageKeyPreimages.get(trieKey)) - .or(() -> preimageStorage.getStorageTrieKeyPreimage(trieKey)); - } - private static UInt256 convertToUInt256(final Bytes value) { // TODO: we could probably have an optimized method to decode a single scalar since it's used // pretty often. @@ -332,13 +324,19 @@ public String toString() { builder.append("codeHash=").append(getCodeHash()).append(", "); return builder.append("}").toString(); } + + private Optional getStorageTrieKeyPreimage(final Bytes32 trieKey) { + return Optional.ofNullable(newStorageKeyPreimages.get(trieKey)) + .or(() -> preimageStorage.getStorageTrieKeyPreimage(trieKey)); + } } protected static class Updater extends AbstractWorldUpdater { - protected Updater(final DefaultMutableWorldState world) { - super(world); + protected Updater( + final DefaultMutableWorldState world, final EvmConfiguration evmConfiguration) { + super(world, evmConfiguration); } @Override @@ -403,9 +401,7 @@ public void commit() { : origin.storageTrie(); wrapped.updatedStorageTries.put(updated.getAddress(), storageTrie); final TreeSet> entries = - new TreeSet<>( - Comparator.comparing( - (Function, UInt256>) Map.Entry::getKey)); + new TreeSet<>(Map.Entry.comparingByKey()); entries.addAll(updatedStorage.entrySet()); for (final Map.Entry entry : entries) { @@ -431,5 +427,12 @@ public void commit() { wrapped.accountStateTrie.put(updated.getAddressHash(), account); } } + + private static Bytes serializeAccount( + final long nonce, final Wei balance, final Hash storageRoot, final Hash codeHash) { + final StateTrieAccountValue accountValue = + new StateTrieAccountValue(nonce, balance, storageRoot, codeHash); + return RLP.encode(accountValue::writeTo); + } } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultWorldStateArchive.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultWorldStateArchive.java index b1955561cc5..a29bd9b2ff4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultWorldStateArchive.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultWorldStateArchive.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.proof.WorldStateProof; import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldState; import java.util.List; @@ -35,14 +36,18 @@ public class DefaultWorldStateArchive implements WorldStateArchive { private final WorldStateStorage worldStateStorage; private final WorldStatePreimageStorage preimageStorage; private final WorldStateProofProvider worldStateProof; + private final EvmConfiguration evmConfiguration; private static final Hash EMPTY_ROOT_HASH = Hash.wrap(MerkleTrie.EMPTY_TRIE_NODE_HASH); public DefaultWorldStateArchive( - final WorldStateStorage worldStateStorage, final WorldStatePreimageStorage preimageStorage) { + final WorldStateStorage worldStateStorage, + final WorldStatePreimageStorage preimageStorage, + final EvmConfiguration evmConfiguration) { this.worldStateStorage = worldStateStorage; this.preimageStorage = preimageStorage; this.worldStateProof = new WorldStateProofProvider(worldStateStorage); + this.evmConfiguration = evmConfiguration; } @Override @@ -66,7 +71,9 @@ public Optional getMutable(final Hash rootHash, final Hash bl if (!worldStateStorage.isWorldStateAvailable(rootHash, blockHash)) { return Optional.empty(); } - return Optional.of(new DefaultMutableWorldState(rootHash, worldStateStorage, preimageStorage)); + return Optional.of( + new DefaultMutableWorldState( + rootHash, worldStateStorage, preimageStorage, evmConfiguration)); } @Override diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java index f5d5fb9726e..e54cd96a30d 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.DefaultMutableWorldState; import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; @@ -79,11 +80,17 @@ public static MutableBlockchain createInMemoryBlockchain( public static DefaultWorldStateArchive createInMemoryWorldStateArchive() { return new DefaultWorldStateArchive( new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage())); + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), + EvmConfiguration.DEFAULT); } public static BonsaiWorldStateProvider createBonsaiInMemoryWorldStateArchive( final Blockchain blockchain) { + return createBonsaiInMemoryWorldStateArchive(blockchain, EvmConfiguration.DEFAULT); + } + + public static BonsaiWorldStateProvider createBonsaiInMemoryWorldStateArchive( + final Blockchain blockchain, final EvmConfiguration evmConfiguration) { final InMemoryKeyValueStorageProvider inMemoryKeyValueStorageProvider = new InMemoryKeyValueStorageProvider(); final CachedMerkleTrieLoader cachedMerkleTrieLoader = @@ -95,14 +102,16 @@ public static BonsaiWorldStateProvider createBonsaiInMemoryWorldStateArchive( Optional.empty(), cachedMerkleTrieLoader, new NoOpMetricsSystem(), - null); + null, + evmConfiguration); } public static MutableWorldState createInMemoryWorldState() { final InMemoryKeyValueStorageProvider provider = new InMemoryKeyValueStorageProvider(); return new DefaultMutableWorldState( provider.createWorldStateStorage(DataStorageFormat.FOREST), - provider.createWorldStatePreimageStorage()); + provider.createWorldStatePreimageStorage(), + EvmConfiguration.DEFAULT); } public static PrivateStateStorage createInMemoryPrivateStateStorage() { diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryPrivacyStorageProvider.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryPrivacyStorageProvider.java index 7a76d88d64c..91e115cffab 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryPrivacyStorageProvider.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryPrivacyStorageProvider.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; public class InMemoryPrivacyStorageProvider implements PrivacyStorageProvider { @@ -33,13 +34,16 @@ public class InMemoryPrivacyStorageProvider implements PrivacyStorageProvider { public static WorldStateArchive createInMemoryWorldStateArchive() { return new DefaultWorldStateArchive( new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage())); + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), + EvmConfiguration.DEFAULT); } public static MutableWorldState createInMemoryWorldState() { final InMemoryPrivacyStorageProvider provider = new InMemoryPrivacyStorageProvider(); return new DefaultMutableWorldState( - provider.createWorldStateStorage(), provider.createWorldStatePreimageStorage()); + provider.createWorldStateStorage(), + provider.createWorldStatePreimageStorage(), + EvmConfiguration.DEFAULT); } @Override @@ -68,5 +72,7 @@ public int getFactoryVersion() { } @Override - public void close() {} + public void close() { + // no cleanup for in-memory data storage + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java index 9f1e30ff3ba..a33f6a2b04e 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java @@ -46,6 +46,7 @@ import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.exception.StorageException; @@ -55,7 +56,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -public class BlockImportExceptionHandlingTest { +class BlockImportExceptionHandlingTest { private final MainnetTransactionProcessor transactionProcessor = mock(MainnetTransactionProcessor.class); @@ -90,7 +91,8 @@ public class BlockImportExceptionHandlingTest { spy( new BonsaiWorldState( (BonsaiWorldStateProvider) worldStateArchive, - (BonsaiWorldStateKeyValueStorage) worldStateStorage)); + (BonsaiWorldStateKeyValueStorage) worldStateStorage, + EvmConfiguration.DEFAULT)); private final BadBlockManager badBlockManager = new BadBlockManager(); @@ -107,7 +109,7 @@ public void setup() { } @Test - public void shouldNotBadBlockWhenInternalErrorDuringPersisting() { + void shouldNotBadBlockWhenInternalErrorDuringPersisting() { Mockito.doThrow(new StorageException("database problem")).when(persisted).persist(any()); Mockito.doReturn(persisted).when(worldStateArchive).getMutable(); @@ -147,7 +149,7 @@ public void shouldNotBadBlockWhenInternalErrorDuringPersisting() { } @Test - public void shouldNotBadBlockWhenInternalErrorOnBlockLookup() { + void shouldNotBadBlockWhenInternalErrorOnBlockLookup() { Block goodBlock = new BlockDataGenerator() @@ -173,7 +175,7 @@ public void shouldNotBadBlockWhenInternalErrorOnBlockLookup() { any(), eq(HeaderValidationMode.DETACHED_ONLY))) .thenReturn(true); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); + assertThat(badBlockManager.getBadBlocks()).isEmpty(); mainnetBlockValidator.validateAndProcessBlock( protocolContext, goodBlock, @@ -183,7 +185,7 @@ public void shouldNotBadBlockWhenInternalErrorOnBlockLookup() { } @Test - public void shouldNotBadBlockWhenInternalErrorDuringValidateHeader() { + void shouldNotBadBlockWhenInternalErrorDuringValidateHeader() { Block goodBlock = new BlockDataGenerator() @@ -212,7 +214,7 @@ public void shouldNotBadBlockWhenInternalErrorDuringValidateHeader() { } @Test - public void shouldNotBadBlockWhenInternalErrorDuringValidateBody() { + void shouldNotBadBlockWhenInternalErrorDuringValidateBody() { Mockito.doNothing().when(persisted).persist(any()); Mockito.doReturn(persisted).when(worldStateArchive).getMutable(); Mockito.doReturn(Optional.of(persisted)).when(worldStateArchive).getMutable(any(), any()); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java index a6441f970fb..9da6cbc7fcc 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java @@ -66,6 +66,7 @@ import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory; @@ -148,7 +149,8 @@ public void createStorage() { Optional.of(16L), new CachedMerkleTrieLoader(new NoOpMetricsSystem()), new NoOpMetricsSystem(), - null); + null, + EvmConfiguration.DEFAULT); var ws = archive.getMutable(); genesisState.writeStateTo(ws); protocolContext = new ProtocolContext(blockchain, archive, null, Optional.empty()); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java index 0721522ffb1..0c664f77e02 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java @@ -43,6 +43,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; @@ -63,7 +64,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -public class BonsaiWorldStateArchiveTest { +class BonsaiWorldStateArchiveTest { final BlockHeaderTestFixture blockBuilder = new BlockHeaderTestFixture(); @Mock Blockchain blockchain; @@ -89,7 +90,7 @@ public void setUp() { } @Test - public void testGetMutableReturnPersistedStateWhenNeeded() { + void testGetMutableReturnPersistedStateWhenNeeded() { final BlockHeader chainHead = blockBuilder.number(0).buildHeader(); when(segmentedKeyValueStorage.get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY)) @@ -106,14 +107,15 @@ public void testGetMutableReturnPersistedStateWhenNeeded() { trieLogManager, new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem())); + new CachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); assertThat(bonsaiWorldStateArchive.getMutable(chainHead, true)) .containsInstanceOf(BonsaiWorldState.class); } @Test - public void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() { + void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() { bonsaiWorldStateArchive = new BonsaiWorldStateProvider( new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), @@ -121,7 +123,8 @@ public void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() { Optional.of(512L), new CachedMerkleTrieLoader(new NoOpMetricsSystem()), new NoOpMetricsSystem(), - null); + null, + EvmConfiguration.DEFAULT); final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); final BlockHeader chainHead = blockBuilder.number(512).buildHeader(); when(blockchain.getChainHeadHeader()).thenReturn(chainHead); @@ -130,7 +133,7 @@ public void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() { } @Test - public void testGetMutableWhenLoadLessThanLimitLayersBack() { + void testGetMutableWhenLoadLessThanLimitLayersBack() { bonsaiWorldStateArchive = new BonsaiWorldStateProvider( @@ -138,7 +141,8 @@ public void testGetMutableWhenLoadLessThanLimitLayersBack() { trieLogManager, new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem())); + new CachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); final BlockHeader chainHead = blockBuilder.number(511).buildHeader(); final BonsaiWorldState mockWorldState = mock(BonsaiWorldState.class); @@ -153,9 +157,8 @@ public void testGetMutableWhenLoadLessThanLimitLayersBack() { .containsInstanceOf(BonsaiWorldState.class); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Test - public void testGetMutableWithStorageInconsistencyRollbackTheState() { + void testGetMutableWithStorageInconsistencyRollbackTheState() { doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) .when(trieLogManager) @@ -170,10 +173,11 @@ public void testGetMutableWithStorageInconsistencyRollbackTheState() { trieLogManager, worldStateStorage, blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()))); + new CachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT)); final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); - when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader)); + when(blockchain.getBlockHeader(blockHeader.getHash())).thenReturn(Optional.of(blockHeader)); assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash())) .containsInstanceOf(BonsaiWorldState.class); @@ -184,7 +188,7 @@ public void testGetMutableWithStorageInconsistencyRollbackTheState() { // @SuppressWarnings({"unchecked", "rawtypes"}) @Test - public void testGetMutableWithStorageConsistencyNotRollbackTheState() { + void testGetMutableWithStorageConsistencyNotRollbackTheState() { var worldStateStorage = new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); @@ -195,12 +199,13 @@ public void testGetMutableWithStorageConsistencyNotRollbackTheState() { trieLogManager, worldStateStorage, blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()))); + new CachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT)); final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); - when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader)); - when(blockchain.getBlockHeader(eq(Hash.ZERO))).thenReturn(Optional.of(blockHeader)); + when(blockchain.getBlockHeader(blockHeader.getHash())).thenReturn(Optional.of(blockHeader)); + when(blockchain.getBlockHeader(Hash.ZERO)).thenReturn(Optional.of(blockHeader)); assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash())) .containsInstanceOf(BonsaiWorldState.class); @@ -209,9 +214,8 @@ public void testGetMutableWithStorageConsistencyNotRollbackTheState() { verify(trieLogManager, Mockito.never()).getTrieLogLayer(any()); } - @SuppressWarnings({"unchecked"}) @Test - public void testGetMutableWithStorageConsistencyToRollbackAndRollForwardTheState() { + void testGetMutableWithStorageConsistencyToRollbackAndRollForwardTheState() { final BlockHeader genesis = blockBuilder.number(0).buildHeader(); final BlockHeader blockHeaderChainA = blockBuilder.number(1).timestamp(1).parentHash(genesis.getHash()).buildHeader(); @@ -232,27 +236,27 @@ public void testGetMutableWithStorageConsistencyToRollbackAndRollForwardTheState trieLogManager, worldStateStorage, blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()))); + new CachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT)); // initial persisted state hash key - when(blockchain.getBlockHeader(eq(Hash.ZERO))).thenReturn(Optional.of(blockHeaderChainA)); - when(blockchain.getBlockHeader(eq(blockHeaderChainB.getHash()))) + when(blockchain.getBlockHeader(Hash.ZERO)).thenReturn(Optional.of(blockHeaderChainA)); + when(blockchain.getBlockHeader(blockHeaderChainB.getHash())) .thenReturn(Optional.of(blockHeaderChainB)); - when(blockchain.getBlockHeader(eq(genesis.getHash()))).thenReturn(Optional.of(genesis)); + when(blockchain.getBlockHeader(genesis.getHash())).thenReturn(Optional.of(genesis)); assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeaderChainB.getHash())) .containsInstanceOf(BonsaiWorldState.class); // verify is trying to get the trie log layers to rollback and roll forward - verify(trieLogManager).getTrieLogLayer(eq(blockHeaderChainA.getHash())); - verify(trieLogManager).getTrieLogLayer(eq(blockHeaderChainB.getHash())); + verify(trieLogManager).getTrieLogLayer(blockHeaderChainA.getHash()); + verify(trieLogManager).getTrieLogLayer(blockHeaderChainB.getHash()); } - @SuppressWarnings({"unchecked"}) @Test // TODO: refactor to test original intent @Disabled("needs refactor, getMutable(hash, hash) cannot trigger saveTrieLog") - public void testGetMutableWithRollbackNotOverrideTrieLogLayer() { + void testGetMutableWithRollbackNotOverrideTrieLogLayer() { when(segmentedKeyValueStorage.startTransaction()) .thenReturn(segmentedKeyValueStorageTransaction); final BlockHeader genesis = blockBuilder.number(0).buildHeader(); @@ -272,10 +276,11 @@ public void testGetMutableWithRollbackNotOverrideTrieLogLayer() { trieLogManager, new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()))); + new CachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT)); // initial persisted state hash key - when(blockchain.getBlockHeader(eq(Hash.ZERO))).thenReturn(Optional.of(blockHeaderChainA)); + when(blockchain.getBlockHeader(Hash.ZERO)).thenReturn(Optional.of(blockHeaderChainA)); // fake trie log layer final BytesValueRLPOutput rlpLogBlockB = new BytesValueRLPOutput(); final TrieLogLayer trieLogLayerBlockB = new TrieLogLayer(); @@ -284,9 +289,9 @@ public void testGetMutableWithRollbackNotOverrideTrieLogLayer() { when(segmentedKeyValueStorage.get(BLOCKCHAIN, blockHeaderChainB.getHash().toArrayUnsafe())) .thenReturn(Optional.of(rlpLogBlockB.encoded().toArrayUnsafe())); - when(blockchain.getBlockHeader(eq(blockHeaderChainB.getHash()))) + when(blockchain.getBlockHeader(blockHeaderChainB.getHash())) .thenReturn(Optional.of(blockHeaderChainB)); - when(blockchain.getBlockHeader(eq(genesis.getHash()))).thenReturn(Optional.of(genesis)); + when(blockchain.getBlockHeader(genesis.getHash())).thenReturn(Optional.of(genesis)); assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeaderChainB.getHash())) .containsInstanceOf(BonsaiWorldState.class); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java index 054e1b88cc7..225930fdb74 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.log.LogsBloomFilter; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -159,7 +160,9 @@ void simpleRollForwardTest() { final BonsaiWorldState worldState = new BonsaiWorldState( - archive, new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem())); + archive, + new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); final WorldUpdater updater = worldState.updater(); final MutableAccount mutableAccount = updater.createAccount(addressOne, 1, Wei.of(1L)); @@ -171,7 +174,8 @@ void simpleRollForwardTest() { final BonsaiWorldState secondWorldState = new BonsaiWorldState( secondArchive, - new BonsaiWorldStateKeyValueStorage(secondProvider, new NoOpMetricsSystem())); + new BonsaiWorldStateKeyValueStorage(secondProvider, new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); final BonsaiWorldStateUpdateAccumulator secondUpdater = (BonsaiWorldStateUpdateAccumulator) secondWorldState.updater(); @@ -200,7 +204,9 @@ void simpleRollForwardTest() { void rollForwardTwice() { final BonsaiWorldState worldState = new BonsaiWorldState( - archive, new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem())); + archive, + new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); final WorldUpdater updater = worldState.updater(); final MutableAccount mutableAccount = updater.createAccount(addressOne, 1, Wei.of(1L)); @@ -220,7 +226,8 @@ void rollForwardTwice() { final BonsaiWorldState secondWorldState = new BonsaiWorldState( secondArchive, - new BonsaiWorldStateKeyValueStorage(secondProvider, new NoOpMetricsSystem())); + new BonsaiWorldStateKeyValueStorage(secondProvider, new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); final BonsaiWorldStateUpdateAccumulator secondUpdater = (BonsaiWorldStateUpdateAccumulator) secondWorldState.updater(); @@ -250,7 +257,9 @@ void rollForwardTwice() { void rollBackOnce() { final BonsaiWorldState worldState = new BonsaiWorldState( - archive, new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem())); + archive, + new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); final WorldUpdater updater = worldState.updater(); final MutableAccount mutableAccount = updater.createAccount(addressOne, 1, Wei.of(1L)); @@ -277,7 +286,8 @@ void rollBackOnce() { final BonsaiWorldState secondWorldState = new BonsaiWorldState( secondArchive, - new BonsaiWorldStateKeyValueStorage(secondProvider, new NoOpMetricsSystem())); + new BonsaiWorldStateKeyValueStorage(secondProvider, new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); final WorldUpdater secondUpdater = secondWorldState.updater(); final MutableAccount secondMutableAccount = diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java index 397ebf1f9ab..5aa0f68daa0 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; @@ -54,7 +55,9 @@ public static void main(final String[] arg) throws IOException { InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive(null); final BonsaiWorldState bonsaiState = new BonsaiWorldState( - archive, new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem())); + archive, + new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); final SegmentedInMemoryKeyValueStorage worldStateStorage = (SegmentedInMemoryKeyValueStorage) provider.getStorageBySegmentIdentifiers( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java index 0c911d33556..9dfe5fac2db 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import java.util.concurrent.atomic.AtomicBoolean; @@ -36,7 +37,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class TrieLogManagerTests { +class TrieLogManagerTests { BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); @@ -47,7 +48,9 @@ public class TrieLogManagerTests { @Mock BonsaiWorldState worldState; @Mock Blockchain blockchain; BonsaiWorldStateUpdateAccumulator bonsaiUpdater = - spy(new BonsaiWorldStateUpdateAccumulator(worldState, (__, ___) -> {}, (__, ___) -> {})); + spy( + new BonsaiWorldStateUpdateAccumulator( + worldState, (__, ___) -> {}, (__, ___) -> {}, EvmConfiguration.DEFAULT)); TrieLogManager trieLogManager; @@ -57,7 +60,7 @@ public void setup() { } @Test - public void testSaveTrieLogEvent() { + void testSaveTrieLogEvent() { AtomicBoolean eventFired = new AtomicBoolean(false); trieLogManager.subscribe( layer -> { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java index bc632e0f62a..4e423563416 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.evm.account.AccountStorageEntry; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.evm.worldstate.WorldState.StreamableAccount; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -56,7 +57,7 @@ class DefaultMutableWorldStateTest { private static MutableWorldState createEmpty(final WorldStateKeyValueStorage storage) { final WorldStatePreimageKeyValueStorage preimageStorage = new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()); - return new DefaultMutableWorldState(storage, preimageStorage); + return new DefaultMutableWorldState(storage, preimageStorage, EvmConfiguration.DEFAULT); } private static MutableWorldState createEmpty() { @@ -276,7 +277,8 @@ void commitAndPersist() { new DefaultMutableWorldState( expectedRootHash, new WorldStateKeyValueStorage(storage), - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage())); + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), + EvmConfiguration.DEFAULT); assertThat(newWorldState.rootHash()).isEqualTo(expectedRootHash); assertThat(newWorldState.get(ADDRESS)).isNotNull(); assertThat(newWorldState.get(ADDRESS).getBalance()).isEqualTo(newBalance); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPrunerTest.java index c91ea5dcb08..28997591f73 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPrunerTest.java @@ -33,6 +33,7 @@ import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -55,7 +56,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class MarkSweepPrunerTest { +class MarkSweepPrunerTest { private final BlockDataGenerator gen = new BlockDataGenerator(); private final NoOpMetricsSystem metricsSystem = new NoOpMetricsSystem(); @@ -65,13 +66,15 @@ public class MarkSweepPrunerTest { spy(new WorldStateKeyValueStorage(stateStorage)); private final WorldStateArchive worldStateArchive = new DefaultWorldStateArchive( - worldStateStorage, new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage())); + worldStateStorage, + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), + EvmConfiguration.DEFAULT); private final InMemoryKeyValueStorage markStorage = new InMemoryKeyValueStorage(); private final Block genesisBlock = gen.genesisBlock(); private final MutableBlockchain blockchain = createInMemoryBlockchain(genesisBlock); @Test - public void mark_marksAllExpectedNodes() { + void mark_marksAllExpectedNodes() { final MarkSweepPruner pruner = new MarkSweepPruner(worldStateStorage, blockchain, markStorage, metricsSystem); @@ -84,7 +87,7 @@ public void mark_marksAllExpectedNodes() { final BlockHeader markBlock = blockchain.getBlockHeader(markBlockNumber).get(); // Collect the nodes we expect to keep final Set expectedNodes = collectWorldStateNodes(markBlock.getStateRoot()); - assertThat(hashValueStore.size()).isGreaterThan(expectedNodes.size()); // Sanity check + assertThat(hashValueStore).hasSizeGreaterThan(expectedNodes.size()); // Sanity check // Mark and sweep pruner.mark(markBlock.getStateRoot()); @@ -113,14 +116,14 @@ public void mark_marksAllExpectedNodes() { } // Check that storage contains only the values we expect - assertThat(hashValueStore.size()).isEqualTo(expectedNodes.size()); + assertThat(hashValueStore).hasSameSizeAs(expectedNodes); assertThat(hashValueStore.values().stream().map(Optional::get)) .containsExactlyInAnyOrderElementsOf( expectedNodes.stream().map(Bytes::toArrayUnsafe).collect(Collectors.toSet())); } @Test - public void sweepBefore_shouldSweepStateRootFirst() { + void sweepBefore_shouldSweepStateRootFirst() { final MarkSweepPruner pruner = new MarkSweepPruner(worldStateStorage, blockchain, markStorage, metricsSystem); @@ -155,7 +158,7 @@ public void sweepBefore_shouldSweepStateRootFirst() { } @Test - public void sweepBefore_shouldNotRemoveMarkedStateRoots() { + void sweepBefore_shouldNotRemoveMarkedStateRoots() { final MarkSweepPruner pruner = new MarkSweepPruner(worldStateStorage, blockchain, markStorage, metricsSystem); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java index 74c6364383e..e69a56e498a 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java @@ -63,6 +63,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.AccountStorageEntry; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -99,7 +100,7 @@ import org.junit.jupiter.api.Timeout; @Disabled("PIE-1434 - Ignored while working to make test more reliable") -public class FastWorldStateDownloaderTest { +class FastWorldStateDownloaderTest { private static final Hash EMPTY_TRIE_ROOT = Hash.wrap(MerkleTrie.EMPTY_TRIE_NODE_HASH); @@ -123,43 +124,43 @@ public void tearDown() throws Exception { @Test @Timeout(value = 60) - public void downloadWorldStateFromPeers_onePeerOneWithManyRequestsOneAtATime() { + void downloadWorldStateFromPeers_onePeerOneWithManyRequestsOneAtATime() { downloadAvailableWorldStateFromPeers(1, 50, 1, 1); } @Test @Timeout(value = 60) - public void downloadWorldStateFromPeers_onePeerOneWithManyRequests() { + void downloadWorldStateFromPeers_onePeerOneWithManyRequests() { downloadAvailableWorldStateFromPeers(1, 50, 1, 10); } @Test @Timeout(value = 60) - public void downloadWorldStateFromPeers_onePeerWithSingleRequest() { + void downloadWorldStateFromPeers_onePeerWithSingleRequest() { downloadAvailableWorldStateFromPeers(1, 1, 100, 10); } @Test @Timeout(value = 60) - public void downloadWorldStateFromPeers_largeStateFromMultiplePeers() { + void downloadWorldStateFromPeers_largeStateFromMultiplePeers() { downloadAvailableWorldStateFromPeers(5, 100, 10, 10); } @Test @Timeout(value = 60) - public void downloadWorldStateFromPeers_smallStateFromMultiplePeers() { + void downloadWorldStateFromPeers_smallStateFromMultiplePeers() { downloadAvailableWorldStateFromPeers(5, 5, 1, 10); } @Test @Timeout(value = 60) - public void downloadWorldStateFromPeers_singleRequestWithMultiplePeers() { + void downloadWorldStateFromPeers_singleRequestWithMultiplePeers() { downloadAvailableWorldStateFromPeers(5, 1, 50, 50); } @Test @Timeout(value = 60) - public void downloadEmptyWorldState() { + void downloadEmptyWorldState() { final BlockHeader header = dataGen @@ -173,7 +174,7 @@ public void downloadEmptyWorldState() { Stream.generate( () -> EthProtocolManagerTestUtil.createPeer(ethProtocolManager, header.getNumber())) .limit(5) - .collect(Collectors.toList()); + .toList(); final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); @@ -193,7 +194,7 @@ public void downloadEmptyWorldState() { @Test @Timeout(value = 60) - public void downloadAlreadyAvailableWorldState() { + void downloadAlreadyAvailableWorldState() { // Setup existing state final DefaultWorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); final MutableWorldState worldState = worldStateArchive.getMutable(); @@ -210,7 +211,7 @@ public void downloadAlreadyAvailableWorldState() { Stream.generate( () -> EthProtocolManagerTestUtil.createPeer(ethProtocolManager, header.getNumber())) .limit(5) - .collect(Collectors.toList()); + .toList(); final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); @@ -233,7 +234,7 @@ public void downloadAlreadyAvailableWorldState() { @Test @Timeout(value = 60) - public void canRecoverFromTimeouts() { + void canRecoverFromTimeouts() { final DeterministicEthScheduler.TimeoutPolicy timeoutPolicy = DeterministicEthScheduler.TimeoutPolicy.timeoutXTimes(2); final EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(timeoutPolicy); @@ -281,7 +282,8 @@ public void canRecoverFromTimeouts() { // Check that all expected account data was downloaded final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive(localStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertThat(result).isDone(); assertAccountsMatch(localWorldState, accounts); @@ -289,13 +291,13 @@ public void canRecoverFromTimeouts() { @Test @Timeout(value = 60) - public void handlesPartialResponsesFromNetwork() { + void handlesPartialResponsesFromNetwork() { downloadAvailableWorldStateFromPeers(5, 100, 10, 10, this::respondPartially); } @Test @Timeout(value = 60) - public void doesNotRequestKnownCodeFromNetwork() { + void doesNotRequestKnownCodeFromNetwork() { // Setup "remote" state final WorldStateArchive remoteWorldStateArchive = createInMemoryWorldStateArchive(); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); @@ -349,12 +351,13 @@ public void doesNotRequestKnownCodeFromNetwork() { .map(GetNodeDataMessage::readFrom) .flatMap(m -> StreamSupport.stream(m.hashes().spliterator(), true)) .collect(Collectors.toList()); - assertThat(requestedHashes.size()).isGreaterThan(0); + assertThat(requestedHashes).isNotEmpty(); assertThat(Collections.disjoint(requestedHashes, knownCode.keySet())).isTrue(); // Check that all expected account data was downloaded final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive(localStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertThat(result).isDone(); assertAccountsMatch(localWorldState, accounts); @@ -362,13 +365,13 @@ public void doesNotRequestKnownCodeFromNetwork() { @Test @Timeout(value = 60) - public void cancelDownloader() { + void cancelDownloader() { testCancellation(false); } @Test @Timeout(value = 60) - public void cancelDownloaderFuture() { + void cancelDownloaderFuture() { testCancellation(true); } @@ -396,7 +399,7 @@ private void testCancellation(final boolean shouldCancelFuture) { Stream.generate( () -> EthProtocolManagerTestUtil.createPeer(ethProtocolManager, header.getNumber())) .limit(5) - .collect(Collectors.toList()); + .toList(); final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); @@ -452,12 +455,13 @@ private void testCancellation(final boolean shouldCancelFuture) { @Test @Timeout(value = 60) - public void doesNotRequestKnownAccountTrieNodesFromNetwork() { + void doesNotRequestKnownAccountTrieNodesFromNetwork() { // Setup "remote" state final WorldStateStorage remoteStorage = new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive(remoteStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -484,7 +488,7 @@ public void doesNotRequestKnownAccountTrieNodesFromNetwork() { collectTrieNodesToBeRequestedAfterRoot(remoteStorage, remoteWorldState.rootHash(), 5); final Set knownNodes = new HashSet<>(); final Set unknownNodes = new HashSet<>(); - assertThat(allNodes.size()).isGreaterThan(0); // Sanity check + assertThat(allNodes).isNotEmpty(); // Sanity check final Updater localStorageUpdater = localStorage.updater(); final AtomicBoolean storeNode = new AtomicBoolean(true); allNodes.forEach( @@ -522,13 +526,15 @@ public void doesNotRequestKnownAccountTrieNodesFromNetwork() { .map(GetNodeDataMessage::readFrom) .flatMap(m -> StreamSupport.stream(m.hashes().spliterator(), true)) .collect(Collectors.toList()); - assertThat(requestedHashes.size()).isGreaterThan(0); - assertThat(requestedHashes).containsAll(unknownNodes); - assertThat(requestedHashes).doesNotContainAnyElementsOf(knownNodes); + assertThat(requestedHashes) + .isNotEmpty() + .containsAll(unknownNodes) + .doesNotContainAnyElementsOf(knownNodes); // Check that all expected account data was downloaded final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive(localStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertThat(result).isDone(); assertAccountsMatch(localWorldState, accounts); @@ -536,12 +542,13 @@ public void doesNotRequestKnownAccountTrieNodesFromNetwork() { @Test @Timeout(value = 60) - public void doesNotRequestKnownStorageTrieNodesFromNetwork() { + void doesNotRequestKnownStorageTrieNodesFromNetwork() { // Setup "remote" state final WorldStateStorage remoteStorage = new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive(remoteStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -582,7 +589,7 @@ public void doesNotRequestKnownStorageTrieNodesFromNetwork() { allTrieNodes.putAll( collectTrieNodesToBeRequestedAfterRoot(remoteStorage, storageRootHash, 5)); } - assertThat(allTrieNodes.size()).isGreaterThan(0); // Sanity check + assertThat(allTrieNodes).isNotEmpty(); // Sanity check final Updater localStorageUpdater = localStorage.updater(); boolean storeNode = true; for (final Map.Entry entry : allTrieNodes.entrySet()) { @@ -624,13 +631,15 @@ public void doesNotRequestKnownStorageTrieNodesFromNetwork() { .map(GetNodeDataMessage::readFrom) .flatMap(m -> StreamSupport.stream(m.hashes().spliterator(), true)) .collect(Collectors.toList()); - assertThat(requestedHashes.size()).isGreaterThan(0); - assertThat(requestedHashes).containsAll(unknownNodes); - assertThat(requestedHashes).doesNotContainAnyElementsOf(knownNodes); + assertThat(requestedHashes) + .isNotEmpty() + .containsAll(unknownNodes) + .doesNotContainAnyElementsOf(knownNodes); // Check that all expected account data was downloaded final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive(localStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertThat(result).isDone(); assertAccountsMatch(localWorldState, accounts); @@ -638,7 +647,7 @@ public void doesNotRequestKnownStorageTrieNodesFromNetwork() { @Test @Timeout(value = 60) - public void stalledDownloader() { + void stalledDownloader() { final EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem())); @@ -646,7 +655,8 @@ public void stalledDownloader() { final WorldStateStorage remoteStorage = new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive(remoteStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -702,12 +712,13 @@ public void stalledDownloader() { @Test @Timeout(value = 60) - public void resumesFromNonEmptyQueue() { + void resumesFromNonEmptyQueue() { // Setup "remote" state final WorldStateStorage remoteStorage = new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive(remoteStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -721,7 +732,7 @@ public void resumesFromNonEmptyQueue() { final InMemoryTasksPriorityQueues taskCollection = spy(new InMemoryTasksPriorityQueues<>()); List queuedHashes = getFirstSetOfChildNodeRequests(remoteStorage, stateRoot); - assertThat(queuedHashes.size()).isGreaterThan(0); // Sanity check + assertThat(queuedHashes).isNotEmpty(); // Sanity check for (Bytes32 bytes32 : queuedHashes) { taskCollection.add(new AccountTrieNodeDataRequest(Hash.wrap(bytes32), Optional.empty())); } @@ -760,8 +771,7 @@ public void resumesFromNonEmptyQueue() { .map(GetNodeDataMessage::readFrom) .flatMap(m -> StreamSupport.stream(m.hashes().spliterator(), true)) .collect(Collectors.toList()); - assertThat(requestedHashes.size()).isGreaterThan(0); - assertThat(requestedHashes).containsAll(queuedHashes); + assertThat(requestedHashes).isNotEmpty().containsAll(queuedHashes); // Check that already enqueued requests were not enqueued more than once for (Bytes32 bytes32 : queuedHashes) { @@ -772,7 +782,8 @@ public void resumesFromNonEmptyQueue() { // Check that all expected account data was downloaded assertThat(result).isDone(); final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive(localStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertAccountsMatch(localWorldState, accounts); } @@ -845,7 +856,8 @@ private void downloadAvailableWorldStateFromPeers( final WorldStateStorage remoteStorage = new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive(remoteStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -869,7 +881,8 @@ private void downloadAvailableWorldStateFromPeers( final WorldStateStorage localStorage = new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive(localStorage, createPreimageStorage()); + new DefaultWorldStateArchive( + localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); final SynchronizerConfiguration syncConfig = SynchronizerConfiguration.builder() .worldStateHashCountPerRequest(hashesPerRequest) @@ -891,7 +904,7 @@ private void downloadAvailableWorldStateFromPeers( EthProtocolManagerTestUtil.createPeer( ethProtocolManager, header.getNumber() - 1L)) .limit(trailingPeerCount) - .collect(Collectors.toList()); + .toList(); // Start downloader final CompletableFuture result = downloader.run(null, new FastSyncState(header)); diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BenchmarkSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BenchmarkSubCommand.java index 078a6e1c4c2..8078bd6ba1b 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BenchmarkSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BenchmarkSubCommand.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.evmtool.benchmarks.ECRecoverBenchmark; import org.hyperledger.besu.evmtool.benchmarks.ModExpBenchmark; import org.hyperledger.besu.evmtool.benchmarks.Secp256k1Benchmark; +import org.hyperledger.besu.util.LogConfigurator; import java.io.PrintStream; import java.util.EnumSet; @@ -85,6 +86,7 @@ public BenchmarkSubCommand(final PrintStream output) { @Override public void run() { + LogConfigurator.setLevel("", "DEBUG"); System.out.println(BesuInfo.version()); var benchmarksToRun = benchmarks.isEmpty() ? EnumSet.allOf(Benchmark.class) : benchmarks; for (var benchmark : benchmarksToRun) { diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainModule.java index 91cdad76a18..0109d6c819a 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainModule.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.worldstate.DefaultMutableWorldState; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; @@ -59,14 +60,17 @@ MutableWorldState getMutableWorldState( final WorldStateStorage worldStateStorage, final WorldStatePreimageStorage worldStatePreimageStorage, final GenesisState genesisState, - @Named("KeyValueStorageName") final String keyValueStorageName) { + @Named("KeyValueStorageName") final String keyValueStorageName, + final EvmConfiguration evmConfiguration) { if ("memory".equals(keyValueStorageName)) { final MutableWorldState mutableWorldState = - new DefaultMutableWorldState(worldStateStorage, worldStatePreimageStorage); + new DefaultMutableWorldState( + worldStateStorage, worldStatePreimageStorage, evmConfiguration); genesisState.writeStateTo(mutableWorldState); return mutableWorldState; } else { - return new DefaultMutableWorldState(stateRoot, worldStateStorage, worldStatePreimageStorage); + return new DefaultMutableWorldState( + stateRoot, worldStateStorage, worldStatePreimageStorage, evmConfiguration); } } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmTool.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmTool.java index c6bef5726cf..00582c9503d 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmTool.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmTool.java @@ -15,9 +15,12 @@ */ package org.hyperledger.besu.evmtool; +import org.hyperledger.besu.util.LogConfigurator; + public final class EvmTool { public static void main(final String... args) { + LogConfigurator.setLevel("", "DEBUG"); final EvmToolCommand evmToolCommand = new EvmToolCommand(); evmToolCommand.execute(args); diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java index 4f2c2a5a5fb..743c215ecbb 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java @@ -126,19 +126,25 @@ void setBytes(final String optionValue) { names = {"--sender"}, paramLabel = "
", description = "Calling address for this invocation.") - private final Address sender = Address.fromHexString("0x00"); + private final Address sender = Address.ZERO; @Option( names = {"--receiver"}, paramLabel = "
", description = "Receiving address for this invocation.") - private final Address receiver = Address.fromHexString("0x00"); + private final Address receiver = Address.ZERO; + + @Option( + names = {"--contract"}, + paramLabel = "
", + description = "The address holding the contract code.") + private final Address contract = Address.ZERO; @Option( names = {"--coinbase"}, paramLabel = "
", description = "Coinbase for this invocation.") - private final Address coinbase = Address.fromHexString("0x00"); + private final Address coinbase = Address.ZERO; @Option( names = {"--input"}, @@ -383,11 +389,13 @@ public void run() { WorldUpdater updater = component.getWorldUpdater(); updater.getOrCreate(sender); updater.getOrCreate(receiver); + var contractAccount = updater.getOrCreate(contract); + contractAccount.setCode(codeBytes); MessageFrame initialMessageFrame = MessageFrame.builder() .type(MessageFrame.Type.MESSAGE_CALL) - .worldUpdater(updater) + .worldUpdater(updater.updater()) .initialGas(txGas) .contract(Address.ZERO) .address(receiver) diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java index 9c97c8d36f8..b8df5fa4a95 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.services.BesuConfigurationImpl; @@ -102,4 +103,34 @@ BesuConfiguration provideBesuConfiguration() { BlockParameter provideBlockParameter() { return blockParameter; } + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) + @CommandLine.Option( + names = {"--Xevm-jumpdest-cache-weight-kb"}, + description = + "size in kilobytes to allow the cache " + + "of valid jump destinations to grow to before evicting the least recently used entry", + fallbackValue = "32000", + defaultValue = "32000", + hidden = true, + arity = "1") + private Long jumpDestCacheWeightKilobytes = + 32_000L; // 10k contracts, (25k max contract size / 8 bit) + 32byte hash + + @CommandLine.Option( + names = {"--Xevm-worldstate-update-mode"}, + description = "How to handle worldstate updates within a transaction", + fallbackValue = "STACKED", + defaultValue = "STACKED", + hidden = true, + arity = "1") + private EvmConfiguration.WorldUpdaterMode worldstateUpdateMode = + EvmConfiguration.WorldUpdaterMode + .STACKED; // Stacked Updater. Years of battle tested correctness. + + @Provides + @Singleton + EvmConfiguration provideEvmConfiguration() { + return new EvmConfiguration(jumpDestCacheWeightKilobytes, worldstateUpdateMode); + } } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java index 6bd2ee52ac7..596c9eddc7f 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import java.io.File; import java.io.IOException; @@ -63,7 +64,8 @@ GenesisConfigOptions provideGenesisConfigOptions(final GenesisConfigFile genesis ProtocolSchedule provideProtocolSchedule( final GenesisConfigOptions configOptions, @Named("Fork") final Optional fork, - @Named("RevertReasonEnabled") final boolean revertReasonEnabled) { + @Named("RevertReasonEnabled") final boolean revertReasonEnabled, + final EvmConfiguration evmConfiguration) { throw new RuntimeException("Abstract"); } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java index 5445fe2b43d..e7c576c5852 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java @@ -52,7 +52,8 @@ BlockHeaderFunctions blockHashFunction() { ProtocolSchedule provideProtocolSchedule( final GenesisConfigOptions configOptions, @Named("Fork") final Optional fork, - @Named("RevertReasonEnabled") final boolean revertReasonEnabled) { + @Named("RevertReasonEnabled") final boolean revertReasonEnabled, + final EvmConfiguration evmConfiguration) { final Optional ecCurve = configOptions.getEcCurve(); if (ecCurve.isEmpty()) { @@ -73,7 +74,7 @@ ProtocolSchedule provideProtocolSchedule( return schedule.get(); } } - return MainnetProtocolSchedule.fromConfig(configOptions, EvmConfiguration.DEFAULT); + return MainnetProtocolSchedule.fromConfig(configOptions, evmConfiguration); } public static Map> createSchedules() { diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nServerSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nServerSubCommand.java index 8b636b9a8c0..a0ef6384b08 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nServerSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nServerSubCommand.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.referencetests.ReferenceTestEnv; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestWorldState; import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evmtool.T8nExecutor.RejectedTransaction; import org.hyperledger.besu.util.LogConfigurator; @@ -33,8 +34,10 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; @@ -114,50 +117,55 @@ void handleT8nRequest( ReferenceTestEnv referenceTestEnv = objectMapper.convertValue(input.get("env"), ReferenceTestEnv.class); - ReferenceTestWorldState initialWorldState = - objectMapper.convertValue(input.get("alloc"), ReferenceTestWorldState.class); - initialWorldState.persist(null); - List transactions = new ArrayList<>(); - List rejections = new ArrayList<>(); - JsonNode txs = input.get("txs"); - if (txs != null) { - if (txs instanceof ArrayNode txsArray) { - extractTransactions( - new PrintWriter(System.err, true, StandardCharsets.UTF_8), - txsArray.elements(), - transactions, - rejections); - } else if (txs instanceof TextNode txt) { - transactions = - extractTransactions( - new PrintWriter(System.err, true, StandardCharsets.UTF_8), - List.of(txt).iterator(), - transactions, - rejections); + Map accounts = + objectMapper.convertValue(input.get("alloc"), new TypeReference<>() {}); + + final T8nExecutor.T8nResult result; + try (ReferenceTestWorldState initialWorldState = + ReferenceTestWorldState.create(accounts, EvmConfiguration.DEFAULT)) { + initialWorldState.persist(null); + List transactions = new ArrayList<>(); + List rejections = new ArrayList<>(); + JsonNode txs = input.get("txs"); + if (txs != null) { + if (txs instanceof ArrayNode txsArray) { + extractTransactions( + new PrintWriter(System.err, true, StandardCharsets.UTF_8), + txsArray.elements(), + transactions, + rejections); + } else if (txs instanceof TextNode txt) { + transactions = + extractTransactions( + new PrintWriter(System.err, true, StandardCharsets.UTF_8), + List.of(txt).iterator(), + transactions, + rejections); + } } - } - final T8nExecutor.T8nResult result = - T8nExecutor.runTest( - chainId, - fork, - reward, - objectMapper, - referenceTestEnv, - initialWorldState, - transactions, - rejections, - new T8nExecutor.TracerManager() { - @Override - public OperationTracer getManagedTracer(final int txIndex, final Hash txHash) { - return OperationTracer.NO_TRACING; - } - - @Override - public void disposeTracer(final OperationTracer tracer) { - // No output streams to dispose of - } - }); + result = + T8nExecutor.runTest( + chainId, + fork, + reward, + objectMapper, + referenceTestEnv, + initialWorldState, + transactions, + rejections, + new T8nExecutor.TracerManager() { + @Override + public OperationTracer getManagedTracer(final int txIndex, final Hash txHash) { + return OperationTracer.NO_TRACING; + } + + @Override + public void disposeTracer(final OperationTracer tracer) { + // No output streams to dispose of + } + }); + } ObjectNode outputObject = objectMapper.createObjectNode(); outputObject.set("alloc", result.allocObject()); diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nSubCommand.java index 006a806e216..40f7bb07987 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nSubCommand.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestEnv; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestWorldState; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.tracing.StandardJsonTracer; import org.hyperledger.besu.evmtool.T8nExecutor.RejectedTransaction; @@ -45,6 +46,7 @@ import java.util.Stack; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; @@ -206,8 +208,9 @@ public void run() { } referenceTestEnv = objectMapper.convertValue(config.get("env"), ReferenceTestEnv.class); - initialWorldState = - objectMapper.convertValue(config.get("alloc"), ReferenceTestWorldState.class); + Map accounts = + objectMapper.convertValue(config.get("alloc"), new TypeReference<>() {}); + initialWorldState = ReferenceTestWorldState.create(accounts, EvmConfiguration.DEFAULT); initialWorldState.persist(null); var node = config.get("txs"); Iterator it; diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestUpdateAccumulator.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestUpdateAccumulator.java index c8349b7f790..e346b5abf6d 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestUpdateAccumulator.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestUpdateAccumulator.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiPreImageProxy; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldView; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.apache.tuweni.bytes.Bytes; @@ -31,8 +32,9 @@ public BonsaiReferenceTestUpdateAccumulator( final BonsaiWorldView world, final Consumer> accountPreloader, final Consumer storagePreloader, - final BonsaiPreImageProxy preImageProxy) { - super(world, accountPreloader, storagePreloader); + final BonsaiPreImageProxy preImageProxy, + final EvmConfiguration evmConfiguration) { + super(world, accountPreloader, storagePreloader, evmConfiguration); this.preImageProxy = preImageProxy; } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java index 59eb9329f33..c46536592cd 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java @@ -26,11 +26,11 @@ import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.trielogs.TrieLog; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; import java.util.Map; import java.util.Optional; @@ -46,16 +46,24 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState private final BonsaiReferenceTestWorldStateStorage refTestStorage; private final BonsaiPreImageProxy preImageProxy; + private final EvmConfiguration evmConfiguration; protected BonsaiReferenceTestWorldState( final BonsaiReferenceTestWorldStateStorage worldStateStorage, final CachedMerkleTrieLoader cachedMerkleTrieLoader, final CachedWorldStorageManager cachedWorldStorageManager, final TrieLogManager trieLogManager, - final BonsaiPreImageProxy preImageProxy) { - super(worldStateStorage, cachedMerkleTrieLoader, cachedWorldStorageManager, trieLogManager); + final BonsaiPreImageProxy preImageProxy, + final EvmConfiguration evmConfiguration) { + super( + worldStateStorage, + cachedMerkleTrieLoader, + cachedWorldStorageManager, + trieLogManager, + evmConfiguration); this.refTestStorage = worldStateStorage; this.preImageProxy = preImageProxy; + this.evmConfiguration = evmConfiguration; setAccumulator( new BonsaiReferenceTestUpdateAccumulator( this, @@ -64,7 +72,8 @@ protected BonsaiReferenceTestWorldState( getWorldStateStorage(), worldStateRootHash, addr), (addr, value) -> cachedMerkleTrieLoader.preLoadStorageSlot(getWorldStateStorage(), addr, value), - preImageProxy)); + preImageProxy, + evmConfiguration)); } @Override @@ -75,7 +84,8 @@ public ReferenceTestWorldState copy() { cachedMerkleTrieLoader, cachedWorldStorageManager, trieLogManager, - preImageProxy); + preImageProxy, + evmConfiguration); } /** @@ -93,6 +103,13 @@ protected void verifyWorldStateRoot(final Hash calculatedStateRoot, final BlockH @JsonCreator public static BonsaiReferenceTestWorldState create( final Map accounts) { + return create(accounts, EvmConfiguration.DEFAULT); + } + + @JsonCreator + public static BonsaiReferenceTestWorldState create( + final Map accounts, + final EvmConfiguration evmConfiguration) { final ObservableMetricsSystem metricsSystem = new NoOpMetricsSystem(); final CachedMerkleTrieLoader cachedMerkleTrieLoader = new CachedMerkleTrieLoader(metricsSystem); final TrieLogManager trieLogManager = new NoOpTrieLogManager(); @@ -114,7 +131,8 @@ public static BonsaiReferenceTestWorldState create( cachedMerkleTrieLoader, noOpCachedWorldStorageManager, trieLogManager, - preImageProxy); + preImageProxy, + evmConfiguration); final WorldUpdater updater = worldState.updater(); for (final Map.Entry entry : accounts.entrySet()) { @@ -140,12 +158,13 @@ public NoOpCachedWorldStorageManager() { new NoOpMetricsSystem()); } + @SuppressWarnings({"UnsynchronizedOverridesSynchronized", "squid:S3551"}) @Override public void addCachedLayer( final BlockHeader blockHeader, final Hash worldStateRootHash, final BonsaiWorldState forWorldState) { - // reference tests do not cache layers + // reference test world states are not cached } @Override @@ -181,7 +200,7 @@ public NoOpTrieLogManager() { super(null, null, 0, null); } - @SuppressWarnings("UnsynchronizedOverridesSynchronized") + @SuppressWarnings({"UnsynchronizedOverridesSynchronized", "squid:S3551"}) @Override public void saveTrieLog( final BonsaiWorldStateUpdateAccumulator localUpdater, @@ -202,16 +221,6 @@ public long getMaxLayersToLoad() { public Optional getTrieLogLayer(final Hash blockHash) { return Optional.empty(); } - - @Override - public synchronized long subscribe(final TrieLogEvent.TrieLogObserver sub) { - return trieLogObservers.subscribe(sub); - } - - @Override - public synchronized void unsubscribe(final long id) { - trieLogObservers.unsubscribe(id); - } } @Override diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/DefaultReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/DefaultReferenceTestWorldState.java index ec0b058ba99..45771578e14 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/DefaultReferenceTestWorldState.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/DefaultReferenceTestWorldState.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.DefaultMutableWorldState; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -32,11 +33,12 @@ public class DefaultReferenceTestWorldState extends DefaultMutableWorldState DefaultReferenceTestWorldState() { super( new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage())); + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), + EvmConfiguration.DEFAULT); } public DefaultReferenceTestWorldState(final WorldState worldState) { - super(worldState); + super(worldState, EvmConfiguration.DEFAULT); } @Override diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestWorldState.java index 825d7a2cd4b..3d4db1edaab 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestWorldState.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestWorldState.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.HashMap; @@ -92,6 +93,12 @@ static void insertAccount( @JsonCreator static ReferenceTestWorldState create(final Map accounts) { // delegate to a Bonsai reference test world state: - return BonsaiReferenceTestWorldState.create(accounts); + return create(accounts, EvmConfiguration.DEFAULT); + } + + static ReferenceTestWorldState create( + final Map accounts, final EvmConfiguration evmConfiguration) { + // delegate to a Bonsai reference test world state: + return BonsaiReferenceTestWorldState.create(accounts, evmConfiguration); } } diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java index af6405fb0ff..fea3d8671da 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.vm; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -29,12 +30,17 @@ import org.hyperledger.besu.ethereum.referencetests.BlockchainReferenceTestCaseSpec; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules; import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.account.AccountState; +import org.hyperledger.besu.evm.internal.EvmConfiguration.WorldUpdaterMode; import org.hyperledger.besu.testutil.JsonTestParameters; import java.util.Arrays; import java.util.Collection; import java.util.List; +import org.apache.tuweni.bytes.Bytes32; import org.assertj.core.api.Assertions; public class BlockchainReferenceTestTools { @@ -58,7 +64,8 @@ public class BlockchainReferenceTestTools { .generator( (testName, fullPath, spec, collector) -> { final String eip = spec.getNetwork(); - collector.add(testName + "[" + eip + "]", fullPath, spec, NETWORKS_TO_RUN.contains(eip)); + collector.add( + testName + "[" + eip + "]", fullPath, spec, NETWORKS_TO_RUN.contains(eip)); }); static { @@ -73,7 +80,7 @@ public class BlockchainReferenceTestTools { // Absurd amount of gas, doesn't run in parallel params.ignore("randomStatetest94_\\w+"); - // Don't do time consuming tests + // Don't do time-consuming tests params.ignore("CALLBlake2f_MaxRounds.*"); params.ignore("loopMul_*"); @@ -100,7 +107,6 @@ public static void executeTest(final BlockchainReferenceTestCaseSpec spec) { spec.getWorldStateArchive() .getMutable(genesisBlockHeader.getStateRoot(), genesisBlockHeader.getHash()) .get(); - assertThat(worldState.rootHash()).isEqualTo(genesisBlockHeader.getStateRoot()); final ProtocolSchedule schedule = REFERENCE_TEST_PROTOCOL_SCHEDULES.getByName(spec.getNetwork()); @@ -119,6 +125,20 @@ public static void executeTest(final BlockchainReferenceTestCaseSpec spec) { final ProtocolSpec protocolSpec = schedule.getByBlockHeader(block.getHeader()); final BlockImporter blockImporter = protocolSpec.getBlockImporter(); + + EVM evm = protocolSpec.getEvm(); + if (evm.getEvmConfiguration().worldUpdaterMode() == WorldUpdaterMode.JOURNALED) { + assumeThat( + worldState + .streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE) + .anyMatch(AccountState::isEmpty)) + .withFailMessage("Journaled account configured and empty account detected") + .isFalse(); + assumeThat(EvmSpecVersion.SPURIOUS_DRAGON.compareTo(evm.getEvmVersion()) > 0) + .withFailMessage("Journaled account configured and fork prior to the merge specified") + .isFalse(); + } + final HeaderValidationMode validationMode = "NoProof".equalsIgnoreCase(spec.getSealEngine()) ? HeaderValidationMode.LIGHT diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java index af6181dab00..e0b0c7332e7 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java @@ -15,7 +15,14 @@ package org.hyperledger.besu.ethereum.vm; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.datatypes.BlobGas; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; @@ -33,17 +40,15 @@ import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestWorldState; import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.AccountState; +import org.hyperledger.besu.evm.internal.EvmConfiguration.WorldUpdaterMode; import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.testutil.JsonTestParameters; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; - public class GeneralStateReferenceTestTools { private static final ReferenceTestProtocolSchedules REFERENCE_TEST_PROTOCOL_SCHEDULES = ReferenceTestProtocolSchedules.create(); @@ -101,7 +106,7 @@ private static ProtocolSpec protocolSpec(final String name) { params.ignore("static_Call1MB1024Calldepth-\\w"); params.ignore("ShanghaiLove_.*"); - // Don't do time consuming tests + // Don't do time-consuming tests params.ignore("CALLBlake2f_MaxRounds.*"); params.ignore("loopMul-.*"); @@ -121,6 +126,20 @@ public static void executeTest(final GeneralStateTestCaseEipSpec spec) { final BlockHeader blockHeader = spec.getBlockHeader(); final ReferenceTestWorldState initialWorldState = spec.getInitialWorldState(); final Transaction transaction = spec.getTransaction(); + ProtocolSpec protocolSpec = protocolSpec(spec.getFork()); + + EVM evm = protocolSpec.getEvm(); + if (evm.getEvmConfiguration().worldUpdaterMode() == WorldUpdaterMode.JOURNALED) { + assumeThat( + initialWorldState + .streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE) + .anyMatch(AccountState::isEmpty)) + .withFailMessage("Journaled account configured and empty account detected") + .isFalse(); + assumeThat(EvmSpecVersion.SPURIOUS_DRAGON.compareTo(evm.getEvmVersion()) > 0) + .withFailMessage("Journaled account configured and fork prior to the merge specified") + .isFalse(); + } // Sometimes the tests ask us assemble an invalid transaction. If we have // no valid transaction then there is no test. GeneralBlockChain tests @@ -145,7 +164,7 @@ public static void executeTest(final GeneralStateTestCaseEipSpec spec) { final WorldUpdater worldStateUpdater = worldState.updater(); final ReferenceTestBlockchain blockchain = new ReferenceTestBlockchain(blockHeader.getNumber()); final Wei blobGasPrice = - protocolSpec(spec.getFork()) + protocolSpec .getFeeMarket() .blobGasPricePerGas(blockHeader.getExcessBlobGas().orElse(BlobGas.ZERO)); final TransactionProcessingResult result = diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java index bed7652c6d7..a467248940f 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java @@ -167,7 +167,8 @@ private boolean buildContext( final WorldStateArchive worldStateArchive = new DefaultWorldStateArchive( new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage())); + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), + EvmConfiguration.DEFAULT); final MutableWorldState worldState = worldStateArchive.getMutable(); genesisState.writeStateTo(worldState); diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoList.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoList.java index cda40649af6..72cdc56fee1 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoList.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoList.java @@ -33,11 +33,11 @@ * * @param The type of the collection. */ -public class UndoList implements List, UndoableCollection { +public class UndoList implements List, Undoable { record UndoEntry(int index, boolean set, V value, long level) { UndoEntry(final int index, final boolean set, final V value) { - this(index, set, value, UndoableCollection.incrementMarkStatic()); + this(index, set, value, Undoable.incrementMarkStatic()); } @Override @@ -140,6 +140,11 @@ public void undo(final long mark) { } } + @Override + public long lastUpdate() { + return undoLog.isEmpty() ? 0L : undoLog.get(undoLog.size() - 1).level; + } + @Override public boolean add(final V v) { undoLog.add(new UndoEntry<>(delegate.size(), false, null)); diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoMap.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoMap.java index 1a76985b5f3..08a20b447ca 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoMap.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoMap.java @@ -34,11 +34,11 @@ * * @param The type of the collection. */ -public class UndoMap implements Map, UndoableCollection { +public class UndoMap implements Map, Undoable { record UndoEntry(K key, V value, long level) { UndoEntry(final K key, final V value) { - this(key, value, UndoableCollection.incrementMarkStatic()); + this(key, value, Undoable.incrementMarkStatic()); } } @@ -70,6 +70,20 @@ public void undo(final long mark) { } } + @Override + public long lastUpdate() { + return undoLog.isEmpty() ? 0L : undoLog.get(undoLog.size() - 1).level; + } + + /** + * Has the map been changed + * + * @return true if there are any undo entries in the log + */ + public boolean updated() { + return !undoLog.isEmpty(); + } + @Override public int size() { return delegate.size(); diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoNavigableMap.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoNavigableMap.java new file mode 100644 index 00000000000..3bbdc242e1e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoNavigableMap.java @@ -0,0 +1,181 @@ +/* + * 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.collections.undo; + +import java.util.Comparator; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.SortedMap; + +/** + * A map that supports rolling back the map to a prior state. + * + *

To register a prior state you want to roll back to call `mark()`. Then use that value in a + * subsequent call to `undo(mark)`. Every mutation operation across all undoable collections + * increases the global mark, so a mark set in once collection is usable across all + * UndoableCollection instances. + * + * @param The type of the collection. + */ +public class UndoNavigableMap extends UndoMap implements NavigableMap { + + /** + * Create an UndoMap backed by another Map instance. + * + * @param delegate The Map instance to use for backing storage + */ + public UndoNavigableMap(final NavigableMap delegate) { + super(delegate); + } + + /** + * Create an undo navigable map backed by a specific map. + * + * @param map The map storing the current state + * @return an undoable map + * @param the key type + * @param the value type + */ + public static UndoNavigableMap of(final NavigableMap map) { + return new UndoNavigableMap<>(map); + } + + @Override + public Comparator comparator() { + return ((NavigableMap) delegate).comparator(); + } + + @Override + public SortedMap subMap(final K fromKey, final K toKey) { + return ((NavigableMap) delegate).subMap(fromKey, toKey); + } + + @Override + public SortedMap headMap(final K toKey) { + return ((NavigableMap) delegate).headMap(toKey); + } + + @Override + public SortedMap tailMap(final K fromKey) { + return ((NavigableMap) delegate).tailMap(fromKey); + } + + @Override + public K firstKey() { + return ((NavigableMap) delegate).firstKey(); + } + + @Override + public K lastKey() { + return ((NavigableMap) delegate).lastKey(); + } + + @Override + public Entry lowerEntry(final K key) { + return ((NavigableMap) delegate).lowerEntry(key); + } + + @Override + public K lowerKey(final K key) { + return ((NavigableMap) delegate).lowerKey(key); + } + + @Override + public Entry floorEntry(final K key) { + return ((NavigableMap) delegate).floorEntry(key); + } + + @Override + public K floorKey(final K key) { + return ((NavigableMap) delegate).floorKey(key); + } + + @Override + public Entry ceilingEntry(final K key) { + return ((NavigableMap) delegate).ceilingEntry(key); + } + + @Override + public K ceilingKey(final K key) { + return ((NavigableMap) delegate).ceilingKey(key); + } + + @Override + public Entry higherEntry(final K key) { + return ((NavigableMap) delegate).higherEntry(key); + } + + @Override + public K higherKey(final K key) { + return ((NavigableMap) delegate).higherKey(key); + } + + @Override + public Entry firstEntry() { + return ((NavigableMap) delegate).firstEntry(); + } + + @Override + public Entry lastEntry() { + return ((NavigableMap) delegate).lastEntry(); + } + + @Override + public Entry pollFirstEntry() { + return ((NavigableMap) delegate).pollFirstEntry(); + } + + @Override + public Entry pollLastEntry() { + return ((NavigableMap) delegate).pollLastEntry(); + } + + @Override + public NavigableMap descendingMap() { + return ((NavigableMap) delegate).descendingMap(); + } + + @Override + public NavigableSet navigableKeySet() { + return ((NavigableMap) delegate).navigableKeySet(); + } + + @Override + public NavigableSet descendingKeySet() { + return ((NavigableMap) delegate).descendingKeySet(); + } + + @Override + public NavigableMap subMap( + final K fromKey, final boolean fromInclusive, final K toKey, final boolean toInclusive) { + return ((NavigableMap) delegate).subMap(fromKey, fromInclusive, toKey, toInclusive); + } + + @Override + public NavigableMap headMap(final K toKey, final boolean inclusive) { + return ((NavigableMap) delegate).headMap(toKey, inclusive); + } + + @Override + public NavigableMap tailMap(final K fromKey, final boolean inclusive) { + return ((NavigableMap) delegate).tailMap(fromKey, inclusive); + } + + @Override + public String toString() { + return "UndoSortedSet{" + "delegate=" + delegate + ", undoLog=" + undoLog + '}'; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoScalar.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoScalar.java new file mode 100644 index 00000000000..0e3356c379b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoScalar.java @@ -0,0 +1,126 @@ +/* + * 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.collections.undo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * An undoable value that tracks the value across time. + * + * @param The type of the scaler. + */ +public class UndoScalar implements Undoable { + record UndoEntry(T value, long level) { + UndoEntry(final T value) { + this(value, Undoable.incrementMarkStatic()); + } + } + + T value; + final List> undoLog; + + /** + * Create an undoable scalar with an initial value + * + * @param value the initial value + * @return the undoable scalar + * @param the type of the scalar + */ + public static UndoScalar of(final T value) { + return new UndoScalar<>(value); + } + + /** + * Create an undo scalar with an initial value + * + * @param value the initial value + */ + public UndoScalar(final T value) { + undoLog = new ArrayList<>(); + this.value = value; + } + + @Override + public long lastUpdate() { + return undoLog.isEmpty() ? 0L : undoLog.get(undoLog.size() - 1).level; + } + + /** + * Has this scalar had any change since the inital value + * + * @return true if there are any changes to undo + */ + public boolean updated() { + return !undoLog.isEmpty(); + } + + /** + * Get the current value of the scalar. + * + * @return the current value + */ + public T get() { + return value; + } + + /** + * Set a new value in the scalar. + * + * @param value new value + */ + public void set(final T value) { + if (!Objects.equals(this.value, value)) { + undoLog.add(new UndoEntry<>(this.value)); + this.value = value; + } + } + + @Override + public void undo(final long mark) { + if (undoLog.isEmpty()) { + return; + } + int pos = undoLog.size() - 1; + while (pos >= 0) { + UndoEntry entry = undoLog.get(pos); + if (entry.level <= mark) { + return; + } + value = entry.value; + undoLog.remove(pos); + pos--; + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof UndoScalar that)) return false; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "UndoScalar{" + "value=" + value + ", undoLog=" + undoLog + '}'; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoSet.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoSet.java index 1ba509d3094..d1f018ac14f 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoSet.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoSet.java @@ -33,15 +33,15 @@ * * @param The type of the collection. */ -public class UndoSet implements Set, UndoableCollection { +public class UndoSet implements Set, Undoable { record UndoEntry(V value, boolean add, long level) { static UndoSet.UndoEntry add(final V value) { - return new UndoEntry<>(value, true, UndoableCollection.incrementMarkStatic()); + return new UndoEntry<>(value, true, Undoable.incrementMarkStatic()); } static UndoSet.UndoEntry remove(final V value) { - return new UndoEntry<>(value, false, UndoableCollection.incrementMarkStatic()); + return new UndoEntry<>(value, false, Undoable.incrementMarkStatic()); } } @@ -79,6 +79,11 @@ public void undo(final long mark) { } } + @Override + public long lastUpdate() { + return undoLog.isEmpty() ? 0L : undoLog.get(undoLog.size() - 1).level; + } + @Override public int size() { return delegate.size(); diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoTable.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoTable.java index 26af3225f47..0109ccdc8f7 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoTable.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoTable.java @@ -37,11 +37,11 @@ * * @param The type of the collection. */ -public class UndoTable implements Table, UndoableCollection { +public class UndoTable implements Table, Undoable { record UndoEntry(R row, C column, V value, long level) { UndoEntry(final R row, final C column, final V value) { - this(row, column, value, UndoableCollection.incrementMarkStatic()); + this(row, column, value, Undoable.incrementMarkStatic()); } } @@ -86,6 +86,11 @@ public void undo(final long mark) { } } + @Override + public long lastUpdate() { + return undoLog.isEmpty() ? 0L : undoLog.get(undoLog.size() - 1).level; + } + @Override public int size() { return delegate.size(); diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoableCollection.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/Undoable.java similarity index 92% rename from evm/src/main/java/org/hyperledger/besu/collections/undo/UndoableCollection.java rename to evm/src/main/java/org/hyperledger/besu/collections/undo/Undoable.java index 25bfaa8ee50..274bbc2297b 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoableCollection.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/Undoable.java @@ -23,7 +23,7 @@ * This allows for tracking of only one undo marker across multiple collections and rolling back * multiple collections to a consistent point with only one number. */ -public interface UndoableCollection { +public interface Undoable { /** The global mark clock for registering marks in undoable collections. */ AtomicLong markState = new AtomicLong(); @@ -48,6 +48,14 @@ static long incrementMarkStatic() { return markState.incrementAndGet(); } + /** + * The last time this object was updated. Any undo requrests greater than this mark will result in + * no changes. + * + * @return The most recent mark. + */ + long lastUpdate(); + /** * Returns the state of the collection to the state it was in when the mark was retrieved. * Additions and removals are undone un reverse order until the collection state is restored. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index a77b4ac93a7..4ce73ef9cc6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -78,6 +78,7 @@ public class EVM { /** The constant OVERFLOW_RESPONSE. */ protected static final OperationResult OVERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + /** The constant UNDERFLOW_RESPONSE. */ protected static final OperationResult UNDERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); @@ -86,6 +87,7 @@ public class EVM { private final GasCalculator gasCalculator; private final Operation endOfScriptStop; private final CodeCache codeCache; + private final EvmConfiguration evmConfiguration; private final EvmSpecVersion evmSpecVersion; // Optimized operation flags @@ -107,6 +109,7 @@ public EVM( this.operations = operations; this.gasCalculator = gasCalculator; this.endOfScriptStop = new VirtualOperation(new StopOperation(gasCalculator)); + this.evmConfiguration = evmConfiguration; this.codeCache = new CodeCache(evmConfiguration); this.evmSpecVersion = evmSpecVersion; @@ -131,6 +134,15 @@ public int getMaxEOFVersion() { return evmSpecVersion.maxEofVersion; } + /** + * Returns the non-fork related configuration parameters of the EVM. + * + * @return the EVM coniguration. + */ + public EvmConfiguration getEvmConfiguration() { + return evmConfiguration; + } + /** * Returns the configured EVM spec version for this EVM * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java index 05ad66574fc..edc3edd1a43 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java @@ -38,7 +38,7 @@ public class SimpleAccount implements MutableAccount { private final Account parent; - private boolean mutable = true; + private boolean immutable = false; private Address address; private final Supplier addressHash = @@ -140,7 +140,7 @@ public NavigableMap storageEntriesFrom( @Override public void setNonce(final long value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } nonce = value; @@ -148,7 +148,7 @@ public void setNonce(final long value) { @Override public void setBalance(final Wei value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } balance = value; @@ -156,7 +156,7 @@ public void setBalance(final Wei value) { @Override public void setCode(final Bytes code) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } this.code = code; @@ -165,7 +165,7 @@ public void setCode(final Bytes code) { @Override public void setStorageValue(final UInt256 key, final UInt256 value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } storage.put(key, value); @@ -173,7 +173,7 @@ public void setStorageValue(final UInt256 key, final UInt256 value) { @Override public void clearStorage() { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } storage.clear(); @@ -186,7 +186,23 @@ public Map getUpdatedStorage() { @Override public void becomeImmutable() { - mutable = false; + immutable = true; + } + + /** + * Commit this simple account entry to the parent. + * + * @return true if there was a parent account that was committed to + */ + public boolean commit() { + if (parent instanceof SimpleAccount simpleAccount) { + simpleAccount.balance = balance; + simpleAccount.nonce = nonce; + simpleAccount.storage.putAll(storage); + return true; + } else { + return false; + } } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java index 19533a538a5..c3ad7ba17aa 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java @@ -16,21 +16,22 @@ package org.hyperledger.besu.evm.internal; /** The Evm configuration. */ -public class EvmConfiguration { - /** The constant DEFAULT. */ - public static final EvmConfiguration DEFAULT = new EvmConfiguration(32_000L); - - private final long jumpDestCacheWeightKB; +public record EvmConfiguration(long jumpDestCacheWeightKB, WorldUpdaterMode worldUpdaterMode) { - /** - * Instantiates a new Evm configuration. - * - * @param jumpDestCacheWeightKB the jump dest cache weight kb - */ - public EvmConfiguration(final long jumpDestCacheWeightKB) { - this.jumpDestCacheWeightKB = jumpDestCacheWeightKB; + /** How should the world state update be handled within transactions? */ + public enum WorldUpdaterMode { + /** + * Stack updates, requiring original account and storage values to read through the whole stack + */ + STACKED, + /** Share a single state for accounts and storage values, undoing changes on reverts. */ + JOURNALED } + /** The constant DEFAULT. */ + public static final EvmConfiguration DEFAULT = + new EvmConfiguration(32_000L, WorldUpdaterMode.STACKED); + /** * Gets jump dest cache weight bytes. * @@ -39,13 +40,4 @@ public EvmConfiguration(final long jumpDestCacheWeightKB) { public long getJumpDestCacheWeightBytes() { return jumpDestCacheWeightKB * 1024L; } - - /** - * Gets jump dest cache weight kb. - * - * @return the jump dest cache weight kb - */ - public long getJumpDestCacheWeightKB() { - return jumpDestCacheWeightKB; - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java index 71d599d0a2b..821406c8aa4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java @@ -146,25 +146,6 @@ static long clampedMultiply(final long a, final long b) { } } - /** - * Multiplies a and b, but if an underflow/overflow occurs return the Integer max/min value - * - * @param a first value - * @param b second value - * @return value of a times b if no over/underflows or Integer.MAX_VALUE/Integer.MIN_VALUE - * otherwise - */ - static int clampedMultiply(final int a, final int b) { - long r = (long) a * (long) b; - int ri = (int) r; - if (ri == r) { - return ri; - } else { - // out of bounds, clamp it! - return ((a ^ b) < 0) ? Integer.MIN_VALUE : Integer.MAX_VALUE; - } - } - /** * Returns the lesser of the two values, when compared as an unsigned value * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AbstractWorldUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AbstractWorldUpdater.java index 2eb48dd1add..947f6f24270 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AbstractWorldUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AbstractWorldUpdater.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import java.util.Collection; import java.util.Collections; @@ -40,9 +41,11 @@ public abstract class AbstractWorldUpdater> updatedAccounts = new ConcurrentHashMap<>(); + /** The Deleted accounts. */ protected Set

deletedAccounts = Collections.synchronizedSet(new BytesTrieSet<>(Address.SIZE)); @@ -51,9 +54,11 @@ public abstract class AbstractWorldUpdatermay or may not be reflected to the created updater, so it is * strongly advised to not update this updater until the returned one is discarded - * (either after having been committed, or because the updates it represent are meant to be + * (either after having been committed, or because the updates it represents are meant to be * discarded). */ @Override public WorldUpdater updater() { - return new StackedUpdater<>(this); + return switch (evmConfiguration.worldUpdaterMode()) { + case STACKED -> new StackedUpdater<>(this, evmConfiguration); + case JOURNALED -> new JournaledUpdater<>(this, evmConfiguration); + }; } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledAccount.java new file mode 100644 index 00000000000..e527e5301d9 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledAccount.java @@ -0,0 +1,397 @@ +/* + * Copyright ConsenSys AG. + * + * 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.evm.worldstate; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.hyperledger.besu.collections.undo.UndoNavigableMap; +import org.hyperledger.besu.collections.undo.UndoScalar; +import org.hyperledger.besu.collections.undo.Undoable; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.ModificationNotAllowedException; +import org.hyperledger.besu.evm.account.AccountStorageEntry; +import org.hyperledger.besu.evm.account.MutableAccount; + +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import javax.annotation.Nullable; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +/** + * An implementation of {@link MutableAccount} that tracks updates made to the account since the + * creation of the updater this is linked to. + * + *

Note that in practice this only track the modified value of the nonce and balance, but doesn't + * remind if those were modified or not (the reason being that any modification of an account imply + * the underlying trie node will have to be updated, and so knowing if the nonce and balance where + * updated or not doesn't matter, we just need their new value). + */ +public class JournaledAccount implements MutableAccount, Undoable { + private final Address address; + private final Hash addressHash; + + @Nullable private MutableAccount account; + + private long transactionBoundaryMark; + private final UndoScalar nonce; + private final UndoScalar balance; + private final UndoScalar code; + private final UndoScalar codeHash; + private final UndoScalar deleted; + + // Only contains updated storage entries, but may contain entry with a value of 0 to signify + // deletion. + private final UndoNavigableMap updatedStorage; + private boolean storageWasCleared = false; + + boolean immutable; + + /** + * Instantiates a new Update tracking account. + * + * @param address the address + */ + JournaledAccount(final Address address) { + checkNotNull(address); + this.address = address; + this.addressHash = this.address.addressHash(); + this.account = null; + + this.nonce = UndoScalar.of(0L); + this.balance = UndoScalar.of(Wei.ZERO); + + this.code = UndoScalar.of(Bytes.EMPTY); + this.codeHash = UndoScalar.of(Hash.EMPTY); + this.deleted = UndoScalar.of(Boolean.FALSE); + this.updatedStorage = UndoNavigableMap.of(new TreeMap<>()); + this.transactionBoundaryMark = mark(); + } + + /** + * Instantiates a new Update tracking account. + * + * @param account the account + */ + public JournaledAccount(final MutableAccount account) { + checkNotNull(account); + + this.address = account.getAddress(); + this.addressHash = + (account instanceof JournaledAccount journaledAccount) + ? journaledAccount.addressHash + : this.address.addressHash(); + this.account = account; + + if (account instanceof JournaledAccount that) { + this.nonce = that.nonce; + this.balance = that.balance; + + this.code = that.code; + this.codeHash = that.codeHash; + + this.deleted = that.deleted; + + this.updatedStorage = that.updatedStorage; + } else { + this.nonce = UndoScalar.of(account.getNonce()); + this.balance = UndoScalar.of(account.getBalance()); + + this.code = UndoScalar.of(account.getCode()); + this.codeHash = UndoScalar.of(account.getCodeHash()); + + this.deleted = UndoScalar.of(Boolean.FALSE); + + this.updatedStorage = UndoNavigableMap.of(new TreeMap<>()); + } + transactionBoundaryMark = mark(); + } + + /** + * The original account over which this tracks updates. + * + * @return The original account over which this tracks updates, or {@code null} if this is a newly + * created account. + */ + public MutableAccount getWrappedAccount() { + return account; + } + + /** + * Sets wrapped account. + * + * @param account the account + */ + public void setWrappedAccount(final MutableAccount account) { + if (this.account == null) { + this.account = account; + storageWasCleared = false; + } else { + throw new IllegalStateException("Already tracking a wrapped account"); + } + } + + /** + * Whether the code of the account was modified. + * + * @return {@code true} if the code was updated. + */ + public boolean codeWasUpdated() { + return code.lastUpdate() >= transactionBoundaryMark; + } + + /** + * A map of the storage entries that were modified. + * + * @return a map containing all entries that have been modified. This may contain entries + * with a value of 0 to signify deletion. + */ + @Override + public Map getUpdatedStorage() { + return updatedStorage; + } + + @Override + public void becomeImmutable() { + immutable = true; + } + + @Override + public Address getAddress() { + return address; + } + + @Override + public Hash getAddressHash() { + return addressHash; + } + + @Override + public long getNonce() { + return nonce.get(); + } + + @Override + public void setNonce(final long value) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + nonce.set(value); + } + + @Override + public Wei getBalance() { + return balance.get(); + } + + @Override + public void setBalance(final Wei value) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + balance.set(value); + } + + @Override + public Bytes getCode() { + return code.get(); + } + + @Override + public Hash getCodeHash() { + return codeHash.get(); + } + + @Override + public boolean hasCode() { + return !code.get().isEmpty(); + } + + /** + * Mark the account as deleted/not deleted + * + * @param accountDeleted delete or don't delete this account. + */ + public void setDeleted(final boolean accountDeleted) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + deleted.set(accountDeleted); + } + + /** + * Is the account marked as deleted? + * + * @return is the account deleted? + */ + public Boolean getDeleted() { + return deleted.get(); + } + + @Override + public void setCode(final Bytes code) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + this.code.set(code == null ? Bytes.EMPTY : code); + this.codeHash.set(code == null ? Hash.EMPTY : Hash.hash(code)); + } + + /** Mark transaction boundary. */ + void markTransactionBoundary() { + transactionBoundaryMark = mark(); + } + + @Override + public UInt256 getStorageValue(final UInt256 key) { + final UInt256 value = updatedStorage.get(key); + if (value != null) { + return value; + } + if (storageWasCleared) { + return UInt256.ZERO; + } + + // We haven't updated the key-value yet, so either it's a new account, and it doesn't have the + // key, or we should query the underlying storage for its existing value (which might be 0). + return account == null ? UInt256.ZERO : account.getStorageValue(key); + } + + @Override + public UInt256 getOriginalStorageValue(final UInt256 key) { + // if storage was cleared then it is because it was an empty account, hence zero storage + // if we have no backing account, it's a new account, hence zero storage + // otherwise ask outside of what we are journaling, journaled change may not be original value + return (storageWasCleared || account == null) ? UInt256.ZERO : account.getStorageValue(key); + } + + @Override + public NavigableMap storageEntriesFrom( + final Bytes32 startKeyHash, final int limit) { + final NavigableMap entries; + if (account != null) { + entries = account.storageEntriesFrom(startKeyHash, limit); + } else { + entries = new TreeMap<>(); + } + updatedStorage.entrySet().stream() + .map(entry -> AccountStorageEntry.forKeyAndValue(entry.getKey(), entry.getValue())) + .filter(entry -> entry.getKeyHash().compareTo(startKeyHash) >= 0) + .forEach(entry -> entries.put(entry.getKeyHash(), entry)); + + while (entries.size() > limit) { + entries.remove(entries.lastKey()); + } + return entries; + } + + @Override + public void setStorageValue(final UInt256 key, final UInt256 value) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + updatedStorage.put(key, value); + } + + @Override + public void clearStorage() { + if (immutable) { + throw new ModificationNotAllowedException(); + } + storageWasCleared = true; + updatedStorage.clear(); + } + + /** + * Gets storage was cleared. + * + * @return boolean if storage was cleared + */ + public boolean getStorageWasCleared() { + return storageWasCleared; + } + + /** + * Sets storage was cleared. + * + * @param storageWasCleared the storage was cleared + */ + public void setStorageWasCleared(final boolean storageWasCleared) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + this.storageWasCleared = storageWasCleared; + } + + @Override + public String toString() { + String storage = updatedStorage.isEmpty() ? "[not updated]" : updatedStorage.toString(); + if (updatedStorage.isEmpty() && storageWasCleared) { + storage = "[cleared]"; + } + return String.format( + "%s -> {nonce: %s, balance:%s, code:%s, storage:%s }", + address, + nonce, + balance, + code.mark() >= transactionBoundaryMark ? "[not updated]" : code.get(), + storage); + } + + @Override + public long lastUpdate() { + return Math.max( + nonce.lastUpdate(), + Math.max( + balance.lastUpdate(), + Math.max( + code.lastUpdate(), Math.max(codeHash.lastUpdate(), updatedStorage.lastUpdate())))); + } + + @Override + public void undo(final long mark) { + nonce.undo(mark); + balance.undo(mark); + code.undo(mark); + codeHash.undo(mark); + deleted.undo(mark); + updatedStorage.undo(mark); + } + + /** Commit this journaled account entry to the parent, if it is not a journaled account. */ + public void commit() { + if (!(account instanceof JournaledAccount)) { + if (nonce.updated()) { + account.setNonce(nonce.get()); + } + if (balance.updated()) { + account.setBalance(balance.get()); + } + if (code.updated()) { + account.setCode(code.get() == null ? Bytes.EMPTY : code.get()); + } + if (updatedStorage.updated()) { + updatedStorage.forEach(account::setStorageValue); + } + } + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java new file mode 100644 index 00000000000..bdec559e071 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java @@ -0,0 +1,184 @@ +/* + * 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.evm.worldstate; + +import org.hyperledger.besu.collections.undo.UndoMap; +import org.hyperledger.besu.collections.undo.UndoSet; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Optional; + +/** + * The Journaled updater. + * + * @param the WorldView type parameter + */ +public class JournaledUpdater implements WorldUpdater { + + final EvmConfiguration evmConfiguration; + final WorldUpdater parentWorld; + final AbstractWorldUpdater rootWorld; + final UndoMap accounts; + final UndoSet

deleted; + final long undoMark; + + /** + * Instantiates a new Stacked updater. + * + * @param world the world + * @param evmConfiguration the EVM Configuration parameters + */ + @SuppressWarnings("unchecked") + public JournaledUpdater(final WorldUpdater world, final EvmConfiguration evmConfiguration) { + parentWorld = world; + this.evmConfiguration = evmConfiguration; + if (world instanceof JournaledUpdater) { + JournaledUpdater journaledUpdater = (JournaledUpdater) world; + accounts = journaledUpdater.accounts; + deleted = journaledUpdater.deleted; + rootWorld = journaledUpdater.rootWorld; + } else if (world instanceof AbstractWorldUpdater) { + accounts = new UndoMap<>(new HashMap<>()); + deleted = UndoSet.of(new HashSet<>()); + rootWorld = (AbstractWorldUpdater) world; + } else { + throw new IllegalArgumentException( + "WorldUpdater must be a JournaledWorldUpdater or an AbstractWorldUpdater"); + } + undoMark = accounts.mark(); + } + + /** + * Get an account suitable for mutation. Defer to parent if not tracked locally. + * + * @param address the account at the address, for mutaton. + * @return the mutable account + */ + protected MutableAccount getForMutation(final Address address) { + final JournaledAccount wrappedTracker = accounts.get(address); + if (wrappedTracker != null) { + return wrappedTracker; + } + final MutableAccount account = rootWorld.getForMutation(address); + return account == null ? null : new UpdateTrackingAccount<>(account); + } + + @Override + public Collection getTouchedAccounts() { + return new ArrayList<>(accounts.values()); + } + + @Override + public Collection
getDeletedAccountAddresses() { + return new ArrayList<>(deleted); + } + + /** + * Remove all changes done by this layer. Rollback to the state prior to the updater's changes. + */ + protected void reset() { + accounts.values().forEach(a -> a.undo(undoMark)); + accounts.undo(undoMark); + deleted.undo(undoMark); + } + + @Override + public void revert() { + reset(); + } + + @Override + public void commit() { + if (!(parentWorld instanceof JournaledUpdater)) { + accounts.values().forEach(JournaledAccount::commit); + deleted.forEach(parentWorld::deleteAccount); + } + } + + @Override + public Optional parentUpdater() { + return Optional.of(parentWorld); + } + + /** Mark transaction boundary. */ + @Override + public void markTransactionBoundary() { + accounts.values().forEach(JournaledAccount::markTransactionBoundary); + } + + @Override + public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { + JournaledAccount journaledAccount = + new JournaledAccount(rootWorld.createAccount(address, nonce, balance)); + accounts.put(address, journaledAccount); + return new JournaledAccount(journaledAccount); + } + + @Override + public MutableAccount getAccount(final Address address) { + // We may have updated it already, so check that first. + final JournaledAccount existing = accounts.get(address); + if (existing != null) { + return existing; + } + if (deleted.contains(address)) { + return null; + } + + // Otherwise, get it from our wrapped view and create a new update tracker. + final MutableAccount origin = rootWorld.getAccount(address); + if (origin == null) { + return null; + } else { + var newAccount = new JournaledAccount(origin); + accounts.put(address, newAccount); + return newAccount; + } + } + + @Override + public void deleteAccount(final Address address) { + deleted.add(address); + var account = accounts.get(address); + if (account != null) { + account.setDeleted(true); + } + } + + @Override + public Account get(final Address address) { + final MutableAccount existing = accounts.get(address); + if (existing != null) { + return existing; + } + if (deleted.contains(address)) { + return null; + } + return rootWorld.get(address); + } + + @Override + public WorldUpdater updater() { + return new JournaledUpdater(this, evmConfiguration); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java index 348f76dfa10..471a807e700 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import java.util.ArrayList; import java.util.Collection; @@ -33,9 +34,11 @@ public class StackedUpdater * Instantiates a new Stacked updater. * * @param world the world + * @param evmConfiguration the EVM Configuration parameters */ - public StackedUpdater(final AbstractWorldUpdater world) { - super(world); + public StackedUpdater( + final AbstractWorldUpdater world, final EvmConfiguration evmConfiguration) { + super(world, evmConfiguration); } @Override @@ -109,6 +112,7 @@ public void commit() { } /** Mark transaction boundary. */ + @Override public void markTransactionBoundary() { getUpdatedAccounts().forEach(UpdateTrackingAccount::markTransactionBoundary); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java index bc8a0fdfcd5..a8c697a21f6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java @@ -52,7 +52,7 @@ public class UpdateTrackingAccount implements MutableAccount @Nullable private A account; // null if this is a new account. - private boolean mutable = true; + private boolean immutable; private long nonce; private Wei balance; @@ -173,7 +173,7 @@ public long getNonce() { @Override public void setNonce(final long value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } this.nonce = value; @@ -186,7 +186,7 @@ public Wei getBalance() { @Override public void setBalance(final Wei value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } this.balance = value; @@ -221,7 +221,7 @@ public boolean hasCode() { @Override public void setCode(final Bytes code) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } this.updatedCode = code; @@ -281,7 +281,7 @@ public NavigableMap storageEntriesFrom( @Override public void setStorageValue(final UInt256 key, final UInt256 value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } updatedStorage.put(key, value); @@ -289,7 +289,7 @@ public void setStorageValue(final UInt256 key, final UInt256 value) { @Override public void clearStorage() { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } storageWasCleared = true; @@ -298,7 +298,7 @@ public void clearStorage() { @Override public void becomeImmutable() { - mutable = false; + immutable = true; } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java index fe88c86a7df..9b57b6ab9e3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java @@ -38,7 +38,7 @@ public interface WorldUpdater extends MutableWorldView { * Creates a new account, or reset it (that is, act as if it was deleted and created anew) if it * already exists. * - *

After this call, the account will exists and will have the provided nonce and balance. His + *

After this call, the account will exists and will have the provided nonce and balance. The * code and storage will be empty. * * @param address the address of the account to create (or reset). @@ -151,4 +151,9 @@ default void clearAccountsThatAreEmpty() { new ArrayList<>(getTouchedAccounts()) .stream().filter(Account::isEmpty).forEach(a -> deleteAccount(a.getAddress())); } + + /** Mark transaction boundary. */ + default void markTransactionBoundary() { + // default is to ignore + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java index 5819ade39d7..64e2b7ae07e 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java @@ -37,7 +37,7 @@ public class ToyAccount implements MutableAccount { private final Account parent; - private boolean mutable = true; + private boolean immutable = false; private Address address; private final Supplier addressHash = @@ -120,7 +120,7 @@ public NavigableMap storageEntriesFrom( @Override public void setNonce(final long value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } nonce = value; @@ -128,7 +128,7 @@ public void setNonce(final long value) { @Override public void setBalance(final Wei value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } balance = value; @@ -136,7 +136,7 @@ public void setBalance(final Wei value) { @Override public void setCode(final Bytes code) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } this.code = code; @@ -145,7 +145,7 @@ public void setCode(final Bytes code) { @Override public void setStorageValue(final UInt256 key, final UInt256 value) { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } storage.put(key, value); @@ -153,7 +153,7 @@ public void setStorageValue(final UInt256 key, final UInt256 value) { @Override public void clearStorage() { - if (!mutable) { + if (immutable) { throw new ModificationNotAllowedException(); } storage.clear(); @@ -166,6 +166,6 @@ public Map getUpdatedStorage() { @Override public void becomeImmutable() { - mutable = false; + immutable = true; } }