diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java index 8862d9461e2..72f97133eb2 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java @@ -22,7 +22,12 @@ import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.rlp.RLPInput; +import java.util.concurrent.ExecutionException; + import com.fasterxml.jackson.annotation.JsonCreator; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.bytes.DelegatingBytes; @@ -75,6 +80,18 @@ public class Address extends DelegatingBytes { /** The constant ZERO. */ public static final Address ZERO = Address.fromHexString("0x0"); + static LoadingCache hashCache = + CacheBuilder.newBuilder() + .maximumSize(4000) + // .weakKeys() // unless we "intern" all addresses we cannot use weak or soft keys. + .build( + new CacheLoader<>() { + @Override + public Hash load(final Address key) { + return Hash.hash(key); + } + }); + /** * Instantiates a new Address. * @@ -237,4 +254,17 @@ public static Address privateContractAddress( out.endList(); }))); } + + /** + * Returns the hash of the address. Backed by a cache for performance reasons. + * + * @return the hash of the address. + */ + public Hash addressHash() { + try { + return hashCache.get(this); + } catch (ExecutionException e) { + return Hash.hash(this); + } + } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java index b051b0f7976..8307a8fef4a 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java @@ -112,25 +112,19 @@ public MessageFrame createMessageFrame() { public MessageFrame.Builder createMessageFrameBuilder() { return MessageFrame.builder() + .parentMessageFrame(messageFrame) .type(MessageFrame.Type.MESSAGE_CALL) - .messageFrameStack(messageFrame.getMessageFrameStack()) .worldUpdater(messageFrame.getWorldUpdater()) .initialGas(messageFrame.getRemainingGas()) .address(messageFrame.getContractAddress()) - .originator(messageFrame.getOriginatorAddress()) .contract(messageFrame.getRecipientAddress()) - .gasPrice(messageFrame.getGasPrice()) .inputData(messageFrame.getInputData()) .sender(messageFrame.getSenderAddress()) .value(messageFrame.getValue()) .apparentValue(messageFrame.getApparentValue()) .code(messageFrame.getCode()) - .blockValues(messageFrame.getBlockValues()) - .depth(messageFrame.getMessageStackDepth()) .isStatic(messageFrame.isStatic()) - .completer(messageFrame -> {}) - .miningBeneficiary(messageFrame.getMiningBeneficiary()) - .maxStackSize(messageFrame.getMaxStackSize()); + .completer(frame -> {}); } public void cleanUp() throws IOException { 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 a7692b48d22..14be477a07d 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 @@ -83,7 +83,7 @@ public BonsaiAccount( this( context, address, - Hash.hash(address), + address.addressHash(), stateTrieAccount.getNonce(), stateTrieAccount.getBalance(), stateTrieAccount.getStorageRoot(), @@ -142,7 +142,7 @@ public static BonsaiAccount fromRLP( in.leaveList(); return new BonsaiAccount( - context, address, Hash.hash(address), nonce, balance, storageRoot, codeHash, mutable); + context, address, address.addressHash(), nonce, balance, storageRoot, codeHash, mutable); } @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 b3e7057f80e..986e736745a 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 @@ -105,11 +105,11 @@ public BonsaiWorldStateProvider( this.persistedState = new BonsaiWorldState(this, worldStateStorage); this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; blockchain - .getBlockHeader(persistedState.worldStateBlockHash) + .getBlockHeader(persistedState.getWorldStateBlockHash()) .ifPresent( blockHeader -> this.trieLogManager.addCachedLayer( - blockHeader, persistedState.worldStateRootHash, persistedState)); + blockHeader, persistedState.getWorldStateRootHash(), persistedState)); } @VisibleForTesting @@ -124,11 +124,11 @@ public BonsaiWorldStateProvider( this.persistedState = new BonsaiWorldState(this, worldStateStorage); this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; blockchain - .getBlockHeader(persistedState.worldStateBlockHash) + .getBlockHeader(persistedState.getWorldStateBlockHash()) .ifPresent( blockHeader -> this.trieLogManager.addCachedLayer( - blockHeader, persistedState.worldStateRootHash, persistedState)); + blockHeader, persistedState.getWorldStateRootHash(), persistedState)); } @Override @@ -300,7 +300,7 @@ public MutableWorldState getMutable() { public void prepareStateHealing(final Address address, final Bytes location) { final Set keysToDelete = new HashSet<>(); final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = worldStateStorage.updater(); - final Hash accountHash = Hash.hash(address); + final Hash accountHash = address.addressHash(); final StoredMerklePatriciaTrie accountTrie = new StoredMerklePatriciaTrie<>( (l, h) -> { @@ -310,7 +310,7 @@ public void prepareStateHealing(final Address address, final Bytes location) { } return node; }, - persistedState.worldStateRootHash, + persistedState.getWorldStateRootHash(), Function.identity(), Function.identity()); try { @@ -359,7 +359,7 @@ public void resetArchiveStateTo(final BlockHeader blockHeader) { persistedState.resetWorldStateTo(blockHeader); this.trieLogManager.reset(); this.trieLogManager.addCachedLayer( - blockHeader, persistedState.worldStateRootHash, persistedState); + blockHeader, persistedState.getWorldStateRootHash(), persistedState); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java index 9dd4955989a..f53342d6317 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java @@ -52,9 +52,8 @@ public CachedMerkleTrieLoader(final ObservableMetricsSystem metricsSystem) { CacheMetricsCollector cacheMetrics = new CacheMetricsCollector(); cacheMetrics.addCache("accountsNodes", accountNodes); cacheMetrics.addCache("storageNodes", storageNodes); - if (metricsSystem instanceof PrometheusMetricsSystem) - ((PrometheusMetricsSystem) metricsSystem) - .addCollector(BesuMetricCategory.BLOCKCHAIN, () -> cacheMetrics); + if (metricsSystem instanceof PrometheusMetricsSystem prometheusMetricsSystem) + prometheusMetricsSystem.addCollector(BesuMetricCategory.BLOCKCHAIN, () -> cacheMetrics); } public void preLoadAccount( @@ -82,7 +81,7 @@ public void cacheAccountNodes( worldStateRootHash, Function.identity(), Function.identity()); - accountTrie.get(Hash.hash(account)); + accountTrie.get(account.addressHash()); } catch (MerkleTrieException e) { // ignore exception for the cache } finally { @@ -102,7 +101,7 @@ public void cacheStorageNodes( final BonsaiWorldStateKeyValueStorage worldStateStorage, final Address account, final StorageSlotKey slotKey) { - final Hash accountHash = Hash.hash(account); + final Hash accountHash = account.addressHash(); final long storageSubscriberId = worldStateStorage.subscribe(this); try { worldStateStorage 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 5ade72748d6..d0f1be97d89 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 @@ -39,7 +39,6 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.LongStream; import java.util.stream.Stream; @@ -106,7 +105,7 @@ public synchronized void addCachedLayer( .get() .updateWorldStateStorage( new BonsaiSnapshotWorldStateKeyValueStorage( - forWorldState.worldStateStorage, metricsSystem)); + forWorldState.getWorldStateStorage(), metricsSystem)); } } else { LOG.atDebug() @@ -120,7 +119,7 @@ public synchronized void addCachedLayer( new CachedBonsaiWorldView( blockHeader, new BonsaiSnapshotWorldStateKeyValueStorage( - forWorldState.worldStateStorage, metricsSystem))); + forWorldState.getWorldStateStorage(), metricsSystem))); } else { // otherwise, add the layer to the cache cachedWorldStatesByHash.put( @@ -257,14 +256,12 @@ protected TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext) { TrieLogProvider getTrieLogProvider() { return new TrieLogProvider() { @Override - public > Optional getTrieLogLayer( - final Hash blockHash) { + public Optional getTrieLogLayer(final Hash blockHash) { return CachedWorldStorageManager.this.getTrieLogLayer(blockHash); } @Override - public > Optional getTrieLogLayer( - final long blockNumber) { + public Optional getTrieLogLayer(final long blockNumber) { return CachedWorldStorageManager.this .blockchain .getBlockHeader(blockNumber) @@ -273,7 +270,7 @@ public > Optional getTrieLogLayer( } @Override - public > List getTrieLogsByRange( + public List getTrieLogsByRange( final long fromBlockNumber, final long toBlockNumber) { return rangeAsStream(fromBlockNumber, toBlockNumber) .map(blockchain::getBlockHeader) @@ -289,7 +286,7 @@ public > List getTrieLogsByRang header.getBlockHash(), header.getNumber(), layer)))) .filter(Optional::isPresent) .map(Optional::get) - .collect(Collectors.toList()); + .toList(); } Stream rangeAsStream(final long fromBlockNumber, final long toBlockNumber) { 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 9991ee994e1..865471eca99 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 @@ -65,13 +65,13 @@ public class BonsaiWorldState private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldState.class); - public BonsaiWorldStateKeyValueStorage worldStateStorage; + private BonsaiWorldStateKeyValueStorage worldStateStorage; private final BonsaiWorldStateProvider archive; private final BonsaiWorldStateUpdateAccumulator accumulator; - public Hash worldStateRootHash; - public Hash worldStateBlockHash; + private Hash worldStateRootHash; + Hash worldStateBlockHash; private boolean isFrozen; @@ -112,6 +112,24 @@ public BonsaiWorldState( this.accumulator = updater; } + /** + * Returns the world state block hash of this world state + * + * @return the world state block hash. + */ + public Hash getWorldStateBlockHash() { + return worldStateBlockHash; + } + + /** + * Returns the world state root hash of this world state + * + * @return the world state root hash. + */ + public Hash getWorldStateRootHash() { + return worldStateRootHash; + } + public BonsaiWorldStateProvider getArchive() { return archive; } @@ -127,7 +145,7 @@ private boolean isPersisted(final WorldStateStorage worldStateStorage) { @Override public Optional getCode(@Nonnull final Address address, final Hash codeHash) { - return worldStateStorage.getCode(codeHash, Hash.hash(address)); + return worldStateStorage.getCode(codeHash, address.addressHash()); } /** @@ -182,15 +200,14 @@ private Hash calculateRootHash( // TODO write to a cache and then generate a layer update from that and the // DB tx updates. Right now it is just DB updates. maybeStateUpdater.ifPresent( - bonsaiUpdater -> { - accountTrie.commit( - (location, hash, value) -> - writeTrieNode( - TRIE_BRANCH_STORAGE, - bonsaiUpdater.getWorldStateTransaction(), - location, - value)); - }); + bonsaiUpdater -> + accountTrie.commit( + (location, hash, value) -> + writeTrieNode( + TRIE_BRANCH_STORAGE, + bonsaiUpdater.getWorldStateTransaction(), + location, + value))); final Bytes32 rootHash = accountTrie.getRootHash(); return Hash.wrap(rootHash); } @@ -234,8 +251,8 @@ private void updateCode( for (final Map.Entry> codeUpdate : worldStateUpdater.getCodeToUpdate().entrySet()) { final Bytes updatedCode = codeUpdate.getValue().getUpdated(); - final Hash accountHash = Hash.hash(codeUpdate.getKey()); - if (updatedCode == null || updatedCode.size() == 0) { + final Hash accountHash = codeUpdate.getKey().addressHash(); + if (updatedCode == null || updatedCode.isEmpty()) { bonsaiUpdater.removeCode(accountHash); } else { bonsaiUpdater.putCode(accountHash, null, updatedCode); @@ -250,7 +267,7 @@ private void updateAccountStorageState( final Map.Entry>> storageAccountUpdate) { final Address updatedAddress = storageAccountUpdate.getKey(); - final Hash updatedAddressHash = Hash.hash(updatedAddress); + final Hash updatedAddressHash = updatedAddress.addressHash(); if (worldStateUpdater.getAccountsToUpdate().containsKey(updatedAddress)) { final BonsaiValue accountValue = worldStateUpdater.getAccountsToUpdate().get(updatedAddress); @@ -297,12 +314,11 @@ private void updateAccountStorageState( final BonsaiAccount accountUpdated = accountValue.getUpdated(); if (accountUpdated != null) { maybeStateUpdater.ifPresent( - bonsaiUpdater -> { - storageTrie.commit( - (location, key, value) -> - writeStorageTrieNode( - bonsaiUpdater, updatedAddressHash, location, key, value)); - }); + bonsaiUpdater -> + storageTrie.commit( + (location, key, value) -> + writeStorageTrieNode( + bonsaiUpdater, updatedAddressHash, location, key, value))); final Hash newStorageRoot = Hash.wrap(storageTrie.getRootHash()); accountUpdated.setStorageRoot(newStorageRoot); } @@ -320,7 +336,7 @@ private void clearStorage( // because we are clearing persisted values we need the account root as persisted final BonsaiAccount oldAccount = worldStateStorage - .getAccount(Hash.hash(address)) + .getAccount(address.addressHash()) .map(bytes -> fromRLP(BonsaiWorldState.this, address, bytes, true)) .orElse(null); if (oldAccount == null) { @@ -328,7 +344,7 @@ private void clearStorage( // block. A not-uncommon DeFi bot pattern. continue; } - final Hash addressHash = Hash.hash(address); + final Hash addressHash = address.addressHash(); final MerkleTrie storageTrie = createTrie( (location, key) -> getStorageTrieNode(addressHash, location, key), @@ -341,7 +357,7 @@ private void clearStorage( .forEach( k -> bonsaiUpdater.removeStorageValueBySlotHash( - Hash.hash(address), Hash.wrap(k))); + address.addressHash(), Hash.wrap(k))); entriesToDelete.keySet().forEach(storageTrie::remove); if (entriesToDelete.size() == 256) { entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256); @@ -505,7 +521,7 @@ public Stream streamAccounts(final Bytes32 startKeyHash, fina @Override public Account get(final Address address) { return worldStateStorage - .getAccount(Hash.hash(address)) + .getAccount(address.addressHash()) .map(bytes -> fromRLP(accumulator, address, bytes, true)) .orElse(null); } @@ -546,7 +562,7 @@ public UInt256 getStorageValue(final Address address, final UInt256 storageKey) public Optional getStorageValueByStorageSlotKey( final Address address, final StorageSlotKey storageSlotKey) { return worldStateStorage - .getStorageValueByStorageSlotKey(Hash.hash(address), storageSlotKey) + .getStorageValueByStorageSlotKey(address.addressHash(), storageSlotKey) .map(UInt256::fromBytes); } @@ -555,7 +571,7 @@ public Optional getStorageValueByStorageSlotKey( final Address address, final StorageSlotKey storageSlotKey) { return worldStateStorage - .getStorageValueByStorageSlotKey(storageRootSupplier, Hash.hash(address), storageSlotKey) + .getStorageValueByStorageSlotKey(storageRootSupplier, address.addressHash(), storageSlotKey) .map(UInt256::fromBytes); } @@ -568,7 +584,7 @@ public UInt256 getPriorStorageValue(final Address address, final UInt256 storage public Map getAllAccountStorage(final Address address, final Hash rootHash) { final StoredMerklePatriciaTrie storageTrie = createTrie( - (location, key) -> getStorageTrieNode(Hash.hash(address), location, key), rootHash); + (location, key) -> getStorageTrieNode(address.addressHash(), location, key), rootHash); return storageTrie.entriesFrom(Bytes32.ZERO, Integer.MAX_VALUE); } 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 24ae04037bb..2a04f92fb19 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 @@ -134,7 +134,7 @@ public EvmAccount createAccount(final Address address, final long nonce, final W new BonsaiAccount( this, address, - Hash.hash(address), + address.addressHash(), nonce, balance, Hash.EMPTY_TRIE_HASH, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index e65b9ce9eb2..a87a35d7257 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -43,7 +43,6 @@ import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.List; @@ -350,7 +349,6 @@ public TransactionProcessingResult processTransaction( dataGas); final WorldUpdater worldUpdater = worldState.updater(); - final Deque messageFrameStack = new ArrayDeque<>(); final ImmutableMap.Builder contextVariablesBuilder = ImmutableMap.builder() .put(KEY_IS_PERSISTING_PRIVATE_STATE, isPersistingPrivateState) @@ -362,7 +360,6 @@ public TransactionProcessingResult processTransaction( final MessageFrame.Builder commonMessageFrameBuilder = MessageFrame.builder() - .messageFrameStack(messageFrameStack) .maxStackSize(maxStackSize) .worldUpdater(worldUpdater.updater()) .initialGas(gasAvailable) @@ -372,7 +369,6 @@ public TransactionProcessingResult processTransaction( .value(transaction.getValue()) .apparentValue(transaction.getValue()) .blockValues(blockHeader) - .depth(0) .completer(__ -> {}) .miningBeneficiary(miningBeneficiary) .blockHashLookup(blockHashLookup) @@ -417,8 +413,8 @@ public TransactionProcessingResult processTransaction( .orElse(CodeV0.EMPTY_CODE)) .build(); } + Deque messageFrameStack = initialFrame.getMessageFrameStack(); - messageFrameStack.addFirst(initialFrame); if (initialFrame.getCode().isValid()) { while (!messageFrameStack.isEmpty()) { process(messageFrameStack.peekFirst(), operationTracer); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java index 8cd8db2bf49..2eeb29cf32b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java @@ -34,7 +34,6 @@ import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.util.ArrayDeque; import java.util.Deque; import java.util.Map; import java.util.Optional; @@ -112,10 +111,8 @@ public TransactionProcessingResult processTransaction( final WorldUpdater mutablePrivateWorldStateUpdater = new DefaultMutablePrivateWorldStateUpdater(publicWorldState, privateWorldState); - final Deque messageFrameStack = new ArrayDeque<>(); final MessageFrame.Builder commonMessageFrameBuilder = MessageFrame.builder() - .messageFrameStack(messageFrameStack) .maxStackSize(maxStackSize) .worldUpdater(mutablePrivateWorldStateUpdater) .initialGas(Long.MAX_VALUE) @@ -125,7 +122,6 @@ public TransactionProcessingResult processTransaction( .value(transaction.getValue()) .apparentValue(transaction.getValue()) .blockValues(blockHeader) - .depth(0) .completer(__ -> {}) .miningBeneficiary(miningBeneficiary) .blockHashLookup(blockHashLookup) @@ -169,8 +165,7 @@ public TransactionProcessingResult processTransaction( .build(); } - messageFrameStack.addFirst(initialFrame); - + final Deque messageFrameStack = initialFrame.getMessageFrameStack(); while (!messageFrameStack.isEmpty()) { process(messageFrameStack.peekFirst(), operationTracer); } @@ -211,13 +206,9 @@ private void process(final MessageFrame frame, final OperationTracer operationTr } private AbstractMessageProcessor getMessageProcessor(final MessageFrame.Type type) { - switch (type) { - case MESSAGE_CALL: - return messageCallProcessor; - case CONTRACT_CREATION: - return contractCreationProcessor; - default: - throw new IllegalStateException("Request for unsupported message processor type " + type); - } + return switch (type) { + case MESSAGE_CALL -> messageCallProcessor; + case CONTRACT_CREATION -> contractCreationProcessor; + }; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProvider.java index d9f23f39566..16023264b7d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProvider.java @@ -62,7 +62,7 @@ public Optional getAccountProof( if (!worldStateStorage.isWorldStateAvailable(worldStateRoot, null)) { return Optional.empty(); } else { - final Hash accountHash = Hash.hash(accountAddress); + final Hash accountHash = accountAddress.addressHash(); final Proof accountProof = newAccountStateTrie(worldStateRoot).getValueWithProof(accountHash); @@ -150,7 +150,7 @@ public boolean isValidRangeProof( final Bytes32 endKeyHash, final Bytes32 rootHash, final List proofs, - final TreeMap keys) { + final SortedMap keys) { // check if it's monotonic increasing if (!Ordering.natural().isOrdered(keys.keySet())) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java index 8d8287a82de..07a10e218ab 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java @@ -49,6 +49,7 @@ public class DebugOperationTracer implements OperationTracer { private long gasRemaining; private Bytes inputData; private int pc; + private int depth; public DebugOperationTracer(final TraceOptions options) { this.options = options; @@ -58,16 +59,16 @@ public DebugOperationTracer(final TraceOptions options) { public void tracePreExecution(final MessageFrame frame) { preExecutionStack = captureStack(frame); gasRemaining = frame.getRemainingGas(); - if (lastFrame != null && frame.getMessageStackDepth() > lastFrame.getDepth()) + if (lastFrame != null && frame.getDepth() > lastFrame.getDepth()) inputData = frame.getInputData().copy(); else inputData = frame.getInputData(); pc = frame.getPC(); + depth = frame.getDepth(); } @Override public void tracePostExecution(final MessageFrame frame, final OperationResult operationResult) { final Operation currentOperation = frame.getCurrentOperation(); - final int depth = frame.getMessageStackDepth(); final String opcode = currentOperation.getName(); final WorldUpdater worldUpdater = frame.getWorldUpdater(); final Bytes outputData = frame.getOutputData(); @@ -122,7 +123,7 @@ public void tracePrecompileCall( frame.getRemainingGas(), OptionalLong.empty(), frame.getGasRefund(), - frame.getMessageStackDepth(), + frame.getDepth(), Optional.empty(), frame.getRecipientAddress(), frame.getValue(), @@ -168,7 +169,7 @@ public void traceAccountCreationResult( frame.getRemainingGas(), OptionalLong.empty(), frame.getGasRefund(), - frame.getMessageStackDepth(), + frame.getDepth(), Optional.of(exceptionalHaltReason), frame.getRecipientAddress(), frame.getValue(), 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 e18931a4ef1..b9283d50da0 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 @@ -112,7 +112,7 @@ public Hash frontierRootHash() { @Override public Account get(final Address address) { - final Hash addressHash = Hash.hash(address); + final Hash addressHash = address.addressHash(); return accountStateTrie .get(addressHash) .map(bytes -> deserializeAccount(address, addressHash, bytes)) @@ -344,7 +344,7 @@ protected Updater(final DefaultMutableWorldState world) { @Override protected WorldStateAccount getForMutation(final Address address) { final DefaultMutableWorldState wrapped = wrappedWorldView(); - final Hash addressHash = Hash.hash(address); + final Hash addressHash = address.addressHash(); return wrapped .accountStateTrie .get(addressHash) @@ -373,7 +373,7 @@ public void commit() { final DefaultMutableWorldState wrapped = wrappedWorldView(); for (final Address address : getDeletedAccounts()) { - final Hash addressHash = Hash.hash(address); + final Hash addressHash = address.addressHash(); wrapped.accountStateTrie.remove(addressHash); wrapped.updatedStorageTries.remove(address); wrapped.updatedAccountCode.remove(address); diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java index 6a3165c5118..bfd56cbaa5d 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java @@ -26,9 +26,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.List; import java.util.Optional; @@ -40,8 +38,8 @@ public class MessageFrameTestFixture { public static final Address DEFAUT_ADDRESS = AddressHelpers.ofValue(244259721); private static final int maxStackSize = DEFAULT_MAX_STACK_SIZE; + private MessageFrame parentFrame; private MessageFrame.Type type = MessageFrame.Type.MESSAGE_CALL; - private Deque messageFrameStack = new ArrayDeque<>(); private Optional blockchain = Optional.empty(); private Optional worldUpdater = Optional.empty(); private long initialGas = Long.MAX_VALUE; @@ -55,17 +53,16 @@ public class MessageFrameTestFixture { private Code code = CodeV0.EMPTY_CODE; private final List stackItems = new ArrayList<>(); private Optional blockHeader = Optional.empty(); - private int depth = 0; private Optional blockHashLookup = Optional.empty(); private ExecutionContextTestFixture executionContextTestFixture; - public MessageFrameTestFixture type(final MessageFrame.Type type) { - this.type = type; + public MessageFrameTestFixture parentFrame(final MessageFrame parentFrame) { + this.parentFrame = parentFrame; return this; } - MessageFrameTestFixture messageFrameStack(final Deque messageFrameStack) { - this.messageFrameStack = messageFrameStack; + public MessageFrameTestFixture type(final MessageFrame.Type type) { + this.type = type; return this; } @@ -140,11 +137,6 @@ public MessageFrameTestFixture blockHeader(final BlockHeader blockHeader) { return this; } - public MessageFrameTestFixture depth(final int depth) { - this.depth = depth; - return this; - } - public MessageFrameTestFixture pushStackItem(final UInt256 item) { stackItems.add(item); return this; @@ -161,8 +153,8 @@ public MessageFrame build() { this.blockHeader.orElseGet(() -> localBlockchain.getBlockHeader(0).get()); final MessageFrame frame = MessageFrame.builder() + .parentMessageFrame(parentFrame) .type(type) - .messageFrameStack(messageFrameStack) .worldUpdater(worldUpdater.orElseGet(this::createDefaultWorldUpdater)) .initialGas(initialGas) .address(address) @@ -175,7 +167,6 @@ public MessageFrame build() { .contract(contract) .code(code) .blockValues(localBlockHeader) - .depth(depth) .completer(c -> {}) .miningBeneficiary(localBlockHeader.getCoinbase()) .blockHashLookup( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java index 5fd11880b29..6f67f657f02 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java @@ -66,14 +66,14 @@ public void setUp(final FlatDbMode flatDbMode) { @ParameterizedTest @MethodSource("data") - public void getCode_returnsEmpty(final FlatDbMode flatDbMode) { + void getCode_returnsEmpty(final FlatDbMode flatDbMode) { setUp(flatDbMode); assertThat(storage.getCode(Hash.EMPTY, Hash.EMPTY)).contains(Bytes.EMPTY); } @ParameterizedTest @MethodSource("data") - public void getAccountStateTrieNode_returnsEmptyNode(final FlatDbMode flatDbMode) { + void getAccountStateTrieNode_returnsEmptyNode(final FlatDbMode flatDbMode) { setUp(flatDbMode); assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) .contains(MerkleTrie.EMPTY_TRIE_NODE); @@ -81,7 +81,7 @@ public void getAccountStateTrieNode_returnsEmptyNode(final FlatDbMode flatDbMode @ParameterizedTest @MethodSource("data") - public void getAccountStorageTrieNode_returnsEmptyNode(final FlatDbMode flatDbMode) { + void getAccountStorageTrieNode_returnsEmptyNode(final FlatDbMode flatDbMode) { setUp(flatDbMode); assertThat( storage.getAccountStorageTrieNode( @@ -91,21 +91,21 @@ public void getAccountStorageTrieNode_returnsEmptyNode(final FlatDbMode flatDbMo @ParameterizedTest @MethodSource("data") - public void getNodeData_returnsEmptyValue(final FlatDbMode flatDbMode) { + void getNodeData_returnsEmptyValue(final FlatDbMode flatDbMode) { setUp(flatDbMode); assertThat(storage.getNodeData(null, null)).isEmpty(); } @ParameterizedTest @MethodSource("data") - public void getNodeData_returnsEmptyNode(final FlatDbMode flatDbMode) { + void getNodeData_returnsEmptyNode(final FlatDbMode flatDbMode) { setUp(flatDbMode); assertThat(storage.getNodeData(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)).isEmpty(); } @ParameterizedTest @MethodSource("data") - public void getCode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { + void getCode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { setUp(flatDbMode); storage .updater() @@ -119,7 +119,7 @@ public void getCode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { @ParameterizedTest @MethodSource("data") - public void getCode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { + void getCode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { setUp(flatDbMode); final Bytes bytes = Bytes.fromHexString("0x123456"); storage.updater().putCode(Hash.EMPTY, bytes).commit(); @@ -129,7 +129,7 @@ public void getCode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { @ParameterizedTest @MethodSource("data") - public void getAccountStateTrieNode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { + void getAccountStateTrieNode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { setUp(flatDbMode); storage .updater() @@ -145,7 +145,7 @@ public void getAccountStateTrieNode_saveAndGetSpecialValues(final FlatDbMode fla @ParameterizedTest @MethodSource("data") - public void getAccountStateTrieNode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { + void getAccountStateTrieNode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { setUp(flatDbMode); final Bytes location = Bytes.fromHexString("0x01"); final Bytes bytes = Bytes.fromHexString("0x123456"); @@ -157,7 +157,7 @@ public void getAccountStateTrieNode_saveAndGetRegularValue(final FlatDbMode flat @ParameterizedTest @MethodSource("data") - public void getAccountStorageTrieNode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { + void getAccountStorageTrieNode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { setUp(flatDbMode); storage @@ -180,9 +180,9 @@ public void getAccountStorageTrieNode_saveAndGetSpecialValues(final FlatDbMode f @ParameterizedTest @MethodSource("data") - public void getAccountStorageTrieNode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { + void getAccountStorageTrieNode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { setUp(flatDbMode); - final Hash accountHash = Hash.hash(Address.fromHexString("0x1")); + final Hash accountHash = Address.fromHexString("0x1").addressHash(); final Bytes location = Bytes.fromHexString("0x01"); final Bytes bytes = Bytes.fromHexString("0x123456"); @@ -197,7 +197,7 @@ public void getAccountStorageTrieNode_saveAndGetRegularValue(final FlatDbMode fl @ParameterizedTest @MethodSource("data") - public void getAccount_notLoadFromTrieWhenEmptyAndFlatDbFullMode(final FlatDbMode flatDbMode) { + void getAccount_notLoadFromTrieWhenEmptyAndFlatDbFullMode(final FlatDbMode flatDbMode) { setUp(flatDbMode); Assumptions.assumeTrue(flatDbMode == FlatDbMode.FULL); final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); @@ -229,7 +229,7 @@ public void getAccount_notLoadFromTrieWhenEmptyAndFlatDbFullMode(final FlatDbMod @ParameterizedTest @MethodSource("data") - public void getAccount_loadFromTrieWhenEmptyAndFlatDbPartialMode(final FlatDbMode flatDbMode) { + void getAccount_loadFromTrieWhenEmptyAndFlatDbPartialMode(final FlatDbMode flatDbMode) { setUp(flatDbMode); Assumptions.assumeTrue(flatDbMode == FlatDbMode.PARTIAL); final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); @@ -258,7 +258,7 @@ public void getAccount_loadFromTrieWhenEmptyAndFlatDbPartialMode(final FlatDbMod @ParameterizedTest @MethodSource("data") - public void shouldUsePartialDBStrategyAfterDowngradingMode(final FlatDbMode flatDbMode) { + void shouldUsePartialDBStrategyAfterDowngradingMode(final FlatDbMode flatDbMode) { setUp(flatDbMode); Assumptions.assumeTrue(flatDbMode == FlatDbMode.PARTIAL); final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); @@ -290,7 +290,7 @@ public void shouldUsePartialDBStrategyAfterDowngradingMode(final FlatDbMode flat @ParameterizedTest @MethodSource("data") - public void getStorage_loadFromTrieWhenEmptyWithPartialMode(final FlatDbMode flatDbMode) { + void getStorage_loadFromTrieWhenEmptyWithPartialMode(final FlatDbMode flatDbMode) { setUp(flatDbMode); Assumptions.assumeTrue(flatDbMode == FlatDbMode.PARTIAL); final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); @@ -339,7 +339,7 @@ public void getStorage_loadFromTrieWhenEmptyWithPartialMode(final FlatDbMode fla @ParameterizedTest @MethodSource("data") - public void getStorage_loadFromTrieWhenEmptyWithFullMode(final FlatDbMode flatDbMode) { + void getStorage_loadFromTrieWhenEmptyWithFullMode(final FlatDbMode flatDbMode) { setUp(flatDbMode); Assumptions.assumeTrue(flatDbMode == FlatDbMode.FULL); final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); @@ -359,7 +359,7 @@ public void getStorage_loadFromTrieWhenEmptyWithFullMode(final FlatDbMode flatDb @ParameterizedTest @MethodSource("data") - public void clear_reloadFlatDbStrategy(final FlatDbMode flatDbMode) { + void clear_reloadFlatDbStrategy(final FlatDbMode flatDbMode) { setUp(flatDbMode); final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); @@ -379,11 +379,11 @@ public void clear_reloadFlatDbStrategy(final FlatDbMode flatDbMode) { @ParameterizedTest @MethodSource("data") - public void reconcilesNonConflictingUpdaters(final FlatDbMode flatDbMode) { + void reconcilesNonConflictingUpdaters(final FlatDbMode flatDbMode) { setUp(flatDbMode); - final Hash accountHashA = Hash.hash(Address.fromHexString("0x1")); - final Hash accountHashB = Hash.hash(Address.fromHexString("0x2")); - final Hash accountHashD = Hash.hash(Address.fromHexString("0x4")); + final Hash accountHashA = Address.fromHexString("0x1").addressHash(); + final Hash accountHashB = Address.fromHexString("0x2").addressHash(); + final Hash accountHashD = Address.fromHexString("0x4").addressHash(); final Bytes bytesA = Bytes.fromHexString("0x12"); final Bytes bytesB = Bytes.fromHexString("0x1234"); final Bytes bytesC = Bytes.fromHexString("0x123456"); @@ -405,14 +405,14 @@ public void reconcilesNonConflictingUpdaters(final FlatDbMode flatDbMode) { @ParameterizedTest @MethodSource("data") - public void isWorldStateAvailable_defaultIsFalse(final FlatDbMode flatDbMode) { + void isWorldStateAvailable_defaultIsFalse(final FlatDbMode flatDbMode) { setUp(flatDbMode); assertThat(emptyStorage().isWorldStateAvailable(UInt256.valueOf(1), Hash.EMPTY)).isFalse(); } @ParameterizedTest @MethodSource("data") - public void isWorldStateAvailable_StateAvailableByRootHash(final FlatDbMode flatDbMode) { + void isWorldStateAvailable_StateAvailableByRootHash(final FlatDbMode flatDbMode) { setUp(flatDbMode); final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); @@ -427,7 +427,7 @@ public void isWorldStateAvailable_StateAvailableByRootHash(final FlatDbMode flat @ParameterizedTest @MethodSource("data") - public void isWorldStateAvailable_afterCallingSaveWorldstate(final FlatDbMode flatDbMode) { + void isWorldStateAvailable_afterCallingSaveWorldstate(final FlatDbMode flatDbMode) { setUp(flatDbMode); final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java index cbb125647bf..dfb247ee29b 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java @@ -43,7 +43,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -public class CachedMerkleTrieLoaderTest { +class CachedMerkleTrieLoaderTest { private CachedMerkleTrieLoader merkleTrieLoader; private final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); @@ -59,12 +59,13 @@ public class CachedMerkleTrieLoaderTest { public void setup() { trie = TrieGenerator.generateTrie( - inMemoryWorldState, accounts.stream().map(Hash::hash).collect(Collectors.toList())); + inMemoryWorldState, + accounts.stream().map(Address::addressHash).collect(Collectors.toList())); merkleTrieLoader = new CachedMerkleTrieLoader(new NoOpMetricsSystem()); } @Test - public void shouldAddAccountNodesInCacheDuringPreload() { + void shouldAddAccountNodesInCacheDuringPreload() { merkleTrieLoader.cacheAccountNodes( inMemoryWorldState, Hash.wrap(trie.getRootHash()), accounts.get(0)); @@ -79,13 +80,13 @@ public void shouldAddAccountNodesInCacheDuringPreload() { Function.identity(), Function.identity()); - final Hash hashAccountZero = Hash.hash(accounts.get(0)); + final Hash hashAccountZero = accounts.get(0).addressHash(); assertThat(cachedTrie.get(hashAccountZero)).isEqualTo(trie.get(hashAccountZero)); } @Test - public void shouldAddStorageNodesInCacheDuringPreload() { - final Hash hashAccountZero = Hash.hash(accounts.get(0)); + void shouldAddStorageNodesInCacheDuringPreload() { + final Hash hashAccountZero = accounts.get(0).addressHash(); final StateTrieAccountValue stateTrieAccountValue = StateTrieAccountValue.readFrom(RLP.input(trie.get(hashAccountZero).orElseThrow())); final StoredMerklePatriciaTrie storageTrie = @@ -123,12 +124,11 @@ public void shouldAddStorageNodesInCacheDuringPreload() { cachedSlots.add(node.getEncodedBytes()); return TrieIterator.State.CONTINUE; }); - assertThat(originalSlots).isNotEmpty(); - assertThat(originalSlots).isEqualTo(cachedSlots); + assertThat(originalSlots).isNotEmpty().isEqualTo(cachedSlots); } @Test - public void shouldFallbackWhenAccountNodesIsNotInCache() { + void shouldFallbackWhenAccountNodesIsNotInCache() { final StoredMerklePatriciaTrie cachedTrie = new StoredMerklePatriciaTrie<>( (location, hash) -> @@ -136,13 +136,13 @@ public void shouldFallbackWhenAccountNodesIsNotInCache() { trie.getRootHash(), Function.identity(), Function.identity()); - final Hash hashAccountZero = Hash.hash(accounts.get(0)); + final Hash hashAccountZero = accounts.get(0).addressHash(); assertThat(cachedTrie.get(hashAccountZero)).isEqualTo(trie.get(hashAccountZero)); } @Test - public void shouldFallbackWhenStorageNodesIsNotInCache() { - final Hash hashAccountZero = Hash.hash(accounts.get(0)); + void shouldFallbackWhenStorageNodesIsNotInCache() { + final Hash hashAccountZero = accounts.get(0).addressHash(); final StateTrieAccountValue stateTrieAccountValue = StateTrieAccountValue.readFrom(RLP.input(trie.get(hashAccountZero).orElseThrow())); final StoredMerklePatriciaTrie storageTrie = @@ -173,7 +173,6 @@ public void shouldFallbackWhenStorageNodesIsNotInCache() { cachedSlots.add(node.getEncodedBytes()); return TrieIterator.State.CONTINUE; }); - assertThat(originalSlots).isNotEmpty(); - assertThat(originalSlots).isEqualTo(cachedSlots); + assertThat(originalSlots).isNotEmpty().isEqualTo(cachedSlots); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProviderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProviderTest.java index 08f83d146bb..b6357954f53 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProviderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProviderTest.java @@ -67,7 +67,7 @@ public void getProofWhenWorldStateNotAvailable() { @Test public void getProofWhenWorldStateAvailable() { - final Hash addressHash = Hash.hash(address); + final Hash addressHash = address.addressHash(); final MerkleTrie worldStateTrie = emptyWorldStateTrie(addressHash); final MerkleTrie storageTrie = emptyStorageTrie(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java index d4b22c72622..48fe26af187 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java @@ -83,6 +83,11 @@ public void shouldRecordOpcode() { @Test public void shouldRecordDepth() { final MessageFrame frame = validMessageFrame(); + // simulate 4 calls + frame.getMessageFrameStack().add(frame); + frame.getMessageFrameStack().add(frame); + frame.getMessageFrameStack().add(frame); + frame.getMessageFrameStack().add(frame); final TraceFrame traceFrame = traceFrame(frame); assertThat(traceFrame.getDepth()).isEqualTo(DEPTH); } @@ -201,8 +206,7 @@ private MessageFrameTestFixture validMessageFrameBuilder() { .worldUpdater(worldUpdater) .gasPrice(Wei.of(25)) .blockHeader(blockHeader) - .blockchain(blockchain) - .depth(DEPTH); + .blockchain(blockchain); } private Map setupStorageForCapture(final MessageFrame frame) { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/EstimateGasOperationTracerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/EstimateGasOperationTracerTest.java index 5a6096047b4..db931d9585c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/EstimateGasOperationTracerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/EstimateGasOperationTracerTest.java @@ -49,23 +49,26 @@ public void shouldDetectChangeInDepthDuringExecution() { assertThat(operationTracer.getMaxDepth()).isZero(); - final MessageFrame firstFrame = messageFrameTestFixture.depth(0).build(); + final MessageFrame firstFrame = messageFrameTestFixture.build(); operationTracer.tracePostExecution(firstFrame, testResult); assertThat(operationTracer.getMaxDepth()).isZero(); - final MessageFrame secondFrame = messageFrameTestFixture.depth(1).build(); + final MessageFrame secondFrame = messageFrameTestFixture.parentFrame(firstFrame).build(); operationTracer.tracePostExecution(secondFrame, testResult); assertThat(operationTracer.getMaxDepth()).isEqualTo(1); + firstFrame.getMessageFrameStack().removeFirst(); - final MessageFrame thirdFrame = messageFrameTestFixture.depth(1).build(); + final MessageFrame thirdFrame = messageFrameTestFixture.parentFrame(firstFrame).build(); operationTracer.tracePostExecution(thirdFrame, testResult); assertThat(operationTracer.getMaxDepth()).isEqualTo(1); - final MessageFrame fourthFrame = messageFrameTestFixture.depth(2).build(); + final MessageFrame fourthFrame = messageFrameTestFixture.parentFrame(thirdFrame).build(); operationTracer.tracePostExecution(fourthFrame, testResult); assertThat(operationTracer.getMaxDepth()).isEqualTo(2); + firstFrame.getMessageFrameStack().removeFirst(); + firstFrame.getMessageFrameStack().removeFirst(); - final MessageFrame fifthFrame = messageFrameTestFixture.depth(0).build(); + final MessageFrame fifthFrame = messageFrameTestFixture.build(); operationTracer.tracePostExecution(fifthFrame, testResult); assertThat(operationTracer.getMaxDepth()).isEqualTo(2); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java index 5be64317e29..441dcfeb9d5 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; @@ -90,7 +89,7 @@ public static Optional> createCheckpointDownloader( .ifPresent( address -> snapContext.addAccountsToBeRepaired( - CompactEncoding.bytesToPath(Hash.hash(address)))); + CompactEncoding.bytesToPath(address.addressHash()))); } else if (fastSyncState.getPivotBlockHeader().isEmpty() && protocolContext.getBlockchain().getChainHeadBlockNumber() != BlockHeader.GENESIS_BLOCK_NUMBER) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java index ffea38d3950..3f6596cf0b8 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; @@ -85,7 +84,7 @@ public static Optional> createSnapDownloader( .ifPresent( address -> snapContext.addAccountsToBeRepaired( - CompactEncoding.bytesToPath(Hash.hash(address)))); + CompactEncoding.bytesToPath(address.addressHash()))); } else if (fastSyncState.getPivotBlockHeader().isEmpty() && protocolContext.getBlockchain().getChainHeadBlockNumber() != BlockHeader.GENESIS_BLOCK_NUMBER) { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequestTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequestTest.java index cfe5f2697fc..a281385dd5b 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequestTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequestTest.java @@ -56,7 +56,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class StorageFlatDatabaseHealingRangeRequestTest { +class StorageFlatDatabaseHealingRangeRequestTest { @Mock private SnapWorldDownloadState downloadState; @Mock private SnapSyncProcessState snapSyncState; @@ -82,8 +82,9 @@ public void setup() { proofProvider = new WorldStateProofProvider(worldStateStorage); trie = TrieGenerator.generateTrie( - worldStateStorage, accounts.stream().map(Hash::hash).collect(Collectors.toList())); - account0Hash = Hash.hash(accounts.get(0)); + worldStateStorage, + accounts.stream().map(Address::addressHash).collect(Collectors.toList())); + account0Hash = accounts.get(0).addressHash(); account0StorageRoot = trie.get(account0Hash) .map(RLP::input) @@ -93,7 +94,7 @@ public void setup() { } @Test - public void shouldReturnChildRequests() { + void shouldReturnChildRequests() { final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( @@ -150,7 +151,7 @@ public void shouldReturnChildRequests() { } @Test - public void shouldNotReturnChildRequestsWhenNoMoreSlots() { + void shouldNotReturnChildRequestsWhenNoMoreSlots() { final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( @@ -191,7 +192,7 @@ public void shouldNotReturnChildRequestsWhenNoMoreSlots() { } @Test - public void doNotPersistWhenProofIsValid() { + void doNotPersistWhenProofIsValid() { final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( @@ -248,7 +249,7 @@ public void doNotPersistWhenProofIsValid() { } @Test - public void doHealAndPersistWhenProofIsInvalid() { + void doHealAndPersistWhenProofIsInvalid() { final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( 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 a73ccd49c6c..269f4c2b7d2 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 @@ -53,7 +53,6 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.time.Instant; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -367,11 +366,9 @@ public void run() { updater.getOrCreate(sender); updater.getOrCreate(receiver); - final Deque messageFrameStack = new ArrayDeque<>(); - messageFrameStack.add( + MessageFrame initialMessageFrame = MessageFrame.builder() .type(MessageFrame.Type.MESSAGE_CALL) - .messageFrameStack(messageFrameStack) .worldUpdater(updater) .initialGas(txGas) .contract(Address.ZERO) @@ -384,13 +381,13 @@ public void run() { .apparentValue(ethValue) .code(code) .blockValues(blockHeader) - .depth(0) .completer(c -> {}) .miningBeneficiary(blockHeader.getCoinbase()) .blockHashLookup(new CachingBlockHashLookup(blockHeader, component.getBlockchain())) - .build()); + .build(); final MessageCallProcessor mcp = new MessageCallProcessor(evm, precompileContractRegistry); + final Deque messageFrameStack = initialMessageFrame.getMessageFrameStack(); stopwatch.start(); while (!messageFrameStack.isEmpty()) { final MessageFrame messageFrame = messageFrameStack.peek(); @@ -448,7 +445,7 @@ public static void dumpWorldState(final WorldState worldState, final PrintWriter account -> { out.println( " \"" + account.getAddress().map(Address::toHexString).orElse("-") + "\": {"); - if (account.getCode() != null && account.getCode().size() > 0) { + if (account.getCode() != null && !account.getCode().isEmpty()) { out.println(" \"code\": \"" + account.getCode().toHexString() + "\","); } NavigableMap storageEntries = diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java index 22a3ab80ade..33aad83a22d 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java @@ -81,6 +81,21 @@ public class StateTestSubCommand implements Runnable { description = "Force the state tests to run on a specific fork.") private String fork = null; + @Option( + names = {"--data-index"}, + description = "Limit execution to one data variable.") + private Integer dataIndex = null; + + @Option( + names = {"--gas-index"}, + description = "Limit execution to one gas variable.") + private Integer gasIndex = null; + + @Option( + names = {"--value-index"}, + description = "Limit execution to one value variable.") + private Integer valueIndex = null; + @ParentCommand private final EvmToolCommand parentCommand; @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") // picocli does it magically @@ -169,6 +184,15 @@ private void traceTestSpecs(final String test, final ListTo 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 UndoList implements List, UndoableCollection { + + 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()); + } + + @Override + public String toString() { + return "UndoEntry{" + + "index=" + + index + + ", set=" + + set + + ", value=" + + value + + ", level=" + + level + + '}'; + } + } + + static class ReadOnlyListIterator implements ListIterator { + ListIterator iterDelegate; + + public ReadOnlyListIterator(final ListIterator iterDelegate) { + this.iterDelegate = iterDelegate; + } + + @Override + public boolean hasNext() { + return iterDelegate.hasNext(); + } + + @Override + public V next() { + return iterDelegate.next(); + } + + @Override + public boolean hasPrevious() { + return iterDelegate.hasPrevious(); + } + + @Override + public V previous() { + return iterDelegate.previous(); + } + + @Override + public int nextIndex() { + return iterDelegate.nextIndex(); + } + + @Override + public int previousIndex() { + return iterDelegate.previousIndex(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException( + "UndoList does not support iterator based modification"); + } + + @Override + public void set(final V v) { + throw new UnsupportedOperationException( + "UndoList does not support iterator based modification"); + } + + @Override + public void add(final V v) { + throw new UnsupportedOperationException( + "UndoList does not support iterator based modification"); + } + } + + List delegate; + List> undoLog = new ArrayList<>(); + + /** + * Create an UndoList backed by another List instance. + * + * @param delegate The List instance to use for backing storage + */ + public UndoList(final List delegate) { + this.delegate = delegate; + } + + @Override + public void undo(final long mark) { + int pos = undoLog.size() - 1; + while (pos >= 0 && undoLog.get(pos).level > mark) { + final var entry = undoLog.get(pos); + undoLog.remove(pos); + if (entry.value() == null) { + delegate.remove(entry.index); + } else if (entry.set) { + delegate.set(entry.index, entry.value()); + } else { + delegate.add(entry.index, entry.value); + } + pos--; + } + } + + @Override + public boolean add(final V v) { + undoLog.add(new UndoEntry<>(delegate.size(), false, null)); + return delegate.add(v); + } + + @SuppressWarnings({"unchecked", "SuspiciousMethodCalls"}) + @Override + public boolean remove(final Object v) { + int index = delegate.indexOf(v); + if (index >= 0) { + delegate.remove(index); + undoLog.add(new UndoEntry<>(index, false, (V) v)); + return true; + } else { + return false; + } + } + + @Override + public boolean containsAll(final @Nonnull Collection c) { + return new HashSet<>(delegate).containsAll(c); + } + + @Override + public boolean addAll(final Collection c) { + for (V v : c) { + add(v); + } + return !c.isEmpty(); + } + + @Override + public boolean addAll(final int index, final Collection c) { + int pos = index; + for (V v : c) { + add(pos++, v); + } + return !c.isEmpty(); + } + + @Override + public boolean removeAll(final @Nonnull Collection c) { + HashSet hs = new HashSet<>(c); + ListIterator iter = delegate.listIterator(); + boolean updated = false; + while (iter.hasNext()) { + V v = iter.next(); + if (hs.contains(v)) { + undoLog.add(new UndoEntry<>(iter.previousIndex(), false, v)); + iter.remove(); + updated = true; + } + } + return updated; + } + + @Override + public boolean retainAll(final @Nonnull Collection c) { + HashSet hs = new HashSet<>(c); + ListIterator iter = delegate.listIterator(); + boolean updated = false; + while (iter.hasNext()) { + V v = iter.next(); + if (!hs.contains(v)) { + undoLog.add(new UndoEntry<>(iter.previousIndex(), false, v)); + iter.remove(); + updated = true; + } + } + return updated; + } + + @Override + public void clear() { + // store in log in reverse so when we restore them we are appending + for (int i = delegate.size() - 1; i >= 0; i--) { + remove(i); + } + } + + @Override + public V set(final int index, final V element) { + V oldValue = delegate.set(index, element); + undoLog.add(new UndoEntry<>(index, true, oldValue)); + return oldValue; + } + + @Override + public void add(final int index, final V element) { + delegate.add(index, element); + undoLog.add(new UndoEntry<>(index, false, null)); + } + + @Override + public V remove(final int index) { + undoLog.add(new UndoEntry<>(index, false, delegate.get(index))); + return delegate.remove(index); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(final Object o) { + return delegate.contains(o); + } + + @Override + public Iterator iterator() { + return delegate.iterator(); + } + + @Override + public Object[] toArray() { + return delegate.toArray(); + } + + @Override + public T[] toArray(final @Nonnull T[] a) { + return delegate.toArray(a); + } + + @Override + public V get(final int index) { + return delegate.get(index); + } + + @Override + public int indexOf(final Object o) { + return delegate.indexOf(o); + } + + @Override + public int lastIndexOf(final Object o) { + return delegate.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return new ReadOnlyListIterator<>(delegate.listIterator()); + } + + @Override + public ListIterator listIterator(final int index) { + return new ReadOnlyListIterator<>(delegate.listIterator(index)); + } + + @Override + public List subList(final int fromIndex, final int toIndex) { + return delegate.subList(fromIndex, toIndex); + } + + @Override + public String toString() { + return "UndoList{" + "delegate=" + delegate + ", undoLog=" + undoLog + '}'; + } + + @Override + public boolean equals(final Object o) { + return o instanceof UndoList && delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode() ^ 0xde1e647e; + } +} 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 new file mode 100644 index 00000000000..1a76985b5f3 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoMap.java @@ -0,0 +1,156 @@ +/* + * 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.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * 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 UndoMap implements Map, UndoableCollection { + + record UndoEntry(K key, V value, long level) { + UndoEntry(final K key, final V value) { + this(key, value, UndoableCollection.incrementMarkStatic()); + } + } + + Map delegate; + List> undoLog; + + /** + * Create an UndoMap backed by another Map instance. + * + * @param delegate The Map instance to use for backing storage + */ + public UndoMap(final Map delegate) { + this.delegate = delegate; + undoLog = new ArrayList<>(); + } + + @Override + public void undo(final long mark) { + int pos = undoLog.size() - 1; + while (pos >= 0 && undoLog.get(pos).level > mark) { + final var entry = undoLog.get(pos); + undoLog.remove(pos); + if (entry.value() == null) { + delegate.remove(entry.key()); + } else { + delegate.put(entry.key(), entry.value()); + } + pos--; + } + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(final Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(final Object value) { + return delegate.containsValue(value); + } + + @Override + public V get(final Object key) { + return delegate.get(key); + } + + @Override + public V put(final @Nonnull K key, final @Nonnull V value) { + Objects.requireNonNull(value); + final V oldValue = delegate.put(key, value); + if (!value.equals(oldValue)) { + undoLog.add(new UndoEntry<>(key, oldValue)); + } + return oldValue; + } + + @SuppressWarnings("unchecked") + @Override + public V remove(final Object key) { + final V oldValue = delegate.remove(key); + if (oldValue != null) { + undoLog.add(new UndoEntry<>((K) key, oldValue)); + } + return oldValue; + } + + @Override + public void putAll(@Nonnull final Map m) { + m.forEach(this::put); + } + + @Override + public void clear() { + delegate.forEach((k, v) -> undoLog.add(new UndoEntry<>(k, v))); + delegate.clear(); + } + + @Nonnull + @Override + public Set keySet() { + return Collections.unmodifiableSet(delegate.keySet()); + } + + @Nonnull + @Override + public Collection values() { + return Collections.unmodifiableCollection(delegate.values()); + } + + @Nonnull + @Override + public Set> entrySet() { + return Collections.unmodifiableSet(delegate.entrySet()); + } + + @Override + public boolean equals(final Object o) { + return o instanceof UndoMap && delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode() ^ 0xde1e647e; + } +} 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 new file mode 100644 index 00000000000..1ba509d3094 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoSet.java @@ -0,0 +1,192 @@ +/* + * 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.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * A set that supports rolling back the set 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 UndoSet implements Set, UndoableCollection { + + record UndoEntry(V value, boolean add, long level) { + static UndoSet.UndoEntry add(final V value) { + return new UndoEntry<>(value, true, UndoableCollection.incrementMarkStatic()); + } + + static UndoSet.UndoEntry remove(final V value) { + return new UndoEntry<>(value, false, UndoableCollection.incrementMarkStatic()); + } + } + + Set delegate; + List> undoLog; + + /** + * Create an UndoSet backed by another Set instance. + * + * @param delegate The Set instance to use for backing storage + * @param The type of the collection. + * @return an unduable set + */ + public static UndoSet of(final Set delegate) { + return new UndoSet<>(delegate); + } + + UndoSet(final Set delegate) { + this.delegate = delegate; + undoLog = new ArrayList<>(); + } + + @Override + public void undo(final long mark) { + int pos = undoLog.size() - 1; + while (pos >= 0 && undoLog.get(pos).level > mark) { + final var entry = undoLog.get(pos); + if (entry.add) { + delegate.remove(entry.value()); + } else { + delegate.add(entry.value()); + } + undoLog.remove(pos); + pos--; + } + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(final Object key) { + return delegate.contains(key); + } + + @Override + public boolean add(final V key) { + final boolean added = delegate.add(key); + if (added) { + undoLog.add(UndoEntry.add(key)); + } + return added; + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(final Object key) { + final boolean removed = delegate.remove(key); + if (removed) { + undoLog.add(UndoEntry.remove((V) key)); + } + return removed; + } + + @Override + public boolean addAll(@Nonnull final Collection m) { + boolean added = false; + for (V v : m) { + // don't use short circuit, we need to evaluate all entries + // we also need undo entries for each added entry + added &= add(v); + } + return added; + } + + @Override + public boolean removeAll(@Nonnull final Collection c) { + boolean removed = false; + for (Object v : c) { + // don't use short circuit, we need to evaluate all entries + // we also need undo entries for each removed entry + removed &= remove(v); + } + return removed; + } + + @Override + public boolean retainAll(@Nonnull final Collection c) { + boolean removed = false; + HashSet hashed = new HashSet<>(c); + Iterator iter = delegate.iterator(); + while (iter.hasNext()) { + V v = iter.next(); + if (!hashed.contains(v)) { + removed = true; + undoLog.add(UndoEntry.remove(v)); + iter.remove(); + } + } + return removed; + } + + @Override + public void clear() { + delegate.forEach(v -> undoLog.add(UndoEntry.remove(v))); + delegate.clear(); + } + + @Nonnull + @Override + public Iterator iterator() { + return new ReadOnlyIterator<>(delegate.iterator()); + } + + @Nonnull + @Override + public Object[] toArray() { + return delegate.toArray(); + } + + @Nonnull + @Override + public T[] toArray(@Nonnull final T[] a) { + return delegate.toArray(a); + } + + @Override + public boolean containsAll(@Nonnull final Collection c) { + return delegate.containsAll(c); + } + + @Override + public boolean equals(final Object o) { + return o instanceof UndoSet && delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode() ^ 0xde1e647e; + } +} 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 new file mode 100644 index 00000000000..26af3225f47 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoTable.java @@ -0,0 +1,209 @@ +/* + * 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.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.annotation.CheckForNull; + +import com.google.common.collect.Table; +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +/** + * A Table that supports rolling back the Table 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 UndoTable implements Table, UndoableCollection { + + 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()); + } + } + + Table delegate; + List> undoLog; + + /** + * Create an UndoTable backed by another Table instance. + * + * @param table The Table instance to use for backing storage + * @param The row type + * @param The column type + * @param The value type + * @return a new undo table backed by the provided table. + */ + public static UndoTable of(final Table table) { + return new UndoTable<>(table); + } + + /** + * Protected constructor for UndoTable + * + * @param delegate the table backing the undotable. + */ + protected UndoTable(final Table delegate) { + this.delegate = delegate; + undoLog = new ArrayList<>(); + } + + @Override + public void undo(final long mark) { + int pos = undoLog.size() - 1; + while (pos >= 0 && undoLog.get(pos).level > mark) { + final var entry = undoLog.get(pos); + undoLog.remove(pos); + if (entry.value() == null) { + delegate.remove(entry.row(), entry.column()); + } else { + delegate.put(entry.row(), entry.column(), entry.value()); + } + pos--; + } + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(final Object rowKey, final Object columnKey) { + return delegate.contains(rowKey, columnKey); + } + + @Override + public boolean containsRow(final Object rowKey) { + return delegate.containsRow(rowKey); + } + + @Override + public boolean containsColumn(final Object columnKey) { + return delegate.containsColumn(columnKey); + } + + @Override + public boolean containsValue(final Object value) { + return delegate.containsValue(value); + } + + @Override + @CheckForNull + public V get(final Object rowKey, final Object columnKey) { + return delegate.get(rowKey, columnKey); + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + @CanIgnoreReturnValue + @CheckForNull + public V put(final R rowKey, final C columnKey, final V value) { + V oldV = delegate.put(rowKey, columnKey, value); + undoLog.add(new UndoEntry<>(rowKey, columnKey, oldV)); + return oldV; + } + + @Override + public void putAll(final Table table) { + for (var cell : table.cellSet()) { + V newV = cell.getValue(); + V oldV = delegate.put(cell.getRowKey(), cell.getColumnKey(), newV); + if (!Objects.equals(oldV, newV)) { + undoLog.add(new UndoEntry<>(cell.getRowKey(), cell.getColumnKey(), oldV)); + } + } + } + + @SuppressWarnings("unchecked") + @Override + @CanIgnoreReturnValue + @CheckForNull + public V remove(final Object rowKey, final Object columnKey) { + V oldV = delegate.remove(rowKey, columnKey); + undoLog.add(new UndoEntry<>((R) rowKey, (C) columnKey, oldV)); + return oldV; + } + + @Override + public Map row(final R rowKey) { + return Collections.unmodifiableMap(delegate.row(rowKey)); + } + + @Override + public Map column(final C columnKey) { + return Collections.unmodifiableMap(delegate.column(columnKey)); + } + + @Override + public Set> cellSet() { + return Collections.unmodifiableSet(delegate.cellSet()); + } + + @Override + public Set rowKeySet() { + return Collections.unmodifiableSet(delegate.rowKeySet()); + } + + @Override + public Set columnKeySet() { + return Collections.unmodifiableSet(delegate.columnKeySet()); + } + + @Override + public Collection values() { + return Collections.unmodifiableCollection(delegate.values()); + } + + @Override + public Map> rowMap() { + return Collections.unmodifiableMap(delegate.rowMap()); + } + + @Override + public Map> columnMap() { + return Collections.unmodifiableMap(delegate.columnMap()); + } + + @Override + public boolean equals(final Object o) { + return o instanceof UndoTable && delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode() ^ 0xde1e647e; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoableCollection.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoableCollection.java new file mode 100644 index 00000000000..25bfaa8ee50 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoableCollection.java @@ -0,0 +1,86 @@ +/* + * 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.Iterator; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A manually ticked clock to determine when in execution an item was added to an undo collection. + * 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 { + /** The global mark clock for registering marks in undoable collections. */ + AtomicLong markState = new AtomicLong(); + + /** + * Retrieves an identifier that represents the current state of the collection. + * + *

This marker is tracked globally so getting a mark in one Undoable collection will provide a + * mark that can be used in other UndoableCollections + * + * @return a long representing the current state. + */ + default long mark() { + return markState.get(); + } + + /** + * Advances the mark to a state greater than when it was before. + * + * @return a new mark that is guaranteed to be after the prior mark's value. + */ + static long incrementMarkStatic() { + return markState.incrementAndGet(); + } + + /** + * 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. + * + * @param mark The mark to which the undo should proceed to, but not prior to + */ + void undo(long mark); + + /** + * Since we are relying on delegation, iterators should not be able to modify the collection. + * + * @param the type of the collection + */ + final class ReadOnlyIterator implements Iterator { + Iterator delegate; + + /** + * Create a read-only delegated iterator + * + * @param delegate the iterator to pass read only calls to. + */ + public ReadOnlyIterator(final Iterator delegate) { + this.delegate = delegate; + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public V next() { + return delegate.next(); + } + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java index 875b3da49db..f25d5259b96 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java @@ -36,7 +36,6 @@ import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; import java.util.List; @@ -345,11 +344,9 @@ public Bytes execute( public Bytes execute() { final MessageCallProcessor mcp = thisMessageCallProcessor(); final ContractCreationProcessor ccp = thisContractCreationProcessor(); - final Deque messageFrameStack = new ArrayDeque<>(); final MessageFrame initialMessageFrame = MessageFrame.builder() .type(MessageFrame.Type.MESSAGE_CALL) - .messageFrameStack(messageFrameStack) .worldUpdater(worldUpdater.updater()) .initialGas(gas) .contract(Address.ZERO) @@ -362,15 +359,14 @@ public Bytes execute() { .apparentValue(ethValue) .code(code) .blockValues(blockValues) - .depth(0) .completer(c -> {}) .miningBeneficiary(Address.ZERO) .blockHashLookup(h -> null) .accessListWarmAddresses(accessListWarmAddresses) .accessListWarmStorage(accessListWarmStorage) .build(); - messageFrameStack.add(initialMessageFrame); + final Deque messageFrameStack = initialMessageFrame.getMessageFrameStack(); while (!messageFrameStack.isEmpty()) { final MessageFrame messageFrame = messageFrameStack.peek(); if (messageFrame.getType() == MessageFrame.Type.CONTRACT_CREATION) { 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 1d1c1c81d3d..75c86070950 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 @@ -41,7 +41,7 @@ public class SimpleAccount implements EvmAccount, MutableAccount { private Address address; private final Supplier addressHash = - Suppliers.memoize(() -> address == null ? Hash.ZERO : Hash.hash(address)); + Suppliers.memoize(() -> address == null ? Hash.ZERO : address.addressHash()); private long nonce; private Wei balance; private Bytes code; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/BlockValues.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/BlockValues.java index 74db00274a0..aade1f98437 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/BlockValues.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/BlockValues.java @@ -22,7 +22,7 @@ import org.apache.tuweni.bytes.Bytes32; /** - * Block Header Values used by various EVM Opcodes. This is not a complete BlocHeader, just the + * Block Header Values used by various EVM Opcodes. This is not a complete BlockHeader, just the * values that are returned or accessed by various operations. */ public interface BlockValues { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index 1c7adf20850..15ea57dccc5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -17,6 +17,8 @@ import static com.google.common.base.Preconditions.checkState; import static java.util.Collections.emptySet; +import org.hyperledger.besu.collections.undo.UndoSet; +import org.hyperledger.besu.collections.undo.UndoTable; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.VersionedHash; @@ -32,6 +34,7 @@ import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; @@ -42,7 +45,9 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; +import com.google.common.base.Suppliers; import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; @@ -56,7 +61,7 @@ * A container object for all the states associated with a message. * *

A message corresponds to an interaction between two accounts. A Transaction spawns at least - * one message when its processed. Messages can also spawn messages depending on the code executed + * one message when it's processed. Messages can also spawn messages depending on the code executed * within a message. * *

Note that there is no specific Message object in the code base. Instead, message executions @@ -200,59 +205,48 @@ public enum Type { // Metadata fields. private final Type type; - private State state; + private State state = State.NOT_STARTED; // Machine state fields. private long gasRemaining; - private final Function blockHashLookup; - private final int maxStackSize; private int pc; - private int section; - private final Memory memory; + private int section = 0; + private final Memory memory = new Memory(); private final OperandStack stack; - private final ReturnStack returnStack; - private Bytes output; - private Bytes returnData; + private final Supplier returnStack; + private Bytes output = Bytes.EMPTY; + private Bytes returnData = Bytes.EMPTY; private final boolean isStatic; - // Transaction substate fields. - private final List logs; - private long gasRefund; - private final Set

selfDestructs; - private final Set
creates; - private final Map refunds; - private final Set
warmedUpAddresses; - private final Multimap warmedUpStorage; + // Transaction state fields. + private final List logs = new ArrayList<>(); + private long gasRefund = 0L; + private final Map refunds = new HashMap<>(); // Execution Environment fields. private final Address recipient; - private final Address originator; private final Address contract; - private final Wei gasPrice; private final Bytes inputData; private final Address sender; private final Wei value; private final Wei apparentValue; private final Code code; - private final BlockValues blockValues; - private final int depth; - private final MessageFrame parentMessageFrame; - private final Deque messageFrameStack; - private final Address miningBeneficiary; + private Optional revertReason; private final Map contextVariables; - private final Optional> versionedHashes; - - private final Table transientStorage = HashBasedTable.create(); - // Miscellaneous fields. private Optional exceptionalHaltReason = Optional.empty(); private Operation currentOperation; private final Consumer completer; private Optional maybeUpdatedMemory = Optional.empty(); private Optional maybeUpdatedStorage = Optional.empty(); + private final TxValues txValues; + + /** The mark of the undoable collections at the creation of this message frame */ + private final long undoMark; + /** * Builder builder. * @@ -264,87 +258,47 @@ public static Builder builder() { private MessageFrame( final Type type, - final Deque messageFrameStack, final WorldUpdater worldUpdater, final long initialGas, final Address recipient, - final Address originator, final Address contract, - final Wei gasPrice, final Bytes inputData, final Address sender, final Wei value, final Wei apparentValue, final Code code, - final BlockValues blockValues, - final int depth, final boolean isStatic, final Consumer completer, - final Address miningBeneficiary, - final Function blockHashLookup, final Map contextVariables, final Optional revertReason, - final int maxStackSize, - final Set
accessListWarmAddresses, - final Multimap accessListWarmStorage, - final Optional> versionedHashes) { + final TxValues txValues) { + + this.txValues = txValues; this.type = type; - this.messageFrameStack = messageFrameStack; - this.parentMessageFrame = messageFrameStack.peek(); this.worldUpdater = worldUpdater; this.gasRemaining = initialGas; - this.blockHashLookup = blockHashLookup; - this.maxStackSize = maxStackSize; - this.pc = 0; - this.section = 0; - this.memory = new Memory(); - this.stack = new OperandStack(maxStackSize); - this.returnStack = new ReturnStack(); - returnStack.push(new ReturnStack.ReturnStackItem(0, 0, 0)); - pc = code.isValid() ? code.getCodeSection(0).getEntryPoint() : 0; - this.output = Bytes.EMPTY; - this.returnData = Bytes.EMPTY; - this.logs = new ArrayList<>(); - this.gasRefund = 0L; - this.selfDestructs = new HashSet<>(); - this.creates = new HashSet<>(); - this.refunds = new HashMap<>(); + this.stack = new OperandStack(txValues.maxStackSize()); + this.returnStack = + Suppliers.memoize( + () -> { + var rStack = new ReturnStack(); + rStack.push(new ReturnStack.ReturnStackItem(0, 0, 0)); + return rStack; + }); + this.pc = code.isValid() ? code.getCodeSection(0).getEntryPoint() : 0; this.recipient = recipient; - this.originator = originator; this.contract = contract; - this.gasPrice = gasPrice; this.inputData = inputData; this.sender = sender; this.value = value; this.apparentValue = apparentValue; this.code = code; - this.blockValues = blockValues; - this.depth = depth; - this.state = State.NOT_STARTED; this.isStatic = isStatic; this.completer = completer; - this.miningBeneficiary = miningBeneficiary; this.contextVariables = contextVariables; this.revertReason = revertReason; - this.warmedUpAddresses = new HashSet<>(accessListWarmAddresses); - this.warmedUpAddresses.add(sender); - this.warmedUpAddresses.add(contract); - this.warmedUpStorage = HashMultimap.create(accessListWarmStorage); - this.versionedHashes = versionedHashes; - - // the warmed up addresses will always be a superset of the address keys in the warmed up - // storage, so we can do both warm-ups in one pass - accessListWarmAddresses.forEach( - address -> - Optional.ofNullable(worldUpdater.get(address)) - .ifPresent( - account -> - warmedUpStorage - .get(address) - .forEach( - storageKeyBytes -> - account.getStorageValue(UInt256.fromBytes(storageKeyBytes))))); + this.undoMark = txValues.transientStorage().mark(); } /** @@ -393,13 +347,14 @@ public ExceptionalHaltReason callFunction(final int calledSection) { CodeSection info = code.getCodeSection(calledSection); if (info == null) { return ExceptionalHaltReason.CODE_SECTION_MISSING; - } else if (stack.size() + info.getMaxStackHeight() > maxStackSize) { + } else if (stack.size() + info.getMaxStackHeight() > txValues.maxStackSize()) { return ExceptionalHaltReason.TOO_MANY_STACK_ITEMS; } else if (stack.size() < info.getInputs()) { return ExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION; } else { - returnStack.push( - new ReturnStack.ReturnStackItem(section, pc + 2, stack.size() - info.getInputs())); + returnStack + .get() + .push(new ReturnStack.ReturnStackItem(section, pc + 2, stack.size() - info.getInputs())); pc = info.getEntryPoint() - 1; // will be +1ed at end of operations loop this.section = calledSection; return null; @@ -407,10 +362,10 @@ public ExceptionalHaltReason callFunction(final int calledSection) { } /** - * Jump function exceptional halt reason. + * Execute the mechanics of the JUMPF operation. * * @param section the section - * @return the exceptional halt reason + * @return the exceptional halt reason, if the jump failed */ public ExceptionalHaltReason jumpFunction(final int section) { CodeSection info = code.getCodeSection(section); @@ -432,10 +387,11 @@ public ExceptionalHaltReason jumpFunction(final int section) { */ public ExceptionalHaltReason returnFunction() { CodeSection thisInfo = code.getCodeSection(this.section); - var returnInfo = returnStack.pop(); + var rStack = returnStack.get(); + var returnInfo = rStack.pop(); if ((returnInfo.getStackHeight() + thisInfo.getOutputs()) != stack.size()) { return ExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS; - } else if (returnStack.isEmpty()) { + } else if (rStack.isEmpty()) { setState(MessageFrame.State.CODE_SUCCESS); setOutputData(Bytes.EMPTY); return null; @@ -599,7 +555,7 @@ public int stackSize() { * @return The current return stack size */ public int returnStackSize() { - return returnStack.size(); + return returnStack.get().size(); } /** @@ -608,7 +564,7 @@ public int returnStackSize() { * @return The top item of the return stack, or null if the stack is empty */ public ReturnStack.ReturnStackItem peekReturnStack() { - return returnStack.peek(); + return returnStack.get().peek(); } /** @@ -617,7 +573,7 @@ public ReturnStack.ReturnStackItem peekReturnStack() { * @param returnStackItem item to be pushed */ public void pushReturnStackItem(final ReturnStack.ReturnStackItem returnStackItem) { - returnStack.push(returnStackItem); + returnStack.get().push(returnStackItem); } /** @@ -948,7 +904,7 @@ public long getGasRefund() { * @param address The recipient to self-destruct */ public void addSelfDestruct(final Address address) { - selfDestructs.add(address); + txValues.selfDestructs().add(address); } /** @@ -957,12 +913,7 @@ public void addSelfDestruct(final Address address) { * @param addresses The addresses to self-destruct */ public void addSelfDestructs(final Set
addresses) { - selfDestructs.addAll(addresses); - } - - /** Removes all entries in the self-destruct set. */ - public void clearSelfDestructs() { - selfDestructs.clear(); + txValues.selfDestructs().addAll(addresses); } /** @@ -971,7 +922,7 @@ public void clearSelfDestructs() { * @return the self-destruct set */ public Set
getSelfDestructs() { - return selfDestructs; + return txValues.selfDestructs(); } /** @@ -980,7 +931,7 @@ public Set
getSelfDestructs() { * @param address The recipient to create */ public void addCreate(final Address address) { - creates.add(address); + txValues.creates().add(address); } /** * Add addresses to the create set if they are not already present. @@ -988,12 +939,7 @@ public void addCreate(final Address address) { * @param addresses The addresses to create */ public void addCreates(final Set
addresses) { - creates.addAll(addresses); - } - - /** Removes all entries in the create set. */ - public void clearCreates() { - creates.clear(); + txValues.creates().addAll(addresses); } /** @@ -1002,7 +948,7 @@ public void clearCreates() { * @return the create set */ public Set
getCreates() { - return creates; + return txValues.creates(); } /** @@ -1015,8 +961,7 @@ public Set
getCreates() { * transaction. */ public boolean wasCreatedInTransaction(final Address address) { - return creates.contains((address)) - || (parentMessageFrame != null && parentMessageFrame.wasCreatedInTransaction(address)); + return txValues.creates().contains((address)); } /** @@ -1045,22 +990,7 @@ public Map getRefunds() { * @return true if the address was already warmed up */ public boolean warmUpAddress(final Address address) { - if (warmedUpAddresses.add(address)) { - return parentMessageFrame != null && parentMessageFrame.isWarm(address); - } else { - return true; - } - } - - private boolean isWarm(final Address address) { - MessageFrame frame = this; - while (frame != null) { - if (frame.warmedUpAddresses.contains(address)) { - return true; - } - frame = frame.parentMessageFrame; - } - return false; + return !txValues.warmedUpAddresses().add(address); } /** @@ -1071,36 +1001,7 @@ private boolean isWarm(final Address address) { * @return true if the storage slot was already warmed up */ public boolean warmUpStorage(final Address address, final Bytes32 slot) { - if (warmedUpStorage.put(address, slot)) { - return parentMessageFrame != null && parentMessageFrame.isWarm(address, slot); - } else { - return true; - } - } - - private boolean isWarm(final Address address, final Bytes32 slot) { - MessageFrame frame = this; - while (frame != null) { - if (frame.warmedUpStorage.containsEntry(address, slot)) { - return true; - } - frame = frame.parentMessageFrame; - } - return false; - } - - /** - * Merge warmed up fields. - * - * @param childFrame the child frame - */ - public void mergeWarmedUpFields(final MessageFrame childFrame) { - if (childFrame == this) { - return; - } - - warmedUpAddresses.addAll(childFrame.warmedUpAddresses); - warmedUpStorage.putAll(childFrame.warmedUpStorage); + return txValues.warmedUpStorage().put(address, slot, Boolean.TRUE) != null; } /** @@ -1167,21 +1068,29 @@ public Address getRecipientAddress() { } /** - * Returns the message stack depth. + * Returns the message stack size. * - * @return the message stack depth + * @return the message stack size */ - public int getMessageStackDepth() { - return depth; + public int getMessageStackSize() { + return txValues.messageFrameStack().size(); } + /** + * Returns the Call Depth, where the rootmost call is depth 0 + * + * @return the call depth + */ + public int getDepth() { + return getMessageStackSize() - 1; + } /** * Returns the recipient that originated the message. * * @return the recipient that originated the message */ public Address getOriginatorAddress() { - return originator; + return txValues.originator(); } /** @@ -1199,7 +1108,7 @@ public Address getContractAddress() { * @return the current gas price */ public Wei getGasPrice() { - return gasPrice; + return txValues.gasPrice(); } /** @@ -1235,7 +1144,7 @@ public Wei getApparentValue() { * @return the current block header */ public BlockValues getBlockValues() { - return blockValues; + return txValues.blockValues(); } /** Performs updates based on the message frame's execution. */ @@ -1249,7 +1158,7 @@ public void notifyCompletion() { * @return the current message frame stack */ public Deque getMessageFrameStack() { - return messageFrameStack; + return txValues.messageFrameStack(); } /** @@ -1277,7 +1186,7 @@ public Optional getExceptionalHaltReason() { * @return the current mining beneficiary */ public Address getMiningBeneficiary() { - return miningBeneficiary; + return txValues.miningBeneficiary(); } /** @@ -1286,7 +1195,7 @@ public Address getMiningBeneficiary() { * @return the block hash lookup */ public Function getBlockHashLookup() { - return blockHashLookup; + return txValues.blockHashLookup(); } /** @@ -1304,7 +1213,7 @@ public Operation getCurrentOperation() { * @return the max stack size */ public int getMaxStackSize() { - return maxStackSize; + return txValues.maxStackSize(); } /** @@ -1356,8 +1265,8 @@ public void setCurrentOperation(final Operation currentOperation) { * * @return the warmed up storage */ - public Multimap getWarmedUpStorage() { - return warmedUpStorage; + public Table getWarmedUpStorage() { + return txValues.warmedUpStorage(); } /** @@ -1386,21 +1295,8 @@ public Optional getMaybeUpdatedStorage() { * @return the data value read */ public Bytes32 getTransientStorageValue(final Address accountAddress, final Bytes32 slot) { - Bytes32 data = transientStorage.get(accountAddress, slot); - - if (data != null) { - return data; - } - - if (parentMessageFrame != null) { - data = parentMessageFrame.getTransientStorageValue(accountAddress, slot); - } - if (data == null) { - data = Bytes32.ZERO; - } - transientStorage.put(accountAddress, slot, data); - - return data; + Bytes32 v = txValues.transientStorage().get(accountAddress, slot); + return v == null ? Bytes32.ZERO : v; } /** @@ -1412,14 +1308,12 @@ public Bytes32 getTransientStorageValue(final Address accountAddress, final Byte */ public void setTransientStorageValue( final Address accountAddress, final Bytes32 slot, final Bytes32 value) { - transientStorage.put(accountAddress, slot, value); + txValues.transientStorage().put(accountAddress, slot, value); } - /** Writes the transient storage to the parent frame, if one exists */ - public void commitTransientStorage() { - if (parentMessageFrame != null) { - parentMessageFrame.transientStorage.putAll(transientStorage); - } + /** Undo all the changes done by this message frame, such as when a revert is called for. */ + public void rollback() { + txValues.undoChanges(undoMark); } /** @@ -1428,7 +1322,7 @@ public void commitTransientStorage() { * @return optional list of hashes */ public Optional> getVersionedHashes() { - return versionedHashes; + return txValues.versionedHashes(); } /** Reset. */ @@ -1440,8 +1334,8 @@ public void reset() { /** The MessageFrame Builder. */ public static class Builder { + private MessageFrame parentMessageFrame; private Type type; - private Deque messageFrameStack; private WorldUpdater worldUpdater; private Long initialGas; private Address address; @@ -1454,7 +1348,6 @@ public static class Builder { private Wei apparentValue; private Code code; private BlockValues blockValues; - private int depth = -1; private int maxStackSize = DEFAULT_MAX_STACK_SIZE; private boolean isStatic = false; private Consumer completer; @@ -1468,24 +1361,25 @@ public static class Builder { private Optional> versionedHashes = Optional.empty(); /** - * Sets Type. + * The "parent" message frame. When present some fields will be populated from the parent and + * ignored if passed in via builder * - * @param type the type + * @param parentMessageFrame the parent message frame * @return the builder */ - public Builder type(final Type type) { - this.type = type; + public Builder parentMessageFrame(final MessageFrame parentMessageFrame) { + this.parentMessageFrame = parentMessageFrame; return this; } /** - * Sets Message frame stack. + * Sets Type. * - * @param messageFrameStack the message frame stack + * @param type the type * @return the builder */ - public Builder messageFrameStack(final Deque messageFrameStack) { - this.messageFrameStack = messageFrameStack; + public Builder type(final Type type) { + this.type = type; return this; } @@ -1621,17 +1515,6 @@ public Builder blockValues(final BlockValues blockValues) { return this; } - /** - * Sets Depth. - * - * @param depth the depth - * @return the builder - */ - public Builder depth(final int depth) { - this.depth = depth; - return this; - } - /** * Sets Is static. * @@ -1743,25 +1626,24 @@ public Builder versionedHashes(final Optional> versionedHash } private void validate() { + if (parentMessageFrame == null) { + checkState(worldUpdater != null, "Missing message frame world updater"); + checkState(originator != null, "Missing message frame originator"); + checkState(gasPrice != null, "Missing message frame getGasRemaining price"); + checkState(blockValues != null, "Missing message frame block header"); + checkState(miningBeneficiary != null, "Missing mining beneficiary"); + checkState(blockHashLookup != null, "Missing block hash lookup"); + } checkState(type != null, "Missing message frame type"); - checkState(messageFrameStack != null, "Missing message frame message frame stack"); - checkState(worldUpdater != null, "Missing message frame world updater"); checkState(initialGas != null, "Missing message frame initial getGasRemaining"); checkState(address != null, "Missing message frame recipient"); - checkState(originator != null, "Missing message frame originator"); checkState(contract != null, "Missing message frame contract"); - checkState(gasPrice != null, "Missing message frame getGasRemaining price"); checkState(inputData != null, "Missing message frame input data"); checkState(sender != null, "Missing message frame sender"); checkState(value != null, "Missing message frame value"); checkState(apparentValue != null, "Missing message frame apparent value"); checkState(code != null, "Missing message frame code"); - checkState(blockValues != null, "Missing message frame block header"); - checkState(depth > -1, "Missing message frame depth"); checkState(completer != null, "Missing message frame completer"); - checkState(miningBeneficiary != null, "Missing mining beneficiary"); - checkState(blockHashLookup != null, "Missing block hash lookup"); - checkState(versionedHashes != null, "Missing optional versioned hashes"); } /** @@ -1772,32 +1654,60 @@ private void validate() { public MessageFrame build() { validate(); - return new MessageFrame( - type, - messageFrameStack, - worldUpdater, - initialGas, - address, - originator, - contract, - gasPrice, - inputData, - sender, - value, - apparentValue, - code, - blockValues, - depth, - isStatic, - completer, - miningBeneficiary, - blockHashLookup, - contextVariables == null ? Map.of() : contextVariables, - reason, - maxStackSize, - accessListWarmAddresses, - accessListWarmStorage, - versionedHashes); + WorldUpdater updater; + boolean newStatic; + TxValues newTxValues; + if (parentMessageFrame == null) { + newTxValues = + new TxValues( + blockHashLookup, + maxStackSize, + UndoSet.of(new HashSet<>()), + UndoTable.of(HashBasedTable.create()), + originator, + gasPrice, + blockValues, + new ArrayDeque<>(), + miningBeneficiary, + versionedHashes, + UndoTable.of(HashBasedTable.create()), + UndoSet.of(new HashSet<>()), + UndoSet.of(new HashSet<>())); + updater = worldUpdater; + newStatic = isStatic; + } else { + newTxValues = parentMessageFrame.txValues; + updater = parentMessageFrame.worldUpdater.updater(); + newStatic = isStatic || parentMessageFrame.isStatic; + } + + MessageFrame messageFrame = + new MessageFrame( + type, + updater, + initialGas, + address, + contract, + inputData, + sender, + value, + apparentValue, + code, + newStatic, + completer, + contextVariables == null ? Map.of() : contextVariables, + reason, + newTxValues); + newTxValues.messageFrameStack().addFirst(messageFrame); + messageFrame.warmUpAddress(sender); + messageFrame.warmUpAddress(contract); + for (Address a : accessListWarmAddresses) { + messageFrame.warmUpAddress(a); + } + for (var e : accessListWarmStorage.entries()) { + messageFrame.warmUpStorage(e.getKey(), e.getValue()); + } + return messageFrame; } } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/TxValues.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/TxValues.java new file mode 100644 index 00000000000..65f7f47b569 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/TxValues.java @@ -0,0 +1,63 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * 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.frame; + +import org.hyperledger.besu.collections.undo.UndoSet; +import org.hyperledger.besu.collections.undo.UndoTable; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.VersionedHash; +import org.hyperledger.besu.datatypes.Wei; + +import java.util.Deque; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes32; + +/** + * Transaction Values used by various EVM Opcodes. These are the values that either do not change or + * the backing stores whose changes transcend message frames and are not part of state, such as + * transient storage and address warming. + */ +public record TxValues( + Function blockHashLookup, + int maxStackSize, + UndoSet
warmedUpAddresses, + UndoTable warmedUpStorage, + Address originator, + Wei gasPrice, + BlockValues blockValues, + Deque messageFrameStack, + Address miningBeneficiary, + Optional> versionedHashes, + UndoTable transientStorage, + UndoSet
creates, + UndoSet
selfDestructs) { + + /** + * For all data stored in this record, undo the changes since the mark. + * + * @param mark the mark to which it should be rolled back to + */ + public void undoChanges(final long mark) { + warmedUpAddresses.undo(mark); + warmedUpStorage.undo(mark); + transientStorage.undo(mark); + creates.undo(mark); + selfDestructs.undo(mark); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java index 12618e7ee41..64cb42757ea 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java @@ -178,7 +178,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final Wei balance = account == null ? Wei.ZERO : account.getBalance(); // If the call is sending more value than the account has or the message frame is to deep // return a failed call - if (value(frame).compareTo(balance) > 0 || frame.getMessageStackDepth() >= 1024) { + if (value(frame).compareTo(balance) > 0 || frame.getDepth() >= 1024) { frame.expandMemory(inputDataOffset(frame), inputDataLength(frame)); frame.expandMemory(outputDataOffset(frame), outputDataLength(frame)); frame.incrementRemainingGas(gasAvailableForChildCall(frame) + cost); @@ -195,33 +195,23 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { : evm.getCode(contract.getCodeHash(), contract.getCode()); if (code.isValid()) { - final MessageFrame childFrame = - MessageFrame.builder() - .type(MessageFrame.Type.MESSAGE_CALL) - .messageFrameStack(frame.getMessageFrameStack()) - .worldUpdater(frame.getWorldUpdater().updater()) - .initialGas(gasAvailableForChildCall(frame)) - .address(address(frame)) - .originator(frame.getOriginatorAddress()) - .contract(to) - .gasPrice(frame.getGasPrice()) - .inputData(inputData) - .sender(sender(frame)) - .value(value(frame)) - .apparentValue(apparentValue(frame)) - .code(code) - .blockValues(frame.getBlockValues()) - .depth(frame.getMessageStackDepth() + 1) - .isStatic(isStatic(frame)) - .completer(child -> complete(frame, child)) - .miningBeneficiary(frame.getMiningBeneficiary()) - .blockHashLookup(frame.getBlockHashLookup()) - .maxStackSize(frame.getMaxStackSize()) - .versionedHashes(frame.getVersionedHashes()) - .build(); + // frame addition is automatically handled by parent messageFrameStack + MessageFrame.builder() + .parentMessageFrame(frame) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(gasAvailableForChildCall(frame)) + .address(address(frame)) + .contract(to) + .inputData(inputData) + .sender(sender(frame)) + .value(value(frame)) + .apparentValue(apparentValue(frame)) + .code(code) + .isStatic(isStatic(frame)) + .completer(child -> complete(frame, child)) + .build(); frame.incrementRemainingGas(cost); - frame.getMessageFrameStack().addFirst(childFrame); frame.setState(MessageFrame.State.CODE_SUSPENDED); return new OperationResult(cost, null, 0); } else { @@ -268,7 +258,6 @@ public void complete(final MessageFrame frame, final MessageFrame childFrame) { frame.popStackItems(getStackItemsConsumed()); if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { - frame.mergeWarmedUpFields(childFrame); frame.pushStackItem(SUCCESS_STACK_ITEM); } else { frame.pushStackItem(FAILURE_STACK_ITEM); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index 9b364347100..50ced6fd8b7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -90,7 +90,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } if (value.compareTo(account.getBalance()) > 0 - || frame.getMessageStackDepth() >= 1024 + || frame.getDepth() >= 1024 || account.getNonce() == -1) { fail(frame); } else { @@ -147,31 +147,21 @@ private void spawnChildMessage(final MessageFrame parent, final Code code, final gasCalculator().gasAvailableForChildCreate(parent.getRemainingGas()); parent.decrementRemainingGas(childGasStipend); - final MessageFrame childFrame = - MessageFrame.builder() - .type(MessageFrame.Type.CONTRACT_CREATION) - .messageFrameStack(parent.getMessageFrameStack()) - .worldUpdater(parent.getWorldUpdater().updater()) - .initialGas(childGasStipend) - .address(contractAddress) - .originator(parent.getOriginatorAddress()) - .contract(contractAddress) - .gasPrice(parent.getGasPrice()) - .inputData(Bytes.EMPTY) - .sender(parent.getRecipientAddress()) - .value(value) - .apparentValue(value) - .code(code) - .blockValues(parent.getBlockValues()) - .depth(parent.getMessageStackDepth() + 1) - .completer(child -> complete(parent, child, evm)) - .miningBeneficiary(parent.getMiningBeneficiary()) - .blockHashLookup(parent.getBlockHashLookup()) - .maxStackSize(parent.getMaxStackSize()) - .versionedHashes(parent.getVersionedHashes()) - .build(); - - parent.getMessageFrameStack().addFirst(childFrame); + // frame addition is automatically handled by parent messageFrameStack + MessageFrame.builder() + .parentMessageFrame(parent) + .type(MessageFrame.Type.CONTRACT_CREATION) + .initialGas(childGasStipend) + .address(contractAddress) + .contract(contractAddress) + .inputData(Bytes.EMPTY) + .sender(parent.getRecipientAddress()) + .value(value) + .apparentValue(value) + .code(code) + .completer(child -> complete(parent, child, evm)) + .build(); + parent.setState(MessageFrame.State.CODE_SUSPENDED); } @@ -190,7 +180,6 @@ private void complete(final MessageFrame frame, final MessageFrame childFrame, f frame.incrementGasRefund(childFrame.getGasRefund()); if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { - frame.mergeWarmedUpFields(childFrame); Address createdAddress = childFrame.getContractAddress(); frame.pushStackItem(Words.fromAddress(createdAddress)); onSuccess(frame, createdAddress); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java index d2b1c99688d..22d466e17d9 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java @@ -115,8 +115,9 @@ private void clearAccumulatedStateBesidesGasAndOutput(final MessageFrame frame) frame.getWorldUpdater().commit(); frame.clearLogs(); - frame.clearSelfDestructs(); frame.clearGasRefund(); + + frame.rollback(); } /** @@ -148,7 +149,6 @@ protected void revert(final MessageFrame frame) { */ private void completedSuccess(final MessageFrame frame) { frame.getWorldUpdater().commit(); - frame.commitTransientStorage(); frame.getMessageFrameStack().removeFirst(); frame.notifyCompletion(); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java index 703b0cec122..f466b443fba 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java @@ -22,13 +22,13 @@ import java.util.ArrayList; import java.util.List; -import com.google.common.collect.Multimap; +import com.google.common.collect.Table; import org.apache.tuweni.bytes.Bytes32; /** The Access List Operation Tracer. */ public class AccessListOperationTracer extends EstimateGasOperationTracer { - private Multimap warmedUpStorage; + private Table warmedUpStorage; @Override public void tracePostExecution(final MessageFrame frame, final OperationResult operationResult) { @@ -36,9 +36,6 @@ public void tracePostExecution(final MessageFrame frame, final OperationResult o warmedUpStorage = frame.getWarmedUpStorage(); } - @Override - public void tracePreExecution(final MessageFrame frame) {} - /** * Get the access list. * @@ -46,12 +43,12 @@ public void tracePreExecution(final MessageFrame frame) {} */ public List getAccessList() { final List list = new ArrayList<>(); - if (warmedUpStorage != null) { + if (warmedUpStorage != null && !warmedUpStorage.isEmpty()) { warmedUpStorage - .asMap() + .rowMap() .forEach( (address, storageKeys) -> - list.add(new AccessListEntry(address, new ArrayList<>(storageKeys)))); + list.add(new AccessListEntry(address, new ArrayList<>(storageKeys.keySet())))); } return list; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/EstimateGasOperationTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/EstimateGasOperationTracer.java index a9ac495a176..c7b11620311 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/EstimateGasOperationTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/EstimateGasOperationTracer.java @@ -27,12 +27,12 @@ public class EstimateGasOperationTracer implements OperationTracer { @Override public void tracePostExecution(final MessageFrame frame, final OperationResult operationResult) { - if (frame.getCurrentOperation() instanceof SStoreOperation && sStoreStipendNeeded == 0L) { - sStoreStipendNeeded = - ((SStoreOperation) frame.getCurrentOperation()).getMinimumGasRemaining(); + if (frame.getCurrentOperation() instanceof SStoreOperation sStoreOperation + && sStoreStipendNeeded == 0L) { + sStoreStipendNeeded = sStoreOperation.getMinimumGasRemaining(); } - if (maxDepth < frame.getMessageStackDepth()) { - maxDepth = frame.getMessageStackDepth(); + if (maxDepth < frame.getDepth()) { + maxDepth = frame.getDepth(); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java index 3db7c38e116..5dbc99dd0cf 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java @@ -48,6 +48,7 @@ public class StandardJsonTracer implements OperationTracer { private String gas; private Bytes memory; private int memorySize; + private int depth; /** * Instantiates a new Standard json tracer. @@ -125,6 +126,7 @@ public void tracePreExecution(final MessageFrame messageFrame) { } else { memory = null; } + depth = messageFrame.getMessageStackSize(); } @Override @@ -133,7 +135,6 @@ public void tracePostExecution( final Operation currentOp = messageFrame.getCurrentOperation(); final int opcode = currentOp.getOpcode(); final Bytes returnData = messageFrame.getReturnData(); - final int depth = messageFrame.getMessageStackDepth() + 1; final StringBuilder sb = new StringBuilder(1024); sb.append("{"); 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 e1121171ccc..865ab0b9b02 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 @@ -73,7 +73,7 @@ public class UpdateTrackingAccount implements MutableAccount, UpdateTrackingAccount(final Address address) { checkNotNull(address); this.address = address; - this.addressHash = Hash.hash(this.address); + this.addressHash = this.address.addressHash(); this.account = null; this.nonce = 0; @@ -95,7 +95,7 @@ public UpdateTrackingAccount(final A account) { this.addressHash = (account instanceof UpdateTrackingAccount) ? ((UpdateTrackingAccount) account).addressHash - : Hash.hash(this.address); + : this.address.addressHash(); this.account = account; this.nonce = account.getNonce(); @@ -243,7 +243,6 @@ public UInt256 getOriginalStorageValue(final UInt256 key) { } @Override - @SuppressWarnings("unchecked") public NavigableMap storageEntriesFrom( final Bytes32 startKeyHash, final int limit) { final NavigableMap entries; diff --git a/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoListTest.java b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoListTest.java new file mode 100644 index 00000000000..1ca96c470a8 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoListTest.java @@ -0,0 +1,383 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class UndoListTest { + UndoList subject; + + @BeforeEach + void createUndoMap() { + final List set = new ArrayList<>(); + subject = new UndoList<>(set); + } + + @Test + void markMovesForward() { + long mark = subject.mark(); + + subject.add("Hello"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.add("Hello"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.add("There"); + assertThat(subject.mark()).isGreaterThan(mark); + } + + @Test + void markOnlyMovesOnWrite() { + long mark; + subject.add("Hello"); + + mark = subject.mark(); + subject.add("Hi"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.undo(mark); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.add("Hello"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.undo(mark); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + // no actions + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.remove("Hi"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + // non-changing undo does not advance mark + subject.undo(mark); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + // non-existent remove doesn't advance mark + subject.remove("Bonjour"); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.clear(); + assertThat(subject.mark()).isGreaterThan(mark); + } + + @Test + void sizeAdjustsWithUndo() { + assertThat(subject).isEmpty(); + + subject.add("Hello"); + long mark1 = subject.mark(); + assertThat(subject).hasSize(1); + + subject.add("Hello"); + long mark2 = subject.mark(); + assertThat(subject).hasSize(2); + + subject.remove(0); + assertThat(subject).hasSize(1); + + subject.remove("Hello"); + assertThat(subject).isEmpty(); + + subject.undo(mark2); + assertThat(subject).hasSize(2); + + subject.undo(mark1); + assertThat(subject).hasSize(1); + + subject.undo(0); + assertThat(subject).isEmpty(); + } + + @Test + void checkUndoContents() { + long mark0 = subject.mark(); + subject.add("foo"); + long level1 = subject.mark(); + subject.add("baz"); + long level2 = subject.mark(); + subject.add(1, "qux"); + long level3 = subject.mark(); + subject.add("foo"); + long level4 = subject.mark(); + subject.add(2, "foo"); + long level5 = subject.mark(); + subject.add("foo"); + long level6 = subject.mark(); + subject.remove("foo"); + long level7 = subject.mark(); + subject.add("foo"); + long level8 = subject.mark(); + subject.set(3, "qux"); + long level9 = subject.mark(); + subject.clear(); + + assertThat(subject).isEmpty(); + + subject.undo(level9); + assertThat(subject).containsExactly("qux", "foo", "baz", "qux", "foo", "foo"); + + subject.undo(level8); + assertThat(subject).containsExactly("qux", "foo", "baz", "foo", "foo", "foo"); + + subject.undo(level7); + assertThat(subject).containsExactly("qux", "foo", "baz", "foo", "foo"); + + subject.undo(level6); + assertThat(subject).containsExactly("foo", "qux", "foo", "baz", "foo", "foo"); + + subject.undo(level5); + assertThat(subject).containsExactly("foo", "qux", "foo", "baz", "foo"); + + subject.undo(level4); + assertThat(subject).containsExactly("foo", "qux", "baz", "foo"); + + subject.undo(level3); + assertThat(subject).containsExactly("foo", "qux", "baz"); + + subject.undo(level2); + assertThat(subject).containsExactly("foo", "baz"); + + subject.undo(level1); + assertThat(subject).containsExactly("foo"); + + subject.undo(mark0); + assertThat(subject).isEmpty(); + } + + @Test + void addAll() { + subject.add("foo"); + + long mark = subject.mark(); + subject.addAll(List.of("Alpha", "Charlie")); + assertThat(subject).containsExactly("foo", "Alpha", "Charlie"); + + long mark2 = subject.mark(); + subject.addAll(2, List.of("foo", "bar")); + assertThat(subject).containsExactly("foo", "Alpha", "foo", "bar", "Charlie"); + + subject.undo(mark2); + assertThat(subject).containsExactly("foo", "Alpha", "Charlie"); + + subject.undo(mark); + assertThat(subject).containsExactly("foo"); + } + + @Test + void removeAll() { + subject.add("foo"); + subject.add("bar"); + subject.add("baz"); + subject.add("qux"); + subject.add("foo"); + + long mark = subject.mark(); + subject.removeAll(Set.of("bar", "baz")); + + assertThat(subject).containsExactly("foo", "qux", "foo"); + + subject.undo(mark); + assertThat(subject).containsExactly("foo", "bar", "baz", "qux", "foo"); + } + + @Test + void retainAll() { + subject.add("foo"); + subject.add("bar"); + subject.add("baz"); + subject.add("qux"); + subject.add("foo"); + + long mark = subject.mark(); + subject.retainAll(Set.of("bar", "baz")); + + assertThat(subject).containsExactly("bar", "baz"); + + subject.undo(mark); + assertThat(subject).containsExactly("foo", "bar", "baz", "qux", "foo"); + } + + @SuppressWarnings(("java:S5838")) // direct use of contains and containsAll need to be tested here + @Test + void contains() { + subject.add("one"); + long mark1 = subject.mark(); + subject.add("three"); + + assertThat(subject).containsOnly("one", "three"); + + subject.undo(mark1); + assertThat(subject).containsOnly("one"); + + subject.add("three"); + long mark2 = subject.mark(); + subject.remove("three"); + assertThat(subject).containsOnly("one"); + + subject.undo(mark2); + assertThat(subject).containsOnly("one", "three"); + + assertThat(subject.contains("one")).isTrue(); + assertThat(subject.contains("two")).isFalse(); + assertThat(subject.containsAll(Set.of("one", "three"))).isTrue(); + assertThat(subject.containsAll(Set.of("one", "two"))).isFalse(); + } + + @Test + void toArray() { + subject.add("foo"); + subject.add("bar"); + long mark = subject.mark(); + subject.add("baz"); + subject.add("qux"); + + String[] generated = subject.toArray(String[]::new); + //noinspection RedundantCast + assertThat(subject.toArray()).containsExactlyInAnyOrder((Object[]) generated); + + subject.undo(mark); + + assertThat(subject.toArray(new String[2])) + .containsExactlyInAnyOrder(subject.toArray(new String[0])); + } + + @SuppressWarnings("JdkObsolete") + @Test + void equalityTests() { + subject.add("Hello"); + long mark = subject.mark(); + subject.add("Hello"); + subject.add("Bonjour"); + subject.undo(mark); + + UndoList second = new UndoList<>(new LinkedList<>()); + second.add("Hello"); + + assertThat(subject).hasSameHashCodeAs(second).isEqualTo(second); + } + + @SuppressWarnings("JdkObsolete") + @Test + void globalMark() { + subject.add("Hello"); + UndoList second = new UndoList<>(new LinkedList<>()); + + second.add("Hello"); + // assert that a mark gathered from another undoSet works in another undoSet + long mark = second.mark(); + + subject.add("Hello"); + subject.add("Bonjour"); + subject.undo(mark); + + assertThat(subject).hasSameHashCodeAs(second).isEqualTo(second); + } + + @Test + void reading() { + subject.add("foo"); + subject.add("bar"); + long mark = subject.mark(); + subject.set(1, "bif"); + subject.add(1, "baz"); + subject.add("qux"); + subject.add("foo"); + + System.out.println(subject); + + assertThat(subject.get(1)).isEqualTo("baz"); + assertThat(subject.get(2)).isEqualTo("bif"); + assertThat(subject.indexOf("foo")).isZero(); + assertThat(subject.lastIndexOf("foo")).isEqualTo(4); + assertThat(subject.lastIndexOf("bar")).isNegative(); + subject.undo(mark); + assertThat(subject.get(1)).isEqualTo("bar"); + assertThat(subject.indexOf("foo")).isZero(); + assertThat(subject.lastIndexOf("foo")).isZero(); + assertThat(subject.lastIndexOf("bif")).isNegative(); + } + + @Test + void sublist() { + subject.add("foo"); + subject.add("bar"); + long mark1 = subject.mark(); + subject.add("baz"); + subject.add("qux"); + subject.add("foo"); + + assertThat(subject.subList(1, 4)).containsExactly("bar", "baz", "qux"); + subject.undo(mark1); + subject.add("one"); + subject.add("two"); + assertThat(subject.subList(1, 4)).containsExactly("bar", "one", "two"); + } + + @Test + void listIterator() { + subject.add("foo"); + subject.add("bar"); + long mark1 = subject.mark(); + subject.add("baz"); + subject.add("qux"); + subject.add("foo"); + + ListIterator listIterator = subject.listIterator(); + assertThat(listIterator.hasPrevious()).isFalse(); + listIterator.next(); + listIterator.next(); + assertThat(listIterator.next()).isEqualTo("baz"); + + assertThatThrownBy(listIterator::remove) + .isExactlyInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> listIterator.add("quux")) + .isExactlyInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> listIterator.set("quux")) + .isExactlyInstanceOf(UnsupportedOperationException.class); + + assertThat(listIterator.previousIndex()).isEqualTo(2); + assertThat(listIterator.nextIndex()).isEqualTo(3); + assertThat(listIterator.previous()).isEqualTo("baz"); + + subject.undo(mark1); + ListIterator listIterator2 = subject.listIterator(1); + assertThat(listIterator2.next()).isEqualTo("bar"); + assertThat(listIterator2.hasNext()).isFalse(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoMapTest.java b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoMapTest.java new file mode 100644 index 00000000000..b2eb030eab9 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoMapTest.java @@ -0,0 +1,269 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class UndoMapTest { + UndoMap subject; + + @BeforeEach + void createUndoMap() { + final Map map = new HashMap<>(); + subject = new UndoMap<>(map); + } + + @Test + void markMovesForward() { + long mark = subject.mark(); + + subject.put("Hello", "World"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.put("Hello", "There"); + assertThat(subject.mark()).isGreaterThan(mark); + } + + @Test + void markOnlyMovesOnWrite() { + long mark; + subject.put("Hello", "World"); + + mark = subject.mark(); + subject.put("Hi", "There"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.undo(mark); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.put("Hello", "Again"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.undo(mark); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + // no actions + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.remove("Hi"); + assertThat(subject.mark()).isGreaterThan(mark); + + subject.put("Bonjour", "Hi"); + + mark = subject.mark(); + // failed specified value remove does not advance mark + subject.remove("Bonjour", "Hello"); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + // non-changing undo does not advance mark + subject.undo(mark); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + // modifying change advances mark + subject.remove("Bonjour"); + subject.undo(mark); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.clear(); + assertThat(subject.mark()).isGreaterThan(mark); + } + + @Test + void sizeAdjustsWithUndo() { + assertThat(subject).isEmpty(); + + subject.put("Hello", "World"); + long mark1 = subject.mark(); + assertThat(subject).hasSize(1); + + subject.put("Hello", "There"); + long mark2 = subject.mark(); + assertThat(subject).hasSize(1); + + subject.remove("Hello"); + assertThat(subject).isEmpty(); + + subject.undo(mark2); + assertThat(subject).hasSize(1); + + subject.undo(mark1); + assertThat(subject).hasSize(1); + + subject.undo(0); + assertThat(subject).isEmpty(); + } + + @Test + void checkUndoContents() { + long mark0 = subject.mark(); + subject.put("foo", "bar"); + long level1 = subject.mark(); + subject.put("baz", "bif"); + long level2 = subject.mark(); + subject.put("qux", "quux"); + long level3 = subject.mark(); + subject.put("foo", "FEE"); + long level4 = subject.mark(); + subject.put("foo", "bar"); + long level5 = subject.mark(); + subject.put("foo", "FIE"); + long level6 = subject.mark(); + subject.remove("foo"); + long level7 = subject.mark(); + subject.put("foo", "FUM"); + long level8 = subject.mark(); + subject.put("qux", "quuux"); + long level9 = subject.mark(); + subject.clear(); + + assertThat(subject).isEmpty(); + + subject.undo(level9); + assertThat(subject) + .containsOnly(Map.entry("foo", "FUM"), Map.entry("baz", "bif"), Map.entry("qux", "quuux")); + + subject.undo(level8); + assertThat(subject) + .containsOnly(Map.entry("foo", "FUM"), Map.entry("baz", "bif"), Map.entry("qux", "quux")); + + subject.undo(level7); + assertThat(subject).containsOnly(Map.entry("baz", "bif"), Map.entry("qux", "quux")); + + subject.undo(level6); + assertThat(subject) + .containsOnly(Map.entry("foo", "FIE"), Map.entry("baz", "bif"), Map.entry("qux", "quux")); + + subject.undo(level5); + assertThat(subject) + .containsOnly(Map.entry("foo", "bar"), Map.entry("baz", "bif"), Map.entry("qux", "quux")); + + subject.undo(level4); + assertThat(subject) + .containsOnly(Map.entry("foo", "FEE"), Map.entry("baz", "bif"), Map.entry("qux", "quux")); + + subject.undo(level3); + assertThat(subject) + .containsOnly(Map.entry("foo", "bar"), Map.entry("baz", "bif"), Map.entry("qux", "quux")); + + subject.undo(level2); + assertThat(subject).containsOnly(Map.entry("foo", "bar"), Map.entry("baz", "bif")); + + subject.undo(level1); + assertThat(subject).containsOnly(Map.entry("foo", "bar")); + + subject.undo(mark0); + assertThat(subject).isEmpty(); + } + + @Test + void putAll() { + subject.put("foo", "bar"); + + long mark = subject.mark(); + subject.putAll(Map.of("Alpha", "Bravo", "Charlie", "Delta")); + assertThat(subject) + .containsOnly( + Map.entry("foo", "bar"), Map.entry("Alpha", "Bravo"), Map.entry("Charlie", "Delta")); + + subject.undo(mark); + assertThat(subject).containsOnly(Map.entry("foo", "bar")); + + mark = subject.mark(); + subject.putAll(Map.of("foo", "foo", "bar", "bar")); + assertThat(subject).containsOnly(Map.entry("foo", "foo"), Map.entry("bar", "bar")); + + subject.undo(mark); + assertThat(subject).containsOnly(Map.entry("foo", "bar")); + } + + @Test + void contains() { + subject.put("one", "two"); + long mark1 = subject.mark(); + subject.put("three", "four"); + + assertThat(subject).containsKey("three").containsValue("four").containsOnlyKeys("one", "three"); + assertThat(subject.values()).containsOnly("two", "four"); + + subject.undo(mark1); + assertThat(subject) + .doesNotContainKey("three") + .doesNotContainValue("four") + .containsOnlyKeys("one"); + assertThat(subject.values()).containsOnly("two"); + + subject.put("three", "four"); + long mark2 = subject.mark(); + subject.remove("three"); + assertThat(subject) + .doesNotContainKey("three") + .doesNotContainValue("four") + .containsOnlyKeys("one"); + assertThat(subject.values()).containsOnly("two"); + + subject.undo(mark2); + assertThat(subject).containsKey("three").containsValue("four").containsOnlyKeys("one", "three"); + assertThat(subject.values()).containsOnly("two", "four"); + } + + @Test + void equalityTests() { + subject.put("Hello", "There"); + long mark = subject.mark(); + subject.put("Hello", "World"); + subject.put("Bonjour", "Hi"); + subject.undo(mark); + + UndoMap second = new UndoMap<>(new TreeMap<>()); + second.put("Hello", "There"); + + assertThat(subject.keySet()).isEqualTo(second.keySet()); + assertThat(subject).hasSameHashCodeAs(second).isEqualTo(second); + } + + @Test + void globalMark() { + subject.put("Hello", "There"); + UndoMap second = new UndoMap<>(new TreeMap<>()); + + second.put("Hello", "There"); + // assert that a mark gathered from another undomap works in another undomap + long mark = second.mark(); + + subject.put("Hello", "World"); + subject.put("Bonjour", "Hi"); + subject.undo(mark); + + assertThat(subject.keySet()).isEqualTo(second.keySet()); + assertThat(subject).hasSameHashCodeAs(second).isEqualTo(second); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoSetTest.java b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoSetTest.java new file mode 100644 index 00000000000..545cb68df12 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoSetTest.java @@ -0,0 +1,301 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class UndoSetTest { + UndoSet subject; + + @BeforeEach + void createUndoSet() { + final Set set = new HashSet<>(); + subject = new UndoSet<>(set); + } + + @Test + void markMovesForward() { + long mark = subject.mark(); + + subject.add("Hello"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.add("Hello"); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.add("There"); + assertThat(subject.mark()).isGreaterThan(mark); + } + + @Test + void markOnlyMovesOnWrite() { + long mark; + subject.add("Hello"); + + mark = subject.mark(); + subject.add("Hi"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + subject.undo(mark); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.add("Hello"); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.undo(mark); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + // no actions + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.remove("Hi"); + assertThat(subject.mark()).isGreaterThan(mark); + + mark = subject.mark(); + // non-changing undo does not advance mark + subject.undo(mark); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + // non-existent remove doesn't advance mark + subject.remove("Bonjour"); + assertThat(subject.mark()).isEqualTo(mark); + + mark = subject.mark(); + subject.clear(); + assertThat(subject.mark()).isGreaterThan(mark); + } + + @Test + void sizeAdjustsWithUndo() { + assertThat(subject).isEmpty(); + + subject.add("Hello"); + long mark1 = subject.mark(); + assertThat(subject).hasSize(1); + + subject.add("Hello"); + long mark2 = subject.mark(); + assertThat(subject).hasSize(1); + + subject.remove("Hello"); + assertThat(subject).isEmpty(); + + subject.undo(mark2); + assertThat(subject).hasSize(1); + + subject.undo(mark1); + assertThat(subject).hasSize(1); + + subject.undo(0); + assertThat(subject).isEmpty(); + } + + @Test + void checkUndoContents() { + long mark0 = subject.mark(); + subject.add("foo"); + long level1 = subject.mark(); + subject.add("baz"); + long level2 = subject.mark(); + subject.add("qux"); + long level3 = subject.mark(); + subject.add("foo"); + long level4 = subject.mark(); + subject.add("foo"); + long level5 = subject.mark(); + subject.add("foo"); + long level6 = subject.mark(); + subject.remove("foo"); + long level7 = subject.mark(); + subject.add("foo"); + long level8 = subject.mark(); + subject.add("qux"); + long level9 = subject.mark(); + subject.clear(); + + assertThat(subject).isEmpty(); + + subject.undo(level9); + assertThat(subject).containsOnly("foo", "baz", "qux"); + + subject.undo(level8); + assertThat(subject).containsOnly("foo", "baz", "qux"); + + subject.undo(level7); + assertThat(subject).containsOnly("baz", "qux"); + + subject.undo(level6); + assertThat(subject).containsOnly("foo", "baz", "qux"); + + subject.undo(level5); + assertThat(subject).containsOnly("foo", "baz", "qux"); + + subject.undo(level4); + assertThat(subject).containsOnly("foo", "baz", "qux"); + + subject.undo(level3); + assertThat(subject).containsOnly("foo", "baz", "qux"); + + subject.undo(level2); + assertThat(subject).containsOnly("foo", "baz"); + + subject.undo(level1); + assertThat(subject).containsOnly("foo"); + + subject.undo(mark0); + assertThat(subject).isEmpty(); + } + + @Test + void addAll() { + subject.add("foo"); + + long mark = subject.mark(); + subject.addAll(Set.of("Alpha", "Charlie")); + assertThat(subject).containsOnly("foo", "Alpha", "Charlie"); + + subject.undo(mark); + assertThat(subject).containsOnly("foo"); + + mark = subject.mark(); + subject.addAll(Set.of("foo", "bar")); + assertThat(subject).containsOnly("foo", "bar"); + + subject.undo(mark); + assertThat(subject).containsOnly("foo"); + } + + @Test + void removeAll() { + subject.add("foo"); + subject.add("bar"); + subject.add("baz"); + subject.add("qux"); + + long mark = subject.mark(); + subject.removeAll(Set.of("bar", "baz")); + + assertThat(subject).containsOnly("foo", "qux"); + + subject.undo(mark); + assertThat(subject).containsOnly("foo", "bar", "baz", "qux"); + } + + @Test + void retainAll() { + subject.add("foo"); + subject.add("bar"); + subject.add("baz"); + subject.add("qux"); + + long mark = subject.mark(); + subject.retainAll(Set.of("bar", "baz")); + + assertThat(subject).containsOnly("bar", "baz"); + + subject.undo(mark); + assertThat(subject).containsOnly("foo", "bar", "baz", "qux"); + } + + @SuppressWarnings(("java:S5838")) // direct use of contains and containsAll need to be tested here + @Test + void contains() { + subject.add("one"); + long mark1 = subject.mark(); + subject.add("three"); + + assertThat(subject).containsOnly("one", "three"); + + assertThat(subject.contains("one")).isTrue(); + assertThat(subject.contains("two")).isFalse(); + assertThat(subject.containsAll(Set.of("one", "three"))).isTrue(); + assertThat(subject.containsAll(Set.of("one", "two"))).isFalse(); + + subject.undo(mark1); + assertThat(subject).containsOnly("one"); + + subject.add("three"); + long mark2 = subject.mark(); + subject.remove("three"); + assertThat(subject).containsOnly("one"); + + subject.undo(mark2); + assertThat(subject).containsOnly("one", "three"); + } + + @Test + void toArray() { + subject.add("foo"); + subject.add("bar"); + long mark = subject.mark(); + subject.add("baz"); + subject.add("qux"); + + String[] generated = subject.toArray(String[]::new); + //noinspection RedundantCast + assertThat(subject.toArray()).containsExactlyInAnyOrder((Object[]) generated); + + subject.undo(mark); + + assertThat(subject.toArray(new String[2])) + .containsExactlyInAnyOrder(subject.toArray(new String[0])); + } + + @Test + void equalityTests() { + subject.add("Hello"); + long mark = subject.mark(); + subject.add("Hello"); + subject.add("Bonjour"); + subject.undo(mark); + + UndoSet second = new UndoSet<>(new TreeSet<>()); + second.add("Hello"); + + assertThat(subject).hasSameHashCodeAs(second).isEqualTo(second); + } + + @Test + void globalMark() { + subject.add("Hello"); + UndoSet second = new UndoSet<>(new TreeSet<>()); + + second.add("Hello"); + // assert that a mark gathered from another undoSet works in another undoSet + long mark = second.mark(); + + subject.add("Hello"); + subject.add("Bonjour"); + subject.undo(mark); + + assertThat(subject).hasSameHashCodeAs(second).isEqualTo(second); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java index ff8014c10e7..07f0f17040c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java @@ -37,7 +37,6 @@ import org.hyperledger.besu.evm.operation.OperationRegistry; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.util.ArrayDeque; import javax.annotation.Nonnull; import org.apache.tuweni.bytes.Bytes; @@ -86,7 +85,6 @@ private MessageFrame createJumpFrame(final CodeV0 getsCached) { final MessageFrame frame = MessageFrame.builder() .type(MESSAGE_CALL) - .messageFrameStack(new ArrayDeque<>()) .worldUpdater(mock(WorldUpdater.class)) .initialGas(10_000L) .address(Address.ZERO) @@ -99,7 +97,6 @@ private MessageFrame createJumpFrame(final CodeV0 getsCached) { .apparentValue(Wei.ZERO) .code(getsCached) .blockValues(mock(BlockValues.class)) - .depth(0) .completer(f -> {}) .miningBeneficiary(Address.ZERO) .blockHashLookup(l -> Hash.EMPTY) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java index cccd5194878..aa3dfd40217 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java @@ -41,7 +41,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount; -import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; import java.util.Optional; @@ -143,7 +143,6 @@ protected void onInvalid(final MessageFrame frame, final CodeInvalid invalidCode private void executeOperation(final Bytes contract, final EVM evm) { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final ArrayDeque messageFrameStack = new ArrayDeque<>(); final MessageFrame messageFrame = MessageFrame.builder() .type(MessageFrame.Type.CONTRACT_CREATION) @@ -153,18 +152,17 @@ private void executeOperation(final Bytes contract, final EVM evm) { .value(Wei.ZERO) .apparentValue(Wei.ZERO) .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) - .depth(1) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) .blockValues(mock(BlockValues.class)) .gasPrice(Wei.ZERO) - .messageFrameStack(messageFrameStack) .miningBeneficiary(Address.ZERO) .originator(Address.ZERO) .initialGas(100000L) .worldUpdater(worldUpdater) .build(); + final Deque messageFrameStack = messageFrame.getMessageFrameStack(); messageFrame.pushStackItem(Bytes.ofUnsignedLong(contract.size())); messageFrame.pushStackItem(memoryOffset); messageFrame.pushStackItem(Bytes.EMPTY); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java index 5fdc25d9380..5590cd09b4f 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java @@ -42,7 +42,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount; -import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; import org.apache.tuweni.bytes.Bytes; @@ -157,13 +157,11 @@ public void setUp(final String sender, final String salt, final String code) { .value(Wei.ZERO) .apparentValue(Wei.ZERO) .code(CodeFactory.createCode(codeBytes, 0, true)) - .depth(1) .completer(__ -> {}) .address(Address.fromHexString(sender)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) .blockValues(mock(BlockValues.class)) .gasPrice(Wei.ZERO) - .messageFrameStack(new ArrayDeque<>()) .miningBeneficiary(Address.ZERO) .originator(Address.ZERO) .initialGas(100_000L) @@ -214,9 +212,7 @@ void shouldCalculateGasPrice( void shanghaiMaxInitCodeSizeCreate() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.fromHexString("0xc000"); - final ArrayDeque messageFrameStack = new ArrayDeque<>(); - final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); when(account.getMutable()).thenReturn(mutableAccount); when(account.getNonce()).thenReturn(55L); @@ -231,7 +227,7 @@ void shanghaiMaxInitCodeSizeCreate() { final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); var result = maxInitCodeOperation.execute(messageFrame, evm); - final MessageFrame createFrame = messageFrameStack.peek(); + final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); final ContractCreationProcessor ccp = new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); ccp.process(createFrame, OperationTracer.NO_TRACING); @@ -246,9 +242,7 @@ void shanghaiMaxInitCodeSizeCreate() { void shanghaiMaxInitCodeSizePlus1Create() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.fromHexString("0xc001"); - final ArrayDeque messageFrameStack = new ArrayDeque<>(); - final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); when(account.getMutable()).thenReturn(mutableAccount); when(account.getNonce()).thenReturn(55L); @@ -271,8 +265,7 @@ private MessageFrame testMemoryFrame( final UInt256 memoryOffset, final UInt256 memoryLength, final UInt256 value, - final int depth, - final ArrayDeque messageFrameStack) { + final int depth) { final MessageFrame messageFrame = MessageFrame.builder() .type(MessageFrame.Type.CONTRACT_CREATION) @@ -282,13 +275,11 @@ private MessageFrame testMemoryFrame( .value(Wei.ZERO) .apparentValue(Wei.ZERO) .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) - .depth(depth) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) .blockValues(mock(BlockValues.class)) .gasPrice(Wei.ZERO) - .messageFrameStack(messageFrameStack) .miningBeneficiary(Address.ZERO) .originator(Address.ZERO) .initialGas(100000L) @@ -301,6 +292,10 @@ private MessageFrame testMemoryFrame( messageFrame.expandMemory(0, 500); messageFrame.writeMemory( memoryOffset.trimLeadingZeros().toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE); + final Deque messageFrameStack = messageFrame.getMessageFrameStack(); + while (messageFrameStack.size() < depth) { + messageFrameStack.push(messageFrame); + } return messageFrame; } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java index e3ffc975772..b685b8702ff 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java @@ -42,7 +42,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount; -import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; import org.apache.tuweni.bytes.Bytes; @@ -89,9 +89,7 @@ void createFromMemoryMutationSafe() { // Given: Execute a CREATE operation with a contract that logs in the constructor final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final ArrayDeque messageFrameStack = new ArrayDeque<>(); - final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); when(account.getMutable()).thenReturn(mutableAccount); when(account.getNonce()).thenReturn(55L); @@ -106,7 +104,7 @@ void createFromMemoryMutationSafe() { final EVM evm = MainnetEVMs.london(EvmConfiguration.DEFAULT); operation.execute(messageFrame, evm); - final MessageFrame createFrame = messageFrameStack.peek(); + final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); final ContractCreationProcessor ccp = new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); ccp.process(createFrame, OperationTracer.NO_TRACING); @@ -130,9 +128,7 @@ void createFromMemoryMutationSafe() { void nonceTooLarge() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final ArrayDeque messageFrameStack = new ArrayDeque<>(); - final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); when(worldUpdater.getAccount(any())).thenReturn(account); when(account.getMutable()).thenReturn(mutableAccount); @@ -149,9 +145,8 @@ void nonceTooLarge() { void messageFrameStackTooDeep() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final ArrayDeque messageFrameStack = new ArrayDeque<>(); final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1025, messageFrameStack); + testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1025); when(worldUpdater.getAccount(any())).thenReturn(account); when(account.getMutable()).thenReturn(mutableAccount); @@ -168,9 +163,9 @@ void messageFrameStackTooDeep() { void notEnoughValue() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final ArrayDeque messageFrameStack = new ArrayDeque<>(); final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.valueOf(1), 1, messageFrameStack); + testMemoryFrame(memoryOffset, memoryLength, UInt256.valueOf(1), 1); + final Deque messageFrameStack = messageFrame.getMessageFrameStack(); for (int i = 0; i < 1025; i++) { messageFrameStack.add(messageFrame); } @@ -190,9 +185,7 @@ void notEnoughValue() { void shanghaiMaxInitCodeSizeCreate() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.fromHexString("0xc000"); - final ArrayDeque messageFrameStack = new ArrayDeque<>(); - final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); when(account.getMutable()).thenReturn(mutableAccount); when(account.getNonce()).thenReturn(55L); @@ -207,7 +200,7 @@ void shanghaiMaxInitCodeSizeCreate() { final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); var result = maxInitCodeOperation.execute(messageFrame, evm); - final MessageFrame createFrame = messageFrameStack.peek(); + final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); final ContractCreationProcessor ccp = new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); ccp.process(createFrame, OperationTracer.NO_TRACING); @@ -222,9 +215,7 @@ void shanghaiMaxInitCodeSizeCreate() { void shanghaiMaxInitCodeSizePlus1Create() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.fromHexString("0xc001"); - final ArrayDeque messageFrameStack = new ArrayDeque<>(); - final MessageFrame messageFrame = - testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1, messageFrameStack); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); when(account.getMutable()).thenReturn(mutableAccount); when(account.getNonce()).thenReturn(55L); @@ -299,8 +290,7 @@ private MessageFrame testMemoryFrame( final UInt256 memoryOffset, final UInt256 memoryLength, final UInt256 value, - final int depth, - final ArrayDeque messageFrameStack) { + final int depth) { final MessageFrame messageFrame = MessageFrame.builder() .type(MessageFrame.Type.CONTRACT_CREATION) @@ -310,13 +300,11 @@ private MessageFrame testMemoryFrame( .value(Wei.ZERO) .apparentValue(Wei.ZERO) .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) - .depth(depth) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) .blockValues(mock(BlockValues.class)) .gasPrice(Wei.ZERO) - .messageFrameStack(messageFrameStack) .miningBeneficiary(Address.ZERO) .originator(Address.ZERO) .initialGas(100000L) @@ -328,6 +316,10 @@ private MessageFrame testMemoryFrame( messageFrame.expandMemory(0, 500); messageFrame.writeMemory( memoryOffset.trimLeadingZeros().toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE); + final Deque messageFrameStack = messageFrame.getMessageFrameStack(); + while (messageFrameStack.size() < depth) { + messageFrameStack.push(messageFrame); + } return messageFrame; } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java index 3ed4d38a339..f596bed8aad 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java @@ -35,8 +35,6 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount; -import java.util.ArrayDeque; - import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; @@ -85,13 +83,11 @@ void checkContractDeletionCommon( .value(Wei.ZERO) .apparentValue(Wei.ZERO) .code(CodeFactory.createCode(SELFDESTRUCT_CODE, 0, true)) - .depth(1) .completer(__ -> {}) .address(originatorAddress) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) .blockValues(mock(BlockValues.class)) .gasPrice(Wei.ZERO) - .messageFrameStack(new ArrayDeque<>()) .miningBeneficiary(Address.ZERO) .originator(Address.ZERO) .initialGas(100_000L) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/Benchmarks.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/Benchmarks.java index 94230483ca5..5a4c57695e8 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/Benchmarks.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/Benchmarks.java @@ -36,7 +36,6 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.math.BigInteger; -import java.util.ArrayDeque; import java.util.Map; import java.util.Random; import java.util.concurrent.TimeUnit; @@ -66,13 +65,11 @@ public class Benchmarks { .value(Wei.ZERO) .apparentValue(Wei.ZERO) .code(CodeV0.EMPTY_CODE) - .depth(1) .completer(__ -> {}) .address(Address.ZERO) .blockHashLookup(n -> null) .blockValues(mock(BlockValues.class)) .gasPrice(Wei.ZERO) - .messageFrameStack(new ArrayDeque<>()) .miningBeneficiary(Address.ZERO) .originator(Address.ZERO) .initialGas(100_000L) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestCodeExecutor.java b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestCodeExecutor.java index 69fbe4e13bb..02f6ffba191 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestCodeExecutor.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestCodeExecutor.java @@ -28,7 +28,6 @@ import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.util.ArrayDeque; import java.util.Deque; import java.util.function.Consumer; @@ -54,8 +53,6 @@ public MessageFrame executeCode( public MessageFrame executeCode( final String codeHexString, final long gasLimit, final WorldUpdater worldUpdater) { - final Deque messageFrameStack = new ArrayDeque<>(); - final MessageCallProcessor messageCallProcessor = new MessageCallProcessor(evm, new PrecompileContractRegistry()); final Bytes codeBytes = Bytes.fromHexString(codeHexString.replaceAll("\\s", "")); @@ -63,7 +60,6 @@ public MessageFrame executeCode( final MessageFrame initialFrame = new TestMessageFrameBuilder() - .messageFrameStack(messageFrameStack) .worldUpdater(worldUpdater) .initialGas(gasLimit) .address(SENDER_ADDRESS) @@ -75,10 +71,9 @@ public MessageFrame executeCode( .value(Wei.ZERO) .code(code) .blockValues(blockValues) - .depth(0) .build(); - messageFrameStack.addFirst(initialFrame); + final Deque messageFrameStack = initialFrame.getMessageFrameStack(); while (!messageFrameStack.isEmpty()) { messageCallProcessor.process(messageFrameStack.peekFirst(), OperationTracer.NO_TRACING); } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java index dfceccaf4f8..0e7fdb50418 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java @@ -27,9 +27,7 @@ import org.hyperledger.besu.evm.toy.ToyWorld; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -41,7 +39,6 @@ public class TestMessageFrameBuilder { public static final Address DEFAUT_ADDRESS = Address.fromHexString("0xe8f1b89"); private static final int maxStackSize = DEFAULT_MAX_STACK_SIZE; - private Deque messageFrameStack = new ArrayDeque<>(); private Optional blockValues = Optional.empty(); private Optional worldUpdater = Optional.empty(); private long initialGas = Long.MAX_VALUE; @@ -56,15 +53,9 @@ public class TestMessageFrameBuilder { private int pc = 0; private int section = 0; private final List stackItems = new ArrayList<>(); - private int depth = 0; private Optional> blockHashLookup = Optional.empty(); private Bytes memory = Bytes.EMPTY; - TestMessageFrameBuilder messageFrameStack(final Deque messageFrameStack) { - this.messageFrameStack = messageFrameStack; - return this; - } - public TestMessageFrameBuilder worldUpdater(final WorldUpdater worldUpdater) { this.worldUpdater = Optional.of(worldUpdater); return this; @@ -130,11 +121,6 @@ public TestMessageFrameBuilder blockValues(final BlockValues blockValues) { return this; } - public TestMessageFrameBuilder depth(final int depth) { - this.depth = depth; - return this; - } - public TestMessageFrameBuilder pushStackItem(final Bytes item) { stackItems.add(item); return this; @@ -154,7 +140,6 @@ public MessageFrame build() { final MessageFrame frame = MessageFrame.builder() .type(MessageFrame.Type.MESSAGE_CALL) - .messageFrameStack(messageFrameStack) .worldUpdater(worldUpdater.orElseGet(this::createDefaultWorldUpdater)) .initialGas(initialGas) .address(address) @@ -167,7 +152,6 @@ public MessageFrame build() { .contract(contract) .code(code) .blockValues(blockValues.orElseGet(() -> new FakeBlockValues(1337))) - .depth(depth) .completer(c -> {}) .miningBeneficiary(Address.ZERO) .blockHashLookup(blockHashLookup.orElse(number -> Hash.hash(Words.longBytes(number)))) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToyCommand.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToyCommand.java index 00e875d1849..eb8030cde76 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToyCommand.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToyCommand.java @@ -34,7 +34,6 @@ import java.io.PrintStream; import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.util.ArrayDeque; import java.util.Deque; import java.util.List; @@ -168,11 +167,9 @@ public void run() { ? new StandardJsonTracer(System.out, showMemory, showStack, showReturnData) : OperationTracer.NO_TRACING; - final Deque messageFrameStack = new ArrayDeque<>(); - messageFrameStack.add( + MessageFrame initialMessageFrame = MessageFrame.builder() .type(MessageFrame.Type.MESSAGE_CALL) - .messageFrameStack(messageFrameStack) .worldUpdater(worldUpdater.updater()) .initialGas(gas) .contract(Address.ZERO) @@ -185,25 +182,21 @@ public void run() { .apparentValue(ethValue) .code(code) .blockValues(new ToyBlockValues()) - .depth(0) .completer(c -> {}) .miningBeneficiary(Address.ZERO) .blockHashLookup(h -> null) - .build()); + .build(); final MessageCallProcessor mcp = new MessageCallProcessor(evm, precompileContractRegistry); final ContractCreationProcessor ccp = new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0); stopwatch.start(); + Deque messageFrameStack = initialMessageFrame.getMessageFrameStack(); while (!messageFrameStack.isEmpty()) { final MessageFrame messageFrame = messageFrameStack.peek(); switch (messageFrame.getType()) { - case CONTRACT_CREATION: - ccp.process(messageFrame, tracer); - break; - case MESSAGE_CALL: - mcp.process(messageFrame, tracer); - break; + case CONTRACT_CREATION -> ccp.process(messageFrame, tracer); + case MESSAGE_CALL -> mcp.process(messageFrame, tracer); } if (lastLoop) { if (messageFrame.getExceptionalHaltReason().isPresent()) { 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 dd6971d83f4..d81e8f7f46e 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 @@ -40,7 +40,7 @@ public class ToyAccount implements EvmAccount, MutableAccount { private Address address; private final Supplier addressHash = - Suppliers.memoize(() -> address == null ? Hash.ZERO : Hash.hash(address)); + Suppliers.memoize(() -> address == null ? Hash.ZERO : address.addressHash()); private long nonce; private Wei balance; private Bytes code;