mirror of https://github.com/hyperledger/besu
Journaled world state (#6023)
Introduce a new Journaled World State Updater. Within a transaction it keeps one copy of account and storage state, restoring previous revisions on reverts and exceptional halts. This updater only supports post-merge semantics with regard to empty accounts, namely that they do not exist in world state. Adds an EvmConfiguration option for stacked vs journaled updater, and wire it in where needed. The staked updater is default mode, which is the current behavior prior to this patch. Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>pull/6110/head
parent
84dee295d9
commit
094c8416df
@ -0,0 +1,181 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
package org.hyperledger.besu.collections.undo; |
||||
|
||||
import java.util.Comparator; |
||||
import java.util.NavigableMap; |
||||
import java.util.NavigableSet; |
||||
import java.util.SortedMap; |
||||
|
||||
/** |
||||
* A map that supports rolling back the map to a prior state. |
||||
* |
||||
* <p>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 <V> The type of the collection. |
||||
*/ |
||||
public class UndoNavigableMap<K, V> extends UndoMap<K, V> implements NavigableMap<K, V> { |
||||
|
||||
/** |
||||
* Create an UndoMap backed by another Map instance. |
||||
* |
||||
* @param delegate The Map instance to use for backing storage |
||||
*/ |
||||
public UndoNavigableMap(final NavigableMap<K, V> delegate) { |
||||
super(delegate); |
||||
} |
||||
|
||||
/** |
||||
* Create an undo navigable map backed by a specific map. |
||||
* |
||||
* @param map The map storing the current state |
||||
* @return an undoable map |
||||
* @param <K> the key type |
||||
* @param <V> the value type |
||||
*/ |
||||
public static <K, V> UndoNavigableMap<K, V> of(final NavigableMap<K, V> map) { |
||||
return new UndoNavigableMap<>(map); |
||||
} |
||||
|
||||
@Override |
||||
public Comparator<? super K> comparator() { |
||||
return ((NavigableMap<K, V>) delegate).comparator(); |
||||
} |
||||
|
||||
@Override |
||||
public SortedMap<K, V> subMap(final K fromKey, final K toKey) { |
||||
return ((NavigableMap<K, V>) delegate).subMap(fromKey, toKey); |
||||
} |
||||
|
||||
@Override |
||||
public SortedMap<K, V> headMap(final K toKey) { |
||||
return ((NavigableMap<K, V>) delegate).headMap(toKey); |
||||
} |
||||
|
||||
@Override |
||||
public SortedMap<K, V> tailMap(final K fromKey) { |
||||
return ((NavigableMap<K, V>) delegate).tailMap(fromKey); |
||||
} |
||||
|
||||
@Override |
||||
public K firstKey() { |
||||
return ((NavigableMap<K, V>) delegate).firstKey(); |
||||
} |
||||
|
||||
@Override |
||||
public K lastKey() { |
||||
return ((NavigableMap<K, V>) delegate).lastKey(); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, V> lowerEntry(final K key) { |
||||
return ((NavigableMap<K, V>) delegate).lowerEntry(key); |
||||
} |
||||
|
||||
@Override |
||||
public K lowerKey(final K key) { |
||||
return ((NavigableMap<K, V>) delegate).lowerKey(key); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, V> floorEntry(final K key) { |
||||
return ((NavigableMap<K, V>) delegate).floorEntry(key); |
||||
} |
||||
|
||||
@Override |
||||
public K floorKey(final K key) { |
||||
return ((NavigableMap<K, V>) delegate).floorKey(key); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, V> ceilingEntry(final K key) { |
||||
return ((NavigableMap<K, V>) delegate).ceilingEntry(key); |
||||
} |
||||
|
||||
@Override |
||||
public K ceilingKey(final K key) { |
||||
return ((NavigableMap<K, V>) delegate).ceilingKey(key); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, V> higherEntry(final K key) { |
||||
return ((NavigableMap<K, V>) delegate).higherEntry(key); |
||||
} |
||||
|
||||
@Override |
||||
public K higherKey(final K key) { |
||||
return ((NavigableMap<K, V>) delegate).higherKey(key); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, V> firstEntry() { |
||||
return ((NavigableMap<K, V>) delegate).firstEntry(); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, V> lastEntry() { |
||||
return ((NavigableMap<K, V>) delegate).lastEntry(); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, V> pollFirstEntry() { |
||||
return ((NavigableMap<K, V>) delegate).pollFirstEntry(); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, V> pollLastEntry() { |
||||
return ((NavigableMap<K, V>) delegate).pollLastEntry(); |
||||
} |
||||
|
||||
@Override |
||||
public NavigableMap<K, V> descendingMap() { |
||||
return ((NavigableMap<K, V>) delegate).descendingMap(); |
||||
} |
||||
|
||||
@Override |
||||
public NavigableSet<K> navigableKeySet() { |
||||
return ((NavigableMap<K, V>) delegate).navigableKeySet(); |
||||
} |
||||
|
||||
@Override |
||||
public NavigableSet<K> descendingKeySet() { |
||||
return ((NavigableMap<K, V>) delegate).descendingKeySet(); |
||||
} |
||||
|
||||
@Override |
||||
public NavigableMap<K, V> subMap( |
||||
final K fromKey, final boolean fromInclusive, final K toKey, final boolean toInclusive) { |
||||
return ((NavigableMap<K, V>) delegate).subMap(fromKey, fromInclusive, toKey, toInclusive); |
||||
} |
||||
|
||||
@Override |
||||
public NavigableMap<K, V> headMap(final K toKey, final boolean inclusive) { |
||||
return ((NavigableMap<K, V>) delegate).headMap(toKey, inclusive); |
||||
} |
||||
|
||||
@Override |
||||
public NavigableMap<K, V> tailMap(final K fromKey, final boolean inclusive) { |
||||
return ((NavigableMap<K, V>) delegate).tailMap(fromKey, inclusive); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "UndoSortedSet{" + "delegate=" + delegate + ", undoLog=" + undoLog + '}'; |
||||
} |
||||
} |
@ -0,0 +1,126 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
package org.hyperledger.besu.collections.undo; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
|
||||
/** |
||||
* An undoable value that tracks the value across time. |
||||
* |
||||
* @param <T> The type of the scaler. |
||||
*/ |
||||
public class UndoScalar<T> implements Undoable { |
||||
record UndoEntry<T>(T value, long level) { |
||||
UndoEntry(final T value) { |
||||
this(value, Undoable.incrementMarkStatic()); |
||||
} |
||||
} |
||||
|
||||
T value; |
||||
final List<UndoEntry<T>> undoLog; |
||||
|
||||
/** |
||||
* Create an undoable scalar with an initial value |
||||
* |
||||
* @param value the initial value |
||||
* @return the undoable scalar |
||||
* @param <T> the type of the scalar |
||||
*/ |
||||
public static <T> UndoScalar<T> of(final T value) { |
||||
return new UndoScalar<>(value); |
||||
} |
||||
|
||||
/** |
||||
* Create an undo scalar with an initial value |
||||
* |
||||
* @param value the initial value |
||||
*/ |
||||
public UndoScalar(final T value) { |
||||
undoLog = new ArrayList<>(); |
||||
this.value = value; |
||||
} |
||||
|
||||
@Override |
||||
public long lastUpdate() { |
||||
return undoLog.isEmpty() ? 0L : undoLog.get(undoLog.size() - 1).level; |
||||
} |
||||
|
||||
/** |
||||
* Has this scalar had any change since the inital value |
||||
* |
||||
* @return true if there are any changes to undo |
||||
*/ |
||||
public boolean updated() { |
||||
return !undoLog.isEmpty(); |
||||
} |
||||
|
||||
/** |
||||
* Get the current value of the scalar. |
||||
* |
||||
* @return the current value |
||||
*/ |
||||
public T get() { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Set a new value in the scalar. |
||||
* |
||||
* @param value new value |
||||
*/ |
||||
public void set(final T value) { |
||||
if (!Objects.equals(this.value, value)) { |
||||
undoLog.add(new UndoEntry<>(this.value)); |
||||
this.value = value; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void undo(final long mark) { |
||||
if (undoLog.isEmpty()) { |
||||
return; |
||||
} |
||||
int pos = undoLog.size() - 1; |
||||
while (pos >= 0) { |
||||
UndoEntry<T> entry = undoLog.get(pos); |
||||
if (entry.level <= mark) { |
||||
return; |
||||
} |
||||
value = entry.value; |
||||
undoLog.remove(pos); |
||||
pos--; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(final Object o) { |
||||
if (this == o) return true; |
||||
if (!(o instanceof UndoScalar<?> that)) return false; |
||||
return Objects.equals(value, that.value); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(value); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "UndoScalar{" + "value=" + value + ", undoLog=" + undoLog + '}'; |
||||
} |
||||
} |
@ -0,0 +1,397 @@ |
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.worldstate; |
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull; |
||||
|
||||
import org.hyperledger.besu.collections.undo.UndoNavigableMap; |
||||
import org.hyperledger.besu.collections.undo.UndoScalar; |
||||
import org.hyperledger.besu.collections.undo.Undoable; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.evm.ModificationNotAllowedException; |
||||
import org.hyperledger.besu.evm.account.AccountStorageEntry; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
|
||||
import java.util.Map; |
||||
import java.util.NavigableMap; |
||||
import java.util.TreeMap; |
||||
import javax.annotation.Nullable; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.apache.tuweni.units.bigints.UInt256; |
||||
|
||||
/** |
||||
* An implementation of {@link MutableAccount} that tracks updates made to the account since the |
||||
* creation of the updater this is linked to. |
||||
* |
||||
* <p>Note that in practice this only track the modified value of the nonce and balance, but doesn't |
||||
* remind if those were modified or not (the reason being that any modification of an account imply |
||||
* the underlying trie node will have to be updated, and so knowing if the nonce and balance where |
||||
* updated or not doesn't matter, we just need their new value). |
||||
*/ |
||||
public class JournaledAccount implements MutableAccount, Undoable { |
||||
private final Address address; |
||||
private final Hash addressHash; |
||||
|
||||
@Nullable private MutableAccount account; |
||||
|
||||
private long transactionBoundaryMark; |
||||
private final UndoScalar<Long> nonce; |
||||
private final UndoScalar<Wei> balance; |
||||
private final UndoScalar<Bytes> code; |
||||
private final UndoScalar<Hash> codeHash; |
||||
private final UndoScalar<Boolean> deleted; |
||||
|
||||
// Only contains updated storage entries, but may contain entry with a value of 0 to signify
|
||||
// deletion.
|
||||
private final UndoNavigableMap<UInt256, UInt256> updatedStorage; |
||||
private boolean storageWasCleared = false; |
||||
|
||||
boolean immutable; |
||||
|
||||
/** |
||||
* Instantiates a new Update tracking account. |
||||
* |
||||
* @param address the address |
||||
*/ |
||||
JournaledAccount(final Address address) { |
||||
checkNotNull(address); |
||||
this.address = address; |
||||
this.addressHash = this.address.addressHash(); |
||||
this.account = null; |
||||
|
||||
this.nonce = UndoScalar.of(0L); |
||||
this.balance = UndoScalar.of(Wei.ZERO); |
||||
|
||||
this.code = UndoScalar.of(Bytes.EMPTY); |
||||
this.codeHash = UndoScalar.of(Hash.EMPTY); |
||||
this.deleted = UndoScalar.of(Boolean.FALSE); |
||||
this.updatedStorage = UndoNavigableMap.of(new TreeMap<>()); |
||||
this.transactionBoundaryMark = mark(); |
||||
} |
||||
|
||||
/** |
||||
* Instantiates a new Update tracking account. |
||||
* |
||||
* @param account the account |
||||
*/ |
||||
public JournaledAccount(final MutableAccount account) { |
||||
checkNotNull(account); |
||||
|
||||
this.address = account.getAddress(); |
||||
this.addressHash = |
||||
(account instanceof JournaledAccount journaledAccount) |
||||
? journaledAccount.addressHash |
||||
: this.address.addressHash(); |
||||
this.account = account; |
||||
|
||||
if (account instanceof JournaledAccount that) { |
||||
this.nonce = that.nonce; |
||||
this.balance = that.balance; |
||||
|
||||
this.code = that.code; |
||||
this.codeHash = that.codeHash; |
||||
|
||||
this.deleted = that.deleted; |
||||
|
||||
this.updatedStorage = that.updatedStorage; |
||||
} else { |
||||
this.nonce = UndoScalar.of(account.getNonce()); |
||||
this.balance = UndoScalar.of(account.getBalance()); |
||||
|
||||
this.code = UndoScalar.of(account.getCode()); |
||||
this.codeHash = UndoScalar.of(account.getCodeHash()); |
||||
|
||||
this.deleted = UndoScalar.of(Boolean.FALSE); |
||||
|
||||
this.updatedStorage = UndoNavigableMap.of(new TreeMap<>()); |
||||
} |
||||
transactionBoundaryMark = mark(); |
||||
} |
||||
|
||||
/** |
||||
* The original account over which this tracks updates. |
||||
* |
||||
* @return The original account over which this tracks updates, or {@code null} if this is a newly |
||||
* created account. |
||||
*/ |
||||
public MutableAccount getWrappedAccount() { |
||||
return account; |
||||
} |
||||
|
||||
/** |
||||
* Sets wrapped account. |
||||
* |
||||
* @param account the account |
||||
*/ |
||||
public void setWrappedAccount(final MutableAccount account) { |
||||
if (this.account == null) { |
||||
this.account = account; |
||||
storageWasCleared = false; |
||||
} else { |
||||
throw new IllegalStateException("Already tracking a wrapped account"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Whether the code of the account was modified. |
||||
* |
||||
* @return {@code true} if the code was updated. |
||||
*/ |
||||
public boolean codeWasUpdated() { |
||||
return code.lastUpdate() >= transactionBoundaryMark; |
||||
} |
||||
|
||||
/** |
||||
* A map of the storage entries that were modified. |
||||
* |
||||
* @return a map containing all entries that have been modified. This <b>may</b> contain entries |
||||
* with a value of 0 to signify deletion. |
||||
*/ |
||||
@Override |
||||
public Map<UInt256, UInt256> getUpdatedStorage() { |
||||
return updatedStorage; |
||||
} |
||||
|
||||
@Override |
||||
public void becomeImmutable() { |
||||
immutable = true; |
||||
} |
||||
|
||||
@Override |
||||
public Address getAddress() { |
||||
return address; |
||||
} |
||||
|
||||
@Override |
||||
public Hash getAddressHash() { |
||||
return addressHash; |
||||
} |
||||
|
||||
@Override |
||||
public long getNonce() { |
||||
return nonce.get(); |
||||
} |
||||
|
||||
@Override |
||||
public void setNonce(final long value) { |
||||
if (immutable) { |
||||
throw new ModificationNotAllowedException(); |
||||
} |
||||
nonce.set(value); |
||||
} |
||||
|
||||
@Override |
||||
public Wei getBalance() { |
||||
return balance.get(); |
||||
} |
||||
|
||||
@Override |
||||
public void setBalance(final Wei value) { |
||||
if (immutable) { |
||||
throw new ModificationNotAllowedException(); |
||||
} |
||||
balance.set(value); |
||||
} |
||||
|
||||
@Override |
||||
public Bytes getCode() { |
||||
return code.get(); |
||||
} |
||||
|
||||
@Override |
||||
public Hash getCodeHash() { |
||||
return codeHash.get(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean hasCode() { |
||||
return !code.get().isEmpty(); |
||||
} |
||||
|
||||
/** |
||||
* Mark the account as deleted/not deleted |
||||
* |
||||
* @param accountDeleted delete or don't delete this account. |
||||
*/ |
||||
public void setDeleted(final boolean accountDeleted) { |
||||
if (immutable) { |
||||
throw new ModificationNotAllowedException(); |
||||
} |
||||
deleted.set(accountDeleted); |
||||
} |
||||
|
||||
/** |
||||
* Is the account marked as deleted? |
||||
* |
||||
* @return is the account deleted? |
||||
*/ |
||||
public Boolean getDeleted() { |
||||
return deleted.get(); |
||||
} |
||||
|
||||
@Override |
||||
public void setCode(final Bytes code) { |
||||
if (immutable) { |
||||
throw new ModificationNotAllowedException(); |
||||
} |
||||
this.code.set(code == null ? Bytes.EMPTY : code); |
||||
this.codeHash.set(code == null ? Hash.EMPTY : Hash.hash(code)); |
||||
} |
||||
|
||||
/** Mark transaction boundary. */ |
||||
void markTransactionBoundary() { |
||||
transactionBoundaryMark = mark(); |
||||
} |
||||
|
||||
@Override |
||||
public UInt256 getStorageValue(final UInt256 key) { |
||||
final UInt256 value = updatedStorage.get(key); |
||||
if (value != null) { |
||||
return value; |
||||
} |
||||
if (storageWasCleared) { |
||||
return UInt256.ZERO; |
||||
} |
||||
|
||||
// We haven't updated the key-value yet, so either it's a new account, and it doesn't have the
|
||||
// key, or we should query the underlying storage for its existing value (which might be 0).
|
||||
return account == null ? UInt256.ZERO : account.getStorageValue(key); |
||||
} |
||||
|
||||
@Override |
||||
public UInt256 getOriginalStorageValue(final UInt256 key) { |
||||
// if storage was cleared then it is because it was an empty account, hence zero storage
|
||||
// if we have no backing account, it's a new account, hence zero storage
|
||||
// otherwise ask outside of what we are journaling, journaled change may not be original value
|
||||
return (storageWasCleared || account == null) ? UInt256.ZERO : account.getStorageValue(key); |
||||
} |
||||
|
||||
@Override |
||||
public NavigableMap<Bytes32, AccountStorageEntry> storageEntriesFrom( |
||||
final Bytes32 startKeyHash, final int limit) { |
||||
final NavigableMap<Bytes32, AccountStorageEntry> entries; |
||||
if (account != null) { |
||||
entries = account.storageEntriesFrom(startKeyHash, limit); |
||||
} else { |
||||
entries = new TreeMap<>(); |
||||
} |
||||
updatedStorage.entrySet().stream() |
||||
.map(entry -> AccountStorageEntry.forKeyAndValue(entry.getKey(), entry.getValue())) |
||||
.filter(entry -> entry.getKeyHash().compareTo(startKeyHash) >= 0) |
||||
.forEach(entry -> entries.put(entry.getKeyHash(), entry)); |
||||
|
||||
while (entries.size() > limit) { |
||||
entries.remove(entries.lastKey()); |
||||
} |
||||
return entries; |
||||
} |
||||
|
||||
@Override |
||||
public void setStorageValue(final UInt256 key, final UInt256 value) { |
||||
if (immutable) { |
||||
throw new ModificationNotAllowedException(); |
||||
} |
||||
updatedStorage.put(key, value); |
||||
} |
||||
|
||||
@Override |
||||
public void clearStorage() { |
||||
if (immutable) { |
||||
throw new ModificationNotAllowedException(); |
||||
} |
||||
storageWasCleared = true; |
||||
updatedStorage.clear(); |
||||
} |
||||
|
||||
/** |
||||
* Gets storage was cleared. |
||||
* |
||||
* @return boolean if storage was cleared |
||||
*/ |
||||
public boolean getStorageWasCleared() { |
||||
return storageWasCleared; |
||||
} |
||||
|
||||
/** |
||||
* Sets storage was cleared. |
||||
* |
||||
* @param storageWasCleared the storage was cleared |
||||
*/ |
||||
public void setStorageWasCleared(final boolean storageWasCleared) { |
||||
if (immutable) { |
||||
throw new ModificationNotAllowedException(); |
||||
} |
||||
this.storageWasCleared = storageWasCleared; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
String storage = updatedStorage.isEmpty() ? "[not updated]" : updatedStorage.toString(); |
||||
if (updatedStorage.isEmpty() && storageWasCleared) { |
||||
storage = "[cleared]"; |
||||
} |
||||
return String.format( |
||||
"%s -> {nonce: %s, balance:%s, code:%s, storage:%s }", |
||||
address, |
||||
nonce, |
||||
balance, |
||||
code.mark() >= transactionBoundaryMark ? "[not updated]" : code.get(), |
||||
storage); |
||||
} |
||||
|
||||
@Override |
||||
public long lastUpdate() { |
||||
return Math.max( |
||||
nonce.lastUpdate(), |
||||
Math.max( |
||||
balance.lastUpdate(), |
||||
Math.max( |
||||
code.lastUpdate(), Math.max(codeHash.lastUpdate(), updatedStorage.lastUpdate())))); |
||||
} |
||||
|
||||
@Override |
||||
public void undo(final long mark) { |
||||
nonce.undo(mark); |
||||
balance.undo(mark); |
||||
code.undo(mark); |
||||
codeHash.undo(mark); |
||||
deleted.undo(mark); |
||||
updatedStorage.undo(mark); |
||||
} |
||||
|
||||
/** Commit this journaled account entry to the parent, if it is not a journaled account. */ |
||||
public void commit() { |
||||
if (!(account instanceof JournaledAccount)) { |
||||
if (nonce.updated()) { |
||||
account.setNonce(nonce.get()); |
||||
} |
||||
if (balance.updated()) { |
||||
account.setBalance(balance.get()); |
||||
} |
||||
if (code.updated()) { |
||||
account.setCode(code.get() == null ? Bytes.EMPTY : code.get()); |
||||
} |
||||
if (updatedStorage.updated()) { |
||||
updatedStorage.forEach(account::setStorageValue); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,184 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.evm.worldstate; |
||||
|
||||
import org.hyperledger.besu.collections.undo.UndoMap; |
||||
import org.hyperledger.besu.collections.undo.UndoSet; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.evm.account.Account; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
import org.hyperledger.besu.evm.internal.EvmConfiguration; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.HashSet; |
||||
import java.util.Optional; |
||||
|
||||
/** |
||||
* The Journaled updater. |
||||
* |
||||
* @param <W> the WorldView type parameter |
||||
*/ |
||||
public class JournaledUpdater<W extends WorldView> implements WorldUpdater { |
||||
|
||||
final EvmConfiguration evmConfiguration; |
||||
final WorldUpdater parentWorld; |
||||
final AbstractWorldUpdater<W, ? extends MutableAccount> rootWorld; |
||||
final UndoMap<Address, JournaledAccount> accounts; |
||||
final UndoSet<Address> deleted; |
||||
final long undoMark; |
||||
|
||||
/** |
||||
* Instantiates a new Stacked updater. |
||||
* |
||||
* @param world the world |
||||
* @param evmConfiguration the EVM Configuration parameters |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public JournaledUpdater(final WorldUpdater world, final EvmConfiguration evmConfiguration) { |
||||
parentWorld = world; |
||||
this.evmConfiguration = evmConfiguration; |
||||
if (world instanceof JournaledUpdater<?>) { |
||||
JournaledUpdater<W> journaledUpdater = (JournaledUpdater<W>) world; |
||||
accounts = journaledUpdater.accounts; |
||||
deleted = journaledUpdater.deleted; |
||||
rootWorld = journaledUpdater.rootWorld; |
||||
} else if (world instanceof AbstractWorldUpdater<?, ?>) { |
||||
accounts = new UndoMap<>(new HashMap<>()); |
||||
deleted = UndoSet.of(new HashSet<>()); |
||||
rootWorld = (AbstractWorldUpdater<W, ? extends MutableAccount>) world; |
||||
} else { |
||||
throw new IllegalArgumentException( |
||||
"WorldUpdater must be a JournaledWorldUpdater or an AbstractWorldUpdater"); |
||||
} |
||||
undoMark = accounts.mark(); |
||||
} |
||||
|
||||
/** |
||||
* Get an account suitable for mutation. Defer to parent if not tracked locally. |
||||
* |
||||
* @param address the account at the address, for mutaton. |
||||
* @return the mutable account |
||||
*/ |
||||
protected MutableAccount getForMutation(final Address address) { |
||||
final JournaledAccount wrappedTracker = accounts.get(address); |
||||
if (wrappedTracker != null) { |
||||
return wrappedTracker; |
||||
} |
||||
final MutableAccount account = rootWorld.getForMutation(address); |
||||
return account == null ? null : new UpdateTrackingAccount<>(account); |
||||
} |
||||
|
||||
@Override |
||||
public Collection<? extends Account> getTouchedAccounts() { |
||||
return new ArrayList<>(accounts.values()); |
||||
} |
||||
|
||||
@Override |
||||
public Collection<Address> getDeletedAccountAddresses() { |
||||
return new ArrayList<>(deleted); |
||||
} |
||||
|
||||
/** |
||||
* Remove all changes done by this layer. Rollback to the state prior to the updater's changes. |
||||
*/ |
||||
protected void reset() { |
||||
accounts.values().forEach(a -> a.undo(undoMark)); |
||||
accounts.undo(undoMark); |
||||
deleted.undo(undoMark); |
||||
} |
||||
|
||||
@Override |
||||
public void revert() { |
||||
reset(); |
||||
} |
||||
|
||||
@Override |
||||
public void commit() { |
||||
if (!(parentWorld instanceof JournaledUpdater<?>)) { |
||||
accounts.values().forEach(JournaledAccount::commit); |
||||
deleted.forEach(parentWorld::deleteAccount); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Optional<WorldUpdater> parentUpdater() { |
||||
return Optional.of(parentWorld); |
||||
} |
||||
|
||||
/** Mark transaction boundary. */ |
||||
@Override |
||||
public void markTransactionBoundary() { |
||||
accounts.values().forEach(JournaledAccount::markTransactionBoundary); |
||||
} |
||||
|
||||
@Override |
||||
public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { |
||||
JournaledAccount journaledAccount = |
||||
new JournaledAccount(rootWorld.createAccount(address, nonce, balance)); |
||||
accounts.put(address, journaledAccount); |
||||
return new JournaledAccount(journaledAccount); |
||||
} |
||||
|
||||
@Override |
||||
public MutableAccount getAccount(final Address address) { |
||||
// We may have updated it already, so check that first.
|
||||
final JournaledAccount existing = accounts.get(address); |
||||
if (existing != null) { |
||||
return existing; |
||||
} |
||||
if (deleted.contains(address)) { |
||||
return null; |
||||
} |
||||
|
||||
// Otherwise, get it from our wrapped view and create a new update tracker.
|
||||
final MutableAccount origin = rootWorld.getAccount(address); |
||||
if (origin == null) { |
||||
return null; |
||||
} else { |
||||
var newAccount = new JournaledAccount(origin); |
||||
accounts.put(address, newAccount); |
||||
return newAccount; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void deleteAccount(final Address address) { |
||||
deleted.add(address); |
||||
var account = accounts.get(address); |
||||
if (account != null) { |
||||
account.setDeleted(true); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Account get(final Address address) { |
||||
final MutableAccount existing = accounts.get(address); |
||||
if (existing != null) { |
||||
return existing; |
||||
} |
||||
if (deleted.contains(address)) { |
||||
return null; |
||||
} |
||||
return rootWorld.get(address); |
||||
} |
||||
|
||||
@Override |
||||
public WorldUpdater updater() { |
||||
return new JournaledUpdater<W>(this, evmConfiguration); |
||||
} |
||||
} |
Loading…
Reference in new issue