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 extends K, ? extends V> 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 extends V> 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 extends R, ? extends C, ? extends V> 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;
|