Compare commits
9 Commits
401e6f36ae
...
92dc7a38c5
Author | SHA1 | Date |
---|---|---|
Martin HS | 92dc7a38c5 | |
Martin HS | ab4a1cc01f | |
Martin Holst Swende | fa13bd6c97 | |
Martin Holst Swende | 474e2bee48 | |
Marius van der Wijden | 08b5bb9182 | |
Martin Holst Swende | d4072dec1a | |
Martin Holst Swende | 03d720707b | |
Martin Holst Swende | 56c5d77dae | |
Martin Holst Swende | 94a7b35752 |
|
@ -254,9 +254,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
|
|
||||||
var (
|
var (
|
||||||
txContext = core.NewEVMTxContext(msg)
|
txContext = core.NewEVMTxContext(msg)
|
||||||
snapshot = statedb.Snapshot()
|
|
||||||
prevGas = gaspool.Gas()
|
prevGas = gaspool.Gas()
|
||||||
)
|
)
|
||||||
|
statedb.Snapshot()
|
||||||
if tracer != nil && tracer.OnTxStart != nil {
|
if tracer != nil && tracer.OnTxStart != nil {
|
||||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||||
}
|
}
|
||||||
|
@ -265,7 +265,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
evm.SetTxContext(txContext)
|
evm.SetTxContext(txContext)
|
||||||
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
|
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statedb.RevertToSnapshot(snapshot)
|
statedb.RevertSnapshot()
|
||||||
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err)
|
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err)
|
||||||
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
|
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
|
||||||
gaspool.SetGas(prevGas)
|
gaspool.SetGas(prevGas)
|
||||||
|
@ -279,6 +279,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
statedb.DiscardSnapshot()
|
||||||
includedTxs = append(includedTxs, tx)
|
includedTxs = append(includedTxs, tx)
|
||||||
if hashError != nil {
|
if hashError != nil {
|
||||||
return nil, nil, nil, NewError(ErrorMissingBlockhash, hashError)
|
return nil, nil, nil, NewError(ErrorMissingBlockhash, hashError)
|
||||||
|
|
|
@ -176,7 +176,8 @@ func runCmd(ctx *cli.Context) error {
|
||||||
sdb := state.NewDatabase(triedb, nil)
|
sdb := state.NewDatabase(triedb, nil)
|
||||||
statedb, _ = state.New(genesis.Root(), sdb)
|
statedb, _ = state.New(genesis.Root(), sdb)
|
||||||
chainConfig = genesisConfig.Config
|
chainConfig = genesisConfig.Config
|
||||||
|
statedb.Snapshot()
|
||||||
|
defer statedb.DiscardSnapshot()
|
||||||
if ctx.String(SenderFlag.Name) != "" {
|
if ctx.String(SenderFlag.Name) != "" {
|
||||||
sender = common.HexToAddress(ctx.String(SenderFlag.Name))
|
sender = common.HexToAddress(ctx.String(SenderFlag.Name))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
// Copyright 2024 the go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
@ -17,477 +17,82 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"maps"
|
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type revision struct {
|
type journal interface {
|
||||||
id int
|
// snapshot starts a new journal scope which can be reverted or discarded.
|
||||||
journalIndex int
|
// The lifeycle of journalling is as follows:
|
||||||
}
|
// - snapshot() starts a 'scope'.
|
||||||
|
// - The method snapshot() may be called any number of times.
|
||||||
// journalEntry is a modification entry in the state change journal that can be
|
// - For each call to snapshot, there should be a corresponding call to end
|
||||||
// reverted on demand.
|
// the scope via either of:
|
||||||
type journalEntry interface {
|
// - revertToSnapshot, which undoes the changes in the scope, or
|
||||||
// revert undoes the changes introduced by this journal entry.
|
// - discardSnapshot, which discards the ability to revert the changes in the scope.
|
||||||
revert(*StateDB)
|
snapshot()
|
||||||
|
|
||||||
// dirtied returns the Ethereum address modified by this journal entry.
|
// revertSnapshot reverts all state changes made since the last call to snapshot().
|
||||||
dirtied() *common.Address
|
revertSnapshot(s *StateDB)
|
||||||
|
|
||||||
// copy returns a deep-copied journal entry.
|
// discardSnapshot removes the latest snapshot; after calling this
|
||||||
copy() journalEntry
|
// method, it is no longer possible to revert to that particular snapshot, the
|
||||||
}
|
// changes are considered part of the parent scope.
|
||||||
|
discardSnapshot()
|
||||||
// journal contains the list of state modifications applied since the last state
|
|
||||||
// commit. These are tracked to be able to be reverted in the case of an execution
|
// reset clears the journal so it can be reused.
|
||||||
// exception or request for reversal.
|
reset()
|
||||||
type journal struct {
|
|
||||||
entries []journalEntry // Current changes tracked by the journal
|
// dirtyAccounts returns a list of all accounts modified in this journal
|
||||||
dirties map[common.Address]int // Dirty accounts and the number of changes
|
dirtyAccounts() []common.Address
|
||||||
|
|
||||||
validRevisions []revision
|
// accessListAddAccount journals the adding of addr to the access list
|
||||||
nextRevisionId int
|
accessListAddAccount(addr common.Address)
|
||||||
}
|
|
||||||
|
// accessListAddSlot journals the adding of addr/slot to the access list
|
||||||
// newJournal creates a new initialized journal.
|
accessListAddSlot(addr common.Address, slot common.Hash)
|
||||||
func newJournal() *journal {
|
|
||||||
return &journal{
|
// logChange journals the adding of a log related to the txHash
|
||||||
dirties: make(map[common.Address]int),
|
logChange(txHash common.Hash)
|
||||||
}
|
|
||||||
}
|
// createObject journals the event of a new account created in the trie.
|
||||||
|
createObject(addr common.Address)
|
||||||
// reset clears the journal, after this operation the journal can be used anew.
|
|
||||||
// It is semantically similar to calling 'newJournal', but the underlying slices
|
// createContract journals the creation of a new contract at addr.
|
||||||
// can be reused.
|
// OBS: This method must not be applied twice, it assumes that the pre-state
|
||||||
func (j *journal) reset() {
|
// (i.e the rollback-state) is non-created.
|
||||||
j.entries = j.entries[:0]
|
createContract(addr common.Address, account *types.StateAccount)
|
||||||
j.validRevisions = j.validRevisions[:0]
|
|
||||||
clear(j.dirties)
|
// destruct journals the destruction of an account in the trie.
|
||||||
j.nextRevisionId = 0
|
// pre-state (i.e the rollback-state) is non-destructed (and, for the purpose
|
||||||
}
|
// of EIP-XXX (TODO lookup), created in this tx).
|
||||||
|
destruct(addr common.Address, account *types.StateAccount)
|
||||||
// snapshot returns an identifier for the current revision of the state.
|
|
||||||
func (j *journal) snapshot() int {
|
// storageChange journals a change in the storage data related to addr.
|
||||||
id := j.nextRevisionId
|
// It records the key and previous value of the slot.
|
||||||
j.nextRevisionId++
|
storageChange(addr common.Address, key, prev, origin common.Hash)
|
||||||
j.validRevisions = append(j.validRevisions, revision{id, j.length()})
|
|
||||||
return id
|
// transientStateChange journals a change in the t-storage data related to addr.
|
||||||
}
|
// It records the key and previous value of the slot.
|
||||||
|
transientStateChange(addr common.Address, key, prev common.Hash)
|
||||||
// revertToSnapshot reverts all state changes made since the given revision.
|
|
||||||
func (j *journal) revertToSnapshot(revid int, s *StateDB) {
|
// refundChange journals that the refund has been changed, recording the previous value.
|
||||||
// Find the snapshot in the stack of valid snapshots.
|
refundChange(previous uint64)
|
||||||
idx := sort.Search(len(j.validRevisions), func(i int) bool {
|
|
||||||
return j.validRevisions[i].id >= revid
|
// balanceChange journals that the balance of addr has been changed, recording the previous value
|
||||||
})
|
balanceChange(addr common.Address, account *types.StateAccount, destructed, newContract bool)
|
||||||
if idx == len(j.validRevisions) || j.validRevisions[idx].id != revid {
|
|
||||||
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
|
// setCode journals that the code of addr has been set.
|
||||||
}
|
// OBS: This method must not be applied twice -- it always assumes that the
|
||||||
snapshot := j.validRevisions[idx].journalIndex
|
// pre-state (i.e the rollback-state) is "no code".
|
||||||
|
setCode(addr common.Address, account *types.StateAccount)
|
||||||
// Replay the journal to undo changes and remove invalidated snapshots
|
|
||||||
j.revert(s, snapshot)
|
// nonceChange journals that the nonce of addr was changed, recording the previous value.
|
||||||
j.validRevisions = j.validRevisions[:idx]
|
nonceChange(addr common.Address, account *types.StateAccount, destructed, newContract bool)
|
||||||
}
|
|
||||||
|
// touchChange journals that the account at addr was touched during execution.
|
||||||
// append inserts a new modification entry to the end of the change journal.
|
touchChange(addr common.Address, account *types.StateAccount, destructed, newContract bool)
|
||||||
func (j *journal) append(entry journalEntry) {
|
|
||||||
j.entries = append(j.entries, entry)
|
// copy returns a deep-copied journal.
|
||||||
if addr := entry.dirtied(); addr != nil {
|
copy() journal
|
||||||
j.dirties[*addr]++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// revert undoes a batch of journalled modifications along with any reverted
|
|
||||||
// dirty handling too.
|
|
||||||
func (j *journal) revert(statedb *StateDB, snapshot int) {
|
|
||||||
for i := len(j.entries) - 1; i >= snapshot; i-- {
|
|
||||||
// Undo the changes made by the operation
|
|
||||||
j.entries[i].revert(statedb)
|
|
||||||
|
|
||||||
// Drop any dirty tracking induced by the change
|
|
||||||
if addr := j.entries[i].dirtied(); addr != nil {
|
|
||||||
if j.dirties[*addr]--; j.dirties[*addr] == 0 {
|
|
||||||
delete(j.dirties, *addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j.entries = j.entries[:snapshot]
|
|
||||||
}
|
|
||||||
|
|
||||||
// dirty explicitly sets an address to dirty, even if the change entries would
|
|
||||||
// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD
|
|
||||||
// precompile consensus exception.
|
|
||||||
func (j *journal) dirty(addr common.Address) {
|
|
||||||
j.dirties[addr]++
|
|
||||||
}
|
|
||||||
|
|
||||||
// length returns the current number of entries in the journal.
|
|
||||||
func (j *journal) length() int {
|
|
||||||
return len(j.entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy returns a deep-copied journal.
|
|
||||||
func (j *journal) copy() *journal {
|
|
||||||
entries := make([]journalEntry, 0, j.length())
|
|
||||||
for i := 0; i < j.length(); i++ {
|
|
||||||
entries = append(entries, j.entries[i].copy())
|
|
||||||
}
|
|
||||||
return &journal{
|
|
||||||
entries: entries,
|
|
||||||
dirties: maps.Clone(j.dirties),
|
|
||||||
validRevisions: slices.Clone(j.validRevisions),
|
|
||||||
nextRevisionId: j.nextRevisionId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) logChange(txHash common.Hash) {
|
|
||||||
j.append(addLogChange{txhash: txHash})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) createObject(addr common.Address) {
|
|
||||||
j.append(createObjectChange{account: addr})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) createContract(addr common.Address) {
|
|
||||||
j.append(createContractChange{account: addr})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) destruct(addr common.Address) {
|
|
||||||
j.append(selfDestructChange{account: addr})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) storageChange(addr common.Address, key, prev, origin common.Hash) {
|
|
||||||
j.append(storageChange{
|
|
||||||
account: addr,
|
|
||||||
key: key,
|
|
||||||
prevvalue: prev,
|
|
||||||
origvalue: origin,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) transientStateChange(addr common.Address, key, prev common.Hash) {
|
|
||||||
j.append(transientStorageChange{
|
|
||||||
account: addr,
|
|
||||||
key: key,
|
|
||||||
prevalue: prev,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) refundChange(previous uint64) {
|
|
||||||
j.append(refundChange{prev: previous})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) balanceChange(addr common.Address, previous *uint256.Int) {
|
|
||||||
j.append(balanceChange{
|
|
||||||
account: addr,
|
|
||||||
prev: previous.Clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) setCode(address common.Address) {
|
|
||||||
j.append(codeChange{account: address})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) nonceChange(address common.Address, prev uint64) {
|
|
||||||
j.append(nonceChange{
|
|
||||||
account: address,
|
|
||||||
prev: prev,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) touchChange(address common.Address) {
|
|
||||||
j.append(touchChange{
|
|
||||||
account: address,
|
|
||||||
})
|
|
||||||
if address == ripemd {
|
|
||||||
// Explicitly put it in the dirty-cache, which is otherwise generated from
|
|
||||||
// flattened journals.
|
|
||||||
j.dirty(address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) accessListAddAccount(addr common.Address) {
|
|
||||||
j.append(accessListAddAccountChange{addr})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *journal) accessListAddSlot(addr common.Address, slot common.Hash) {
|
|
||||||
j.append(accessListAddSlotChange{
|
|
||||||
address: addr,
|
|
||||||
slot: slot,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Changes to the account trie.
|
|
||||||
createObjectChange struct {
|
|
||||||
account common.Address
|
|
||||||
}
|
|
||||||
// createContractChange represents an account becoming a contract-account.
|
|
||||||
// This event happens prior to executing initcode. The journal-event simply
|
|
||||||
// manages the created-flag, in order to allow same-tx destruction.
|
|
||||||
createContractChange struct {
|
|
||||||
account common.Address
|
|
||||||
}
|
|
||||||
selfDestructChange struct {
|
|
||||||
account common.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes to individual accounts.
|
|
||||||
balanceChange struct {
|
|
||||||
account common.Address
|
|
||||||
prev *uint256.Int
|
|
||||||
}
|
|
||||||
nonceChange struct {
|
|
||||||
account common.Address
|
|
||||||
prev uint64
|
|
||||||
}
|
|
||||||
storageChange struct {
|
|
||||||
account common.Address
|
|
||||||
key common.Hash
|
|
||||||
prevvalue common.Hash
|
|
||||||
origvalue common.Hash
|
|
||||||
}
|
|
||||||
codeChange struct {
|
|
||||||
account common.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes to other state values.
|
|
||||||
refundChange struct {
|
|
||||||
prev uint64
|
|
||||||
}
|
|
||||||
addLogChange struct {
|
|
||||||
txhash common.Hash
|
|
||||||
}
|
|
||||||
touchChange struct {
|
|
||||||
account common.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes to the access list
|
|
||||||
accessListAddAccountChange struct {
|
|
||||||
address common.Address
|
|
||||||
}
|
|
||||||
accessListAddSlotChange struct {
|
|
||||||
address common.Address
|
|
||||||
slot common.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes to transient storage
|
|
||||||
transientStorageChange struct {
|
|
||||||
account common.Address
|
|
||||||
key, prevalue common.Hash
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ch createObjectChange) revert(s *StateDB) {
|
|
||||||
delete(s.stateObjects, ch.account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch createObjectChange) dirtied() *common.Address {
|
|
||||||
return &ch.account
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch createObjectChange) copy() journalEntry {
|
|
||||||
return createObjectChange{
|
|
||||||
account: ch.account,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch createContractChange) revert(s *StateDB) {
|
|
||||||
s.getStateObject(ch.account).newContract = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch createContractChange) dirtied() *common.Address {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch createContractChange) copy() journalEntry {
|
|
||||||
return createContractChange{
|
|
||||||
account: ch.account,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch selfDestructChange) revert(s *StateDB) {
|
|
||||||
obj := s.getStateObject(ch.account)
|
|
||||||
if obj != nil {
|
|
||||||
obj.selfDestructed = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch selfDestructChange) dirtied() *common.Address {
|
|
||||||
return &ch.account
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch selfDestructChange) copy() journalEntry {
|
|
||||||
return selfDestructChange{
|
|
||||||
account: ch.account,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
|
||||||
|
|
||||||
func (ch touchChange) revert(s *StateDB) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch touchChange) dirtied() *common.Address {
|
|
||||||
return &ch.account
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch touchChange) copy() journalEntry {
|
|
||||||
return touchChange{
|
|
||||||
account: ch.account,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch balanceChange) revert(s *StateDB) {
|
|
||||||
s.getStateObject(ch.account).setBalance(ch.prev)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch balanceChange) dirtied() *common.Address {
|
|
||||||
return &ch.account
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch balanceChange) copy() journalEntry {
|
|
||||||
return balanceChange{
|
|
||||||
account: ch.account,
|
|
||||||
prev: new(uint256.Int).Set(ch.prev),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch nonceChange) revert(s *StateDB) {
|
|
||||||
s.getStateObject(ch.account).setNonce(ch.prev)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch nonceChange) dirtied() *common.Address {
|
|
||||||
return &ch.account
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch nonceChange) copy() journalEntry {
|
|
||||||
return nonceChange{
|
|
||||||
account: ch.account,
|
|
||||||
prev: ch.prev,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch codeChange) revert(s *StateDB) {
|
|
||||||
s.getStateObject(ch.account).setCode(types.EmptyCodeHash, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch codeChange) dirtied() *common.Address {
|
|
||||||
return &ch.account
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch codeChange) copy() journalEntry {
|
|
||||||
return codeChange{account: ch.account}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch storageChange) revert(s *StateDB) {
|
|
||||||
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch storageChange) dirtied() *common.Address {
|
|
||||||
return &ch.account
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch storageChange) copy() journalEntry {
|
|
||||||
return storageChange{
|
|
||||||
account: ch.account,
|
|
||||||
key: ch.key,
|
|
||||||
prevvalue: ch.prevvalue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch transientStorageChange) revert(s *StateDB) {
|
|
||||||
s.setTransientState(ch.account, ch.key, ch.prevalue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch transientStorageChange) dirtied() *common.Address {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch transientStorageChange) copy() journalEntry {
|
|
||||||
return transientStorageChange{
|
|
||||||
account: ch.account,
|
|
||||||
key: ch.key,
|
|
||||||
prevalue: ch.prevalue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch refundChange) revert(s *StateDB) {
|
|
||||||
s.refund = ch.prev
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch refundChange) dirtied() *common.Address {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch refundChange) copy() journalEntry {
|
|
||||||
return refundChange{
|
|
||||||
prev: ch.prev,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch addLogChange) revert(s *StateDB) {
|
|
||||||
logs := s.logs[ch.txhash]
|
|
||||||
if len(logs) == 1 {
|
|
||||||
delete(s.logs, ch.txhash)
|
|
||||||
} else {
|
|
||||||
s.logs[ch.txhash] = logs[:len(logs)-1]
|
|
||||||
}
|
|
||||||
s.logSize--
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch addLogChange) dirtied() *common.Address {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch addLogChange) copy() journalEntry {
|
|
||||||
return addLogChange{
|
|
||||||
txhash: ch.txhash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) revert(s *StateDB) {
|
|
||||||
/*
|
|
||||||
One important invariant here, is that whenever a (addr, slot) is added, if the
|
|
||||||
addr is not already present, the add causes two journal entries:
|
|
||||||
- one for the address,
|
|
||||||
- one for the (address,slot)
|
|
||||||
Therefore, when unrolling the change, we can always blindly delete the
|
|
||||||
(addr) at this point, since no storage adds can remain when come upon
|
|
||||||
a single (addr) change.
|
|
||||||
*/
|
|
||||||
s.accessList.DeleteAddress(ch.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) dirtied() *common.Address {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) copy() journalEntry {
|
|
||||||
return accessListAddAccountChange{
|
|
||||||
address: ch.address,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) revert(s *StateDB) {
|
|
||||||
s.accessList.DeleteSlot(ch.address, ch.slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) dirtied() *common.Address {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) copy() journalEntry {
|
|
||||||
return accessListAddSlotChange{
|
|
||||||
address: ch.address,
|
|
||||||
slot: ch.slot,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,515 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
)
|
||||||
|
|
||||||
|
// journalEntry is a modification entry in the state change linear journal that can be
|
||||||
|
// reverted on demand.
|
||||||
|
type journalEntry interface {
|
||||||
|
// revert undoes the changes introduced by this entry.
|
||||||
|
revert(*StateDB)
|
||||||
|
|
||||||
|
// dirtied returns the Ethereum address modified by this entry.
|
||||||
|
dirtied() *common.Address
|
||||||
|
|
||||||
|
// copy returns a deep-copied entry.
|
||||||
|
copy() journalEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// linearJournal contains the list of state modifications applied since the last state
|
||||||
|
// commit. These are tracked to be able to be reverted in the case of an execution
|
||||||
|
// exception or request for reversal.
|
||||||
|
type linearJournal struct {
|
||||||
|
entries []journalEntry // Current changes tracked by the linearJournal
|
||||||
|
dirties map[common.Address]int // Dirty accounts and the number of changes
|
||||||
|
|
||||||
|
revisions []int // sequence of indexes to points in time designating snapshots
|
||||||
|
}
|
||||||
|
|
||||||
|
// compile-time interface check
|
||||||
|
var _ journal = (*linearJournal)(nil)
|
||||||
|
|
||||||
|
// newLinearJournal creates a new initialized linearJournal.
|
||||||
|
func newLinearJournal() *linearJournal {
|
||||||
|
s := &linearJournal{
|
||||||
|
dirties: make(map[common.Address]int),
|
||||||
|
}
|
||||||
|
s.snapshot() // create snaphot zero
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset clears the journal, after this operation the journal can be used anew.
|
||||||
|
// It is semantically similar to calling 'newJournal', but the underlying slices
|
||||||
|
// can be reused.
|
||||||
|
func (j *linearJournal) reset() {
|
||||||
|
j.entries = j.entries[:0]
|
||||||
|
j.revisions = j.revisions[:0]
|
||||||
|
clear(j.dirties)
|
||||||
|
j.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j linearJournal) dirtyAccounts() []common.Address {
|
||||||
|
dirty := make([]common.Address, 0, len(j.dirties))
|
||||||
|
// flatten into list
|
||||||
|
for addr := range j.dirties {
|
||||||
|
dirty = append(dirty, addr)
|
||||||
|
}
|
||||||
|
return dirty
|
||||||
|
}
|
||||||
|
|
||||||
|
// snapshot starts a new journal scope which can be reverted or discarded.
|
||||||
|
func (j *linearJournal) snapshot() {
|
||||||
|
j.revisions = append(j.revisions, len(j.entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
// revertSnapshot reverts all state changes made since the last call to snapshot().
|
||||||
|
func (j *linearJournal) revertSnapshot(s *StateDB) {
|
||||||
|
id := len(j.revisions) - 1
|
||||||
|
if id < 0 {
|
||||||
|
j.snapshot()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
revision := j.revisions[id]
|
||||||
|
// Replay the linearJournal to undo changes and remove invalidated snapshots
|
||||||
|
j.revertTo(s, revision)
|
||||||
|
j.revisions = j.revisions[:id]
|
||||||
|
if id == 0 {
|
||||||
|
j.snapshot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardSnapshot removes the latest snapshot; after calling this
|
||||||
|
// method, it is no longer possible to revert to that particular snapshot, the
|
||||||
|
// changes are considered part of the parent scope.
|
||||||
|
func (j *linearJournal) discardSnapshot() {
|
||||||
|
id := len(j.revisions) - 1
|
||||||
|
if id <= 0 {
|
||||||
|
// If a transaction is applied successfully, the statedb.Finalize will
|
||||||
|
// end by clearing and resetting the journal. Invoking a discardSnapshot
|
||||||
|
// afterwards will land here: calling discard on an empty journal.
|
||||||
|
// This is fine
|
||||||
|
return
|
||||||
|
}
|
||||||
|
j.revisions = j.revisions[:id]
|
||||||
|
}
|
||||||
|
|
||||||
|
// append inserts a new modification entry to the end of the change linearJournal.
|
||||||
|
func (j *linearJournal) append(entry journalEntry) {
|
||||||
|
j.entries = append(j.entries, entry)
|
||||||
|
if addr := entry.dirtied(); addr != nil {
|
||||||
|
j.dirties[*addr]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// revert undoes a batch of journalled modifications along with any reverted
|
||||||
|
// dirty handling too.
|
||||||
|
func (j *linearJournal) revertTo(statedb *StateDB, snapshot int) {
|
||||||
|
for i := len(j.entries) - 1; i >= snapshot; i-- {
|
||||||
|
// Undo the changes made by the operation
|
||||||
|
j.entries[i].revert(statedb)
|
||||||
|
|
||||||
|
// Drop any dirty tracking induced by the change
|
||||||
|
if addr := j.entries[i].dirtied(); addr != nil {
|
||||||
|
if j.dirties[*addr]--; j.dirties[*addr] == 0 {
|
||||||
|
delete(j.dirties, *addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j.entries = j.entries[:snapshot]
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirty explicitly sets an address to dirty, even if the change entries would
|
||||||
|
// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD
|
||||||
|
// precompile consensus exception.
|
||||||
|
func (j *linearJournal) dirty(addr common.Address) {
|
||||||
|
j.dirties[addr]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// length returns the current number of entries in the linearJournal.
|
||||||
|
func (j *linearJournal) length() int {
|
||||||
|
return len(j.entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy returns a deep-copied journal.
|
||||||
|
func (j *linearJournal) copy() journal {
|
||||||
|
entries := make([]journalEntry, 0, j.length())
|
||||||
|
for i := 0; i < j.length(); i++ {
|
||||||
|
entries = append(entries, j.entries[i].copy())
|
||||||
|
}
|
||||||
|
return &linearJournal{
|
||||||
|
entries: entries,
|
||||||
|
dirties: maps.Clone(j.dirties),
|
||||||
|
revisions: slices.Clone(j.revisions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) logChange(txHash common.Hash) {
|
||||||
|
j.append(addLogChange{txhash: txHash})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) createObject(addr common.Address) {
|
||||||
|
j.append(createObjectChange{account: addr})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) createContract(addr common.Address, account *types.StateAccount) {
|
||||||
|
j.append(createContractChange{account: addr})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) destruct(addr common.Address, account *types.StateAccount) {
|
||||||
|
j.append(selfDestructChange{account: addr})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) storageChange(addr common.Address, key, prev, origin common.Hash) {
|
||||||
|
j.append(storageChange{
|
||||||
|
account: addr,
|
||||||
|
key: key,
|
||||||
|
prevvalue: prev,
|
||||||
|
origvalue: origin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) transientStateChange(addr common.Address, key, prev common.Hash) {
|
||||||
|
j.append(transientStorageChange{
|
||||||
|
account: addr,
|
||||||
|
key: key,
|
||||||
|
prevalue: prev,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) refundChange(previous uint64) {
|
||||||
|
j.append(refundChange{prev: previous})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) balanceChange(addr common.Address, account *types.StateAccount, destructed, newContract bool) {
|
||||||
|
j.append(balanceChange{
|
||||||
|
account: addr,
|
||||||
|
prev: account.Balance.Clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) setCode(address common.Address, account *types.StateAccount) {
|
||||||
|
j.append(codeChange{account: address})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) nonceChange(address common.Address, account *types.StateAccount, destructed, newContract bool) {
|
||||||
|
j.append(nonceChange{
|
||||||
|
account: address,
|
||||||
|
prev: account.Nonce,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) touchChange(address common.Address, account *types.StateAccount, destructed, newContract bool) {
|
||||||
|
j.append(touchChange{
|
||||||
|
account: address,
|
||||||
|
})
|
||||||
|
if address == ripemd {
|
||||||
|
// Explicitly put it in the dirty-cache, which is otherwise generated from
|
||||||
|
// flattened journals.
|
||||||
|
j.dirty(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) accessListAddAccount(addr common.Address) {
|
||||||
|
j.append(accessListAddAccountChange{addr})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *linearJournal) accessListAddSlot(addr common.Address, slot common.Hash) {
|
||||||
|
j.append(accessListAddSlotChange{
|
||||||
|
address: addr,
|
||||||
|
slot: slot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Changes to the account trie.
|
||||||
|
createObjectChange struct {
|
||||||
|
account common.Address
|
||||||
|
}
|
||||||
|
// createContractChange represents an account becoming a contract-account.
|
||||||
|
// This event happens prior to executing initcode. The linearJournal-event simply
|
||||||
|
// manages the created-flag, in order to allow same-tx destruction.
|
||||||
|
createContractChange struct {
|
||||||
|
account common.Address
|
||||||
|
}
|
||||||
|
selfDestructChange struct {
|
||||||
|
account common.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes to individual accounts.
|
||||||
|
balanceChange struct {
|
||||||
|
account common.Address
|
||||||
|
prev *uint256.Int
|
||||||
|
}
|
||||||
|
nonceChange struct {
|
||||||
|
account common.Address
|
||||||
|
prev uint64
|
||||||
|
}
|
||||||
|
storageChange struct {
|
||||||
|
account common.Address
|
||||||
|
key common.Hash
|
||||||
|
prevvalue common.Hash
|
||||||
|
origvalue common.Hash
|
||||||
|
}
|
||||||
|
codeChange struct {
|
||||||
|
account common.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes to other state values.
|
||||||
|
refundChange struct {
|
||||||
|
prev uint64
|
||||||
|
}
|
||||||
|
addLogChange struct {
|
||||||
|
txhash common.Hash
|
||||||
|
}
|
||||||
|
touchChange struct {
|
||||||
|
account common.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes to the access list
|
||||||
|
accessListAddAccountChange struct {
|
||||||
|
address common.Address
|
||||||
|
}
|
||||||
|
accessListAddSlotChange struct {
|
||||||
|
address common.Address
|
||||||
|
slot common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes to transient storage
|
||||||
|
transientStorageChange struct {
|
||||||
|
account common.Address
|
||||||
|
key, prevalue common.Hash
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ch createObjectChange) revert(s *StateDB) {
|
||||||
|
delete(s.stateObjects, ch.account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch createObjectChange) dirtied() *common.Address {
|
||||||
|
return &ch.account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch createObjectChange) copy() journalEntry {
|
||||||
|
return createObjectChange{
|
||||||
|
account: ch.account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch createContractChange) revert(s *StateDB) {
|
||||||
|
s.getStateObject(ch.account).newContract = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch createContractChange) dirtied() *common.Address {
|
||||||
|
// This method returns nil, since the transformation from non-contract to
|
||||||
|
// contract is not an operation which has an effect on the trie:
|
||||||
|
// it does not make the account part of the dirty-set.
|
||||||
|
// Creating the account (createObject) or setting the code (setCode)
|
||||||
|
// however, do, and are.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch createContractChange) copy() journalEntry {
|
||||||
|
return createContractChange{
|
||||||
|
account: ch.account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch selfDestructChange) revert(s *StateDB) {
|
||||||
|
obj := s.getStateObject(ch.account)
|
||||||
|
if obj != nil {
|
||||||
|
obj.selfDestructed = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch selfDestructChange) dirtied() *common.Address {
|
||||||
|
return &ch.account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch selfDestructChange) copy() journalEntry {
|
||||||
|
return selfDestructChange{
|
||||||
|
account: ch.account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
||||||
|
|
||||||
|
func (ch touchChange) revert(s *StateDB) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch touchChange) dirtied() *common.Address {
|
||||||
|
return &ch.account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch touchChange) copy() journalEntry {
|
||||||
|
return touchChange{
|
||||||
|
account: ch.account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch balanceChange) revert(s *StateDB) {
|
||||||
|
s.getStateObject(ch.account).setBalance(ch.prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch balanceChange) dirtied() *common.Address {
|
||||||
|
return &ch.account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch balanceChange) copy() journalEntry {
|
||||||
|
return balanceChange{
|
||||||
|
account: ch.account,
|
||||||
|
prev: new(uint256.Int).Set(ch.prev),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch nonceChange) revert(s *StateDB) {
|
||||||
|
s.getStateObject(ch.account).setNonce(ch.prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch nonceChange) dirtied() *common.Address {
|
||||||
|
return &ch.account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch nonceChange) copy() journalEntry {
|
||||||
|
return nonceChange{
|
||||||
|
account: ch.account,
|
||||||
|
prev: ch.prev,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch codeChange) revert(s *StateDB) {
|
||||||
|
s.getStateObject(ch.account).setCode(types.EmptyCodeHash, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch codeChange) dirtied() *common.Address {
|
||||||
|
return &ch.account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch codeChange) copy() journalEntry {
|
||||||
|
return codeChange{account: ch.account}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch storageChange) revert(s *StateDB) {
|
||||||
|
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch storageChange) dirtied() *common.Address {
|
||||||
|
return &ch.account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch storageChange) copy() journalEntry {
|
||||||
|
return storageChange{
|
||||||
|
account: ch.account,
|
||||||
|
key: ch.key,
|
||||||
|
prevvalue: ch.prevvalue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch transientStorageChange) revert(s *StateDB) {
|
||||||
|
s.setTransientState(ch.account, ch.key, ch.prevalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch transientStorageChange) dirtied() *common.Address {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch transientStorageChange) copy() journalEntry {
|
||||||
|
return transientStorageChange{
|
||||||
|
account: ch.account,
|
||||||
|
key: ch.key,
|
||||||
|
prevalue: ch.prevalue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch refundChange) revert(s *StateDB) {
|
||||||
|
s.refund = ch.prev
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch refundChange) dirtied() *common.Address {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch refundChange) copy() journalEntry {
|
||||||
|
return refundChange{
|
||||||
|
prev: ch.prev,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch addLogChange) revert(s *StateDB) {
|
||||||
|
logs := s.logs[ch.txhash]
|
||||||
|
if len(logs) == 1 {
|
||||||
|
delete(s.logs, ch.txhash)
|
||||||
|
} else {
|
||||||
|
s.logs[ch.txhash] = logs[:len(logs)-1]
|
||||||
|
}
|
||||||
|
s.logSize--
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch addLogChange) dirtied() *common.Address {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch addLogChange) copy() journalEntry {
|
||||||
|
return addLogChange{
|
||||||
|
txhash: ch.txhash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddAccountChange) revert(s *StateDB) {
|
||||||
|
/*
|
||||||
|
One important invariant here, is that whenever a (addr, slot) is added, if the
|
||||||
|
addr is not already present, the add causes two linearJournal entries:
|
||||||
|
- one for the address,
|
||||||
|
- one for the (address,slot)
|
||||||
|
Therefore, when unrolling the change, we can always blindly delete the
|
||||||
|
(addr) at this point, since no storage adds can remain when come upon
|
||||||
|
a single (addr) change.
|
||||||
|
*/
|
||||||
|
s.accessList.DeleteAddress(ch.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddAccountChange) dirtied() *common.Address {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddAccountChange) copy() journalEntry {
|
||||||
|
return accessListAddAccountChange{
|
||||||
|
address: ch.address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddSlotChange) revert(s *StateDB) {
|
||||||
|
s.accessList.DeleteSlot(ch.address, ch.slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddSlotChange) dirtied() *common.Address {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddSlotChange) copy() journalEntry {
|
||||||
|
return accessListAddSlotChange{
|
||||||
|
address: ch.address,
|
||||||
|
slot: ch.slot,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,475 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ journal = (*sparseJournal)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// journalAccount represents the 'journable state' of a types.Account.
|
||||||
|
// Which means, all the normal fields except storage root, but also with a
|
||||||
|
// destruction-flag.
|
||||||
|
type journalAccount struct {
|
||||||
|
nonce uint64
|
||||||
|
balance uint256.Int
|
||||||
|
codeHash []byte // nil == emptyCodeHAsh
|
||||||
|
destructed bool
|
||||||
|
newContract bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type addrSlot struct {
|
||||||
|
addr common.Address
|
||||||
|
slot common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
type doubleHash struct {
|
||||||
|
origin common.Hash
|
||||||
|
prev common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// scopedJournal represents all changes within a single callscope. These changes
|
||||||
|
// are either all reverted, or all committed -- they cannot be partially applied.
|
||||||
|
type scopedJournal struct {
|
||||||
|
accountChanges map[common.Address]*journalAccount
|
||||||
|
refund int64
|
||||||
|
logs []common.Hash
|
||||||
|
|
||||||
|
accessListAddresses []common.Address
|
||||||
|
accessListAddrSlots []addrSlot
|
||||||
|
|
||||||
|
storageChanges map[common.Address]map[common.Hash]doubleHash
|
||||||
|
tStorageChanges map[common.Address]map[common.Hash]common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func newScopedJournal() *scopedJournal {
|
||||||
|
return &scopedJournal{
|
||||||
|
refund: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) deepCopy() *scopedJournal {
|
||||||
|
var cpy = &scopedJournal{
|
||||||
|
// The accountChanges copy will copy the pointers to
|
||||||
|
// journalAccount objects: thus not actually deep copy those
|
||||||
|
// objects. That is fine: we never mutate journalAccount.
|
||||||
|
accountChanges: maps.Clone(j.accountChanges),
|
||||||
|
refund: j.refund,
|
||||||
|
logs: slices.Clone(j.logs),
|
||||||
|
accessListAddresses: slices.Clone(j.accessListAddresses),
|
||||||
|
accessListAddrSlots: slices.Clone(j.accessListAddrSlots),
|
||||||
|
}
|
||||||
|
if j.storageChanges != nil {
|
||||||
|
cpy.storageChanges = make(map[common.Address]map[common.Hash]doubleHash)
|
||||||
|
for addr, changes := range j.storageChanges {
|
||||||
|
cpy.storageChanges[addr] = maps.Clone(changes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if j.tStorageChanges != nil {
|
||||||
|
cpy.tStorageChanges = make(map[common.Address]map[common.Hash]common.Hash)
|
||||||
|
for addr, changes := range j.tStorageChanges {
|
||||||
|
cpy.tStorageChanges[addr] = maps.Clone(changes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) journalRefundChange(prev uint64) {
|
||||||
|
if j.refund == -1 {
|
||||||
|
// We convert from uint64 to int64 here, so that we can use -1
|
||||||
|
// to represent "no previous value set".
|
||||||
|
// Treating refund as int64 is fine, there's no possibility for
|
||||||
|
// refund to ever exceed maxInt64.
|
||||||
|
j.refund = int64(prev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// journalAccountChange is the common shared implementation for all account-changes.
|
||||||
|
// These changes all fall back to this method:
|
||||||
|
// - balance change
|
||||||
|
// - nonce change
|
||||||
|
// - destruct-change
|
||||||
|
// - code change
|
||||||
|
// - touch change
|
||||||
|
// - creation change (in this case, the account is nil)
|
||||||
|
func (j *scopedJournal) journalAccountChange(address common.Address, account *types.StateAccount, destructed, newContract bool) {
|
||||||
|
if j.accountChanges == nil {
|
||||||
|
j.accountChanges = make(map[common.Address]*journalAccount)
|
||||||
|
}
|
||||||
|
// If the account has already been journalled, we're done here
|
||||||
|
if _, ok := j.accountChanges[address]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if account == nil {
|
||||||
|
j.accountChanges[address] = nil // created now, previously non-existent
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ja := &journalAccount{
|
||||||
|
nonce: account.Nonce,
|
||||||
|
balance: *account.Balance,
|
||||||
|
destructed: destructed,
|
||||||
|
newContract: newContract,
|
||||||
|
}
|
||||||
|
if !bytes.Equal(account.CodeHash, types.EmptyCodeHash[:]) {
|
||||||
|
ja.codeHash = account.CodeHash
|
||||||
|
}
|
||||||
|
j.accountChanges[address] = ja
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) journalLog(txHash common.Hash) {
|
||||||
|
j.logs = append(j.logs, txHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) journalAccessListAddAccount(addr common.Address) {
|
||||||
|
j.accessListAddresses = append(j.accessListAddresses, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) journalAccessListAddSlot(addr common.Address, slot common.Hash) {
|
||||||
|
j.accessListAddrSlots = append(j.accessListAddrSlots, addrSlot{addr, slot})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) journalSetState(addr common.Address, key, prev, origin common.Hash) {
|
||||||
|
if j.storageChanges == nil {
|
||||||
|
j.storageChanges = make(map[common.Address]map[common.Hash]doubleHash)
|
||||||
|
}
|
||||||
|
changes, ok := j.storageChanges[addr]
|
||||||
|
if !ok {
|
||||||
|
changes = make(map[common.Hash]doubleHash)
|
||||||
|
j.storageChanges[addr] = changes
|
||||||
|
}
|
||||||
|
// Do not overwrite a previous value!
|
||||||
|
if _, ok := changes[key]; !ok {
|
||||||
|
changes[key] = doubleHash{origin: origin, prev: prev}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) journalSetTransientState(addr common.Address, key, prev common.Hash) {
|
||||||
|
if j.tStorageChanges == nil {
|
||||||
|
j.tStorageChanges = make(map[common.Address]map[common.Hash]common.Hash)
|
||||||
|
}
|
||||||
|
changes, ok := j.tStorageChanges[addr]
|
||||||
|
if !ok {
|
||||||
|
changes = make(map[common.Hash]common.Hash)
|
||||||
|
j.tStorageChanges[addr] = changes
|
||||||
|
}
|
||||||
|
// Do not overwrite a previous value!
|
||||||
|
if _, ok := changes[key]; !ok {
|
||||||
|
changes[key] = prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) revert(s *StateDB) {
|
||||||
|
// Revert refund
|
||||||
|
if j.refund != -1 {
|
||||||
|
s.refund = uint64(j.refund)
|
||||||
|
}
|
||||||
|
// Revert storage changes
|
||||||
|
for addr, changes := range j.storageChanges {
|
||||||
|
obj := s.getStateObject(addr)
|
||||||
|
for key, val := range changes {
|
||||||
|
obj.setState(key, val.prev, val.origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Revert t-store changes
|
||||||
|
for addr, changes := range j.tStorageChanges {
|
||||||
|
for key, val := range changes {
|
||||||
|
s.setTransientState(addr, key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revert changes to accounts
|
||||||
|
for addr, data := range j.accountChanges {
|
||||||
|
if data == nil { // Reverting a create
|
||||||
|
delete(s.stateObjects, addr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obj := s.getStateObject(addr)
|
||||||
|
obj.setNonce(data.nonce)
|
||||||
|
// Setting 'code' to nil means it will be loaded from disk
|
||||||
|
// next time it is needed. We avoid nilling it unless required
|
||||||
|
journalHash := data.codeHash
|
||||||
|
if data.codeHash == nil {
|
||||||
|
if !bytes.Equal(obj.CodeHash(), types.EmptyCodeHash[:]) {
|
||||||
|
obj.setCode(types.EmptyCodeHash, nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !bytes.Equal(obj.CodeHash(), journalHash) {
|
||||||
|
obj.setCode(common.BytesToHash(data.codeHash), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.setBalance(&data.balance)
|
||||||
|
obj.selfDestructed = data.destructed
|
||||||
|
obj.newContract = data.newContract
|
||||||
|
}
|
||||||
|
// Revert logs
|
||||||
|
for _, txhash := range j.logs {
|
||||||
|
logs := s.logs[txhash]
|
||||||
|
if len(logs) == 1 {
|
||||||
|
delete(s.logs, txhash)
|
||||||
|
} else {
|
||||||
|
s.logs[txhash] = logs[:len(logs)-1]
|
||||||
|
}
|
||||||
|
s.logSize--
|
||||||
|
}
|
||||||
|
// Revert access list additions
|
||||||
|
for i := len(j.accessListAddrSlots) - 1; i >= 0; i-- {
|
||||||
|
item := j.accessListAddrSlots[i]
|
||||||
|
s.accessList.DeleteSlot(item.addr, item.slot)
|
||||||
|
}
|
||||||
|
for i := len(j.accessListAddresses) - 1; i >= 0; i-- {
|
||||||
|
s.accessList.DeleteAddress(j.accessListAddresses[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) merge(parent *scopedJournal) {
|
||||||
|
if parent.refund == -1 {
|
||||||
|
parent.refund = j.refund
|
||||||
|
}
|
||||||
|
// Merge changes to accounts
|
||||||
|
if parent.accountChanges == nil {
|
||||||
|
parent.accountChanges = j.accountChanges
|
||||||
|
} else {
|
||||||
|
for addr, data := range j.accountChanges {
|
||||||
|
if _, present := parent.accountChanges[addr]; present {
|
||||||
|
// Nothing to do here, it's already stored in parent scope
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parent.accountChanges[addr] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Merge logs
|
||||||
|
parent.logs = append(parent.logs, j.logs...)
|
||||||
|
|
||||||
|
// Merge access list additions
|
||||||
|
parent.accessListAddrSlots = append(parent.accessListAddrSlots, j.accessListAddrSlots...)
|
||||||
|
parent.accessListAddresses = append(parent.accessListAddresses, j.accessListAddresses...)
|
||||||
|
|
||||||
|
if parent.storageChanges == nil {
|
||||||
|
parent.storageChanges = j.storageChanges
|
||||||
|
} else {
|
||||||
|
// Merge storage changes
|
||||||
|
for addr, changes := range j.storageChanges {
|
||||||
|
prevChanges, ok := parent.storageChanges[addr]
|
||||||
|
if !ok {
|
||||||
|
parent.storageChanges[addr] = changes
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for k, v := range changes {
|
||||||
|
if _, ok := prevChanges[k]; !ok {
|
||||||
|
prevChanges[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if parent.tStorageChanges == nil {
|
||||||
|
parent.tStorageChanges = j.tStorageChanges
|
||||||
|
} else {
|
||||||
|
// Merge t-store changes
|
||||||
|
for addr, changes := range j.tStorageChanges {
|
||||||
|
prevChanges, ok := parent.tStorageChanges[addr]
|
||||||
|
if !ok {
|
||||||
|
parent.tStorageChanges[addr] = changes
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for k, v := range changes {
|
||||||
|
if _, ok := prevChanges[k]; !ok {
|
||||||
|
prevChanges[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *scopedJournal) addDirtyAccounts(set map[common.Address]any) {
|
||||||
|
// Changes due to account changes
|
||||||
|
for addr := range j.accountChanges {
|
||||||
|
set[addr] = []interface{}{}
|
||||||
|
}
|
||||||
|
// Changes due to storage changes
|
||||||
|
for addr := range j.storageChanges {
|
||||||
|
set[addr] = []interface{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sparseJournal contains the list of state modifications applied since the last state
|
||||||
|
// commit. These are tracked to be able to be reverted in the case of an execution
|
||||||
|
// exception or request for reversal.
|
||||||
|
type sparseJournal struct {
|
||||||
|
entries []*scopedJournal // Current changes tracked by the journal
|
||||||
|
ripeMagic bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newJournal creates a new initialized journal.
|
||||||
|
func newSparseJournal() *sparseJournal {
|
||||||
|
s := new(sparseJournal)
|
||||||
|
s.snapshot() // create snaphot zero
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset clears the journal, after this operation the journal can be used
|
||||||
|
// anew. It is semantically similar to calling 'newJournal', but the underlying
|
||||||
|
// slices can be reused
|
||||||
|
func (j *sparseJournal) reset() {
|
||||||
|
j.entries = j.entries[:0]
|
||||||
|
j.ripeMagic = false
|
||||||
|
j.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) copy() journal {
|
||||||
|
cp := &sparseJournal{
|
||||||
|
entries: make([]*scopedJournal, 0, len(j.entries)),
|
||||||
|
ripeMagic: j.ripeMagic,
|
||||||
|
}
|
||||||
|
for _, entry := range j.entries {
|
||||||
|
cp.entries = append(cp.entries, entry.deepCopy())
|
||||||
|
}
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// snapshot starts a new journal scope which can be reverted or discarded.
|
||||||
|
// OBS: A call to Snapshot is _required_ in order to initialize the journalling,
|
||||||
|
// invoking the journal-methods without having invoked Snapshot will lead to
|
||||||
|
// panic.
|
||||||
|
func (j *sparseJournal) snapshot() {
|
||||||
|
j.entries = append(j.entries, newScopedJournal())
|
||||||
|
}
|
||||||
|
|
||||||
|
// revertSnapshot reverts all state changes made since the last call to snapshot().
|
||||||
|
func (j *sparseJournal) revertSnapshot(s *StateDB) {
|
||||||
|
id := len(j.entries) - 1
|
||||||
|
j.entries[id].revert(s)
|
||||||
|
j.entries = j.entries[:id]
|
||||||
|
if id == 0 {
|
||||||
|
j.snapshot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardSnapshot removes the latest snapshot; after calling this
|
||||||
|
// method, it is no longer possible to revert to that particular snapshot, the
|
||||||
|
// changes are considered part of the parent scope.
|
||||||
|
func (j *sparseJournal) discardSnapshot() {
|
||||||
|
id := len(j.entries) - 1
|
||||||
|
// here we must merge the 'id' with it's parent.
|
||||||
|
if id == 0 {
|
||||||
|
// If a transaction is applied successfully, the statedb.Finalize will
|
||||||
|
// end by clearing and resetting the journal. Invoking a discardSnapshot
|
||||||
|
// afterwards will land here: calling discard on an empty journal.
|
||||||
|
// This is fine
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entry := j.entries[id]
|
||||||
|
parent := j.entries[id-1]
|
||||||
|
entry.merge(parent)
|
||||||
|
j.entries = j.entries[:id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) journalAccountChange(addr common.Address, account *types.StateAccount, destructed, newContract bool) {
|
||||||
|
j.entries[len(j.entries)-1].journalAccountChange(addr, account, destructed, newContract)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) nonceChange(addr common.Address, account *types.StateAccount, destructed, newContract bool) {
|
||||||
|
j.journalAccountChange(addr, account, destructed, newContract)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) balanceChange(addr common.Address, account *types.StateAccount, destructed, newContract bool) {
|
||||||
|
j.journalAccountChange(addr, account, destructed, newContract)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) setCode(addr common.Address, account *types.StateAccount) {
|
||||||
|
j.journalAccountChange(addr, account, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) createObject(addr common.Address) {
|
||||||
|
// Creating an account which is destructed, hence already exists, is not
|
||||||
|
// allowed, hence we know destructed == 'false'.
|
||||||
|
// Also, if we are creating the account now, it cannot yet be a
|
||||||
|
// newContract (that might come later)
|
||||||
|
j.journalAccountChange(addr, nil, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) createContract(addr common.Address, account *types.StateAccount) {
|
||||||
|
// Creating an account which is destructed, hence already exists, is not
|
||||||
|
// allowed, hence we know it to be 'false'.
|
||||||
|
// Also: if we create the contract now, it cannot be previously created
|
||||||
|
j.journalAccountChange(addr, account, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) destruct(addr common.Address, account *types.StateAccount) {
|
||||||
|
// destructing an already destructed account must not be journalled. Hence we
|
||||||
|
// know it to be 'false'.
|
||||||
|
// Also: if we're allowed to destruct it, it must be `newContract:true`, OR
|
||||||
|
// the concept of newContract is unused and moot.
|
||||||
|
j.journalAccountChange(addr, account, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
||||||
|
func (j *sparseJournal) touchChange(addr common.Address, account *types.StateAccount, destructed, newContract bool) {
|
||||||
|
j.journalAccountChange(addr, account, destructed, newContract)
|
||||||
|
if addr == ripemd {
|
||||||
|
// Explicitly put it in the dirty-cache one extra time. Ripe magic.
|
||||||
|
j.ripeMagic = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) logChange(txHash common.Hash) {
|
||||||
|
j.entries[len(j.entries)-1].journalLog(txHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) refundChange(prev uint64) {
|
||||||
|
j.entries[len(j.entries)-1].journalRefundChange(prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) accessListAddAccount(addr common.Address) {
|
||||||
|
j.entries[len(j.entries)-1].journalAccessListAddAccount(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) accessListAddSlot(addr common.Address, slot common.Hash) {
|
||||||
|
j.entries[len(j.entries)-1].journalAccessListAddSlot(addr, slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) storageChange(addr common.Address, key, prev, origin common.Hash) {
|
||||||
|
j.entries[len(j.entries)-1].journalSetState(addr, key, prev, origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) transientStateChange(addr common.Address, key, prev common.Hash) {
|
||||||
|
j.entries[len(j.entries)-1].journalSetTransientState(addr, key, prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *sparseJournal) dirtyAccounts() []common.Address {
|
||||||
|
// The dirty-set should encompass all layers
|
||||||
|
var dirty = make(map[common.Address]any)
|
||||||
|
for _, scope := range j.entries {
|
||||||
|
scope.addDirtyAccounts(dirty)
|
||||||
|
}
|
||||||
|
if j.ripeMagic {
|
||||||
|
dirty[ripemd] = []interface{}{}
|
||||||
|
}
|
||||||
|
var dirtyList = make([]common.Address, 0, len(dirty))
|
||||||
|
for addr := range dirty {
|
||||||
|
dirtyList = append(dirtyList, addr)
|
||||||
|
}
|
||||||
|
return dirtyList
|
||||||
|
}
|
|
@ -0,0 +1,384 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package state provides a caching layer atop the Ethereum state trie.
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLinearJournalDirty(t *testing.T) {
|
||||||
|
testJournalDirty(t, newLinearJournal())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSparseJournalDirty(t *testing.T) {
|
||||||
|
testJournalDirty(t, newSparseJournal())
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies some basics around journalling: the ability to
|
||||||
|
// deliver a dirty-set.
|
||||||
|
func testJournalDirty(t *testing.T, j journal) {
|
||||||
|
acc := &types.StateAccount{
|
||||||
|
Nonce: 1,
|
||||||
|
Balance: new(uint256.Int),
|
||||||
|
Root: common.Hash{},
|
||||||
|
CodeHash: nil,
|
||||||
|
}
|
||||||
|
{
|
||||||
|
j.nonceChange(common.Address{0x1}, acc, false, false)
|
||||||
|
if have, want := len(j.dirtyAccounts()), 1; have != want {
|
||||||
|
t.Errorf("wrong size of dirty accounts, have %v want %v", have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
j.storageChange(common.Address{0x2}, common.Hash{0x1}, common.Hash{0x1}, common.Hash{})
|
||||||
|
if have, want := len(j.dirtyAccounts()), 2; have != want {
|
||||||
|
t.Errorf("wrong size of dirty accounts, have %v want %v", have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ // The previous scopes should also be accounted for
|
||||||
|
j.snapshot()
|
||||||
|
if have, want := len(j.dirtyAccounts()), 2; have != want {
|
||||||
|
t.Errorf("wrong size of dirty accounts, have %v want %v", have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinearJournalAccessList(t *testing.T) {
|
||||||
|
testJournalAccessList(t, newLinearJournal())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSparseJournalAccessList(t *testing.T) {
|
||||||
|
testJournalAccessList(t, newSparseJournal())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testJournalAccessList(t *testing.T, j journal) {
|
||||||
|
var statedb = &StateDB{}
|
||||||
|
statedb.accessList = newAccessList()
|
||||||
|
statedb.journal = j
|
||||||
|
|
||||||
|
j.snapshot()
|
||||||
|
{
|
||||||
|
// If the journal performs the rollback in the wrong order, this
|
||||||
|
// will cause a panic.
|
||||||
|
statedb.AddSlotToAccessList(common.Address{0x1}, common.Hash{0x4})
|
||||||
|
statedb.AddSlotToAccessList(common.Address{0x3}, common.Hash{0x4})
|
||||||
|
}
|
||||||
|
statedb.RevertSnapshot()
|
||||||
|
j.snapshot()
|
||||||
|
{
|
||||||
|
statedb.AddAddressToAccessList(common.Address{0x2})
|
||||||
|
statedb.AddAddressToAccessList(common.Address{0x3})
|
||||||
|
statedb.AddAddressToAccessList(common.Address{0x4})
|
||||||
|
}
|
||||||
|
statedb.RevertSnapshot()
|
||||||
|
if statedb.accessList.ContainsAddress(common.Address{0x2}) {
|
||||||
|
t.Fatal("should be missing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinearJournalRefunds(t *testing.T) {
|
||||||
|
testJournalRefunds(t, newLinearJournal())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSparseJournalRefunds(t *testing.T) {
|
||||||
|
testJournalRefunds(t, newSparseJournal())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testJournalRefunds(t *testing.T, j journal) {
|
||||||
|
var statedb = &StateDB{}
|
||||||
|
statedb.accessList = newAccessList()
|
||||||
|
statedb.journal = j
|
||||||
|
j.snapshot()
|
||||||
|
{
|
||||||
|
j.refundChange(0)
|
||||||
|
j.refundChange(1)
|
||||||
|
j.snapshot()
|
||||||
|
{
|
||||||
|
j.refundChange(2)
|
||||||
|
j.refundChange(3)
|
||||||
|
}
|
||||||
|
j.revertSnapshot(statedb)
|
||||||
|
if have, want := statedb.refund, uint64(2); have != want {
|
||||||
|
t.Fatalf("have %d want %d", have, want)
|
||||||
|
}
|
||||||
|
j.snapshot()
|
||||||
|
{
|
||||||
|
j.refundChange(2)
|
||||||
|
j.refundChange(3)
|
||||||
|
}
|
||||||
|
j.discardSnapshot()
|
||||||
|
}
|
||||||
|
j.revertSnapshot(statedb)
|
||||||
|
if have, want := statedb.refund, uint64(0); have != want {
|
||||||
|
t.Fatalf("have %d want %d", have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fuzzReader struct {
|
||||||
|
input io.Reader
|
||||||
|
exhausted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fuzzReader) byte() byte {
|
||||||
|
return f.bytes(1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fuzzReader) bytes(n int) []byte {
|
||||||
|
r := make([]byte, n)
|
||||||
|
if _, err := f.input.Read(r); err != nil {
|
||||||
|
f.exhausted = true
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEmptyState() *StateDB {
|
||||||
|
s, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// fuzzJournals is pretty similar to `TestSnapshotRandom`/ `newTestAction` in
|
||||||
|
// statedb_test.go. They both execute a sequence of state-actions, however, they
|
||||||
|
// test for different aspects.
|
||||||
|
// This test compares two differing journal-implementations.
|
||||||
|
// The other test compares every point in time, whether it is identical when going
|
||||||
|
// forward as when going backwards through the journal entries.
|
||||||
|
func fuzzJournals(t *testing.T, data []byte) {
|
||||||
|
var (
|
||||||
|
reader = fuzzReader{input: bytes.NewReader(data)}
|
||||||
|
stateDbs = []*StateDB{
|
||||||
|
newEmptyState(),
|
||||||
|
newEmptyState(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
apply := func(action func(stateDbs *StateDB)) {
|
||||||
|
for _, sdb := range stateDbs {
|
||||||
|
action(sdb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stateDbs[0].journal = newLinearJournal()
|
||||||
|
stateDbs[1].journal = newSparseJournal()
|
||||||
|
|
||||||
|
for !reader.exhausted {
|
||||||
|
op := reader.byte() % 18
|
||||||
|
switch op {
|
||||||
|
case 0: // Add account to access lists
|
||||||
|
addr := common.BytesToAddress(reader.bytes(1))
|
||||||
|
t.Logf("Op %d: Add to access list %#x", op, addr)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.accessList.AddAddress(addr)
|
||||||
|
})
|
||||||
|
case 1: // Add slot to access list
|
||||||
|
addr := common.BytesToAddress(reader.bytes(1))
|
||||||
|
slot := common.BytesToHash(reader.bytes(1))
|
||||||
|
t.Logf("Op %d: Add addr:slot to access list %#x : %#x", op, addr, slot)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.AddSlotToAccessList(addr, slot)
|
||||||
|
})
|
||||||
|
case 2:
|
||||||
|
var (
|
||||||
|
addr = common.BytesToAddress(reader.bytes(1))
|
||||||
|
value = uint64(reader.byte())
|
||||||
|
)
|
||||||
|
t.Logf("Op %d: Add balance %#x %d", op, addr, value)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.AddBalance(addr, uint256.NewInt(value), 0)
|
||||||
|
})
|
||||||
|
case 3:
|
||||||
|
t.Logf("Op %d: Copy journals[0]", op)
|
||||||
|
stateDbs[0].journal = stateDbs[0].journal.copy()
|
||||||
|
case 4:
|
||||||
|
t.Logf("Op %d: Copy journals[1]", op)
|
||||||
|
stateDbs[1].journal = stateDbs[1].journal.copy()
|
||||||
|
case 5:
|
||||||
|
var (
|
||||||
|
addr = common.BytesToAddress(reader.bytes(1))
|
||||||
|
code = reader.bytes(2)
|
||||||
|
)
|
||||||
|
t.Logf("Op %d: (Create and) set code 0x%x", op, addr)
|
||||||
|
apply(func(s *StateDB) {
|
||||||
|
if !s.Exist(addr) {
|
||||||
|
s.CreateAccount(addr)
|
||||||
|
}
|
||||||
|
contractHash := s.GetCodeHash(addr)
|
||||||
|
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
|
||||||
|
storageRoot := s.GetStorageRoot(addr)
|
||||||
|
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
|
||||||
|
|
||||||
|
if obj := s.getStateObject(addr); obj != nil {
|
||||||
|
if obj.selfDestructed {
|
||||||
|
// If it's selfdestructed, we cannot create into it
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
|
||||||
|
s.CreateContract(addr)
|
||||||
|
// We also set some code here, to prevent the
|
||||||
|
// CreateContract action from being performed twice in a row,
|
||||||
|
// which would cause a difference in state when unrolling
|
||||||
|
// the linearJournal. (CreateContact assumes created was false prior to
|
||||||
|
// invocation, and the linearJournal rollback sets it to false).
|
||||||
|
s.SetCode(addr, code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
case 6:
|
||||||
|
addr := common.BytesToAddress(reader.bytes(1))
|
||||||
|
t.Logf("Op %d: Create 0x%x", op, addr)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
if !sdb.Exist(addr) {
|
||||||
|
sdb.CreateAccount(addr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
case 7:
|
||||||
|
addr := common.BytesToAddress(reader.bytes(1))
|
||||||
|
t.Logf("Op %d: (Create and) destruct 0x%x", op, addr)
|
||||||
|
apply(func(s *StateDB) {
|
||||||
|
if !s.Exist(addr) {
|
||||||
|
s.CreateAccount(addr)
|
||||||
|
}
|
||||||
|
s.SelfDestruct(addr)
|
||||||
|
})
|
||||||
|
case 8:
|
||||||
|
txHash := common.BytesToHash(reader.bytes(1))
|
||||||
|
t.Logf("Op %d: Add log %#x", op, txHash)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.logs[txHash] = append(sdb.logs[txHash], new(types.Log))
|
||||||
|
sdb.logSize++
|
||||||
|
sdb.journal.logChange(txHash)
|
||||||
|
})
|
||||||
|
case 9:
|
||||||
|
var (
|
||||||
|
addr = common.BytesToAddress(reader.bytes(1))
|
||||||
|
nonce = binary.BigEndian.Uint64(reader.bytes(8))
|
||||||
|
)
|
||||||
|
t.Logf("Op %d: Set nonce %#x %d", op, addr, nonce)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.SetNonce(addr, nonce)
|
||||||
|
})
|
||||||
|
case 10:
|
||||||
|
refund := uint64(reader.byte())
|
||||||
|
t.Logf("Op %d: Set refund %d", op, refund)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.journal.refundChange(refund)
|
||||||
|
})
|
||||||
|
case 11:
|
||||||
|
var (
|
||||||
|
addr = common.BytesToAddress(reader.bytes(1))
|
||||||
|
key = common.BytesToHash(reader.bytes(1))
|
||||||
|
val = common.BytesToHash(reader.bytes(1))
|
||||||
|
)
|
||||||
|
t.Logf("Op %d: Set storage %#x [%#x]=%#x", op, addr, key, val)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.SetState(addr, key, val)
|
||||||
|
})
|
||||||
|
case 12:
|
||||||
|
var (
|
||||||
|
addr = common.BytesToAddress(reader.bytes(1))
|
||||||
|
)
|
||||||
|
t.Logf("Op %d: Zero-balance transfer (touch) %#x", op, addr)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.AddBalance(addr, uint256.NewInt(0), 0)
|
||||||
|
})
|
||||||
|
case 13:
|
||||||
|
var (
|
||||||
|
addr = common.BytesToAddress(reader.bytes(1))
|
||||||
|
key = common.BytesToHash(reader.bytes(1))
|
||||||
|
value = common.BytesToHash(reader.bytes(1))
|
||||||
|
)
|
||||||
|
t.Logf("Op %d: Set t-storage %#x [%#x]=%#x", op, addr, key, value)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.SetTransientState(addr, key, value)
|
||||||
|
})
|
||||||
|
case 14:
|
||||||
|
t.Logf("Op %d: Reset journal", op)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.journal.reset()
|
||||||
|
})
|
||||||
|
case 15:
|
||||||
|
t.Logf("Op %d: Snapshot", op)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.Snapshot()
|
||||||
|
})
|
||||||
|
case 16:
|
||||||
|
t.Logf("Op %d: Discard snapshot", op)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.DiscardSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
case 17:
|
||||||
|
t.Logf("Op %d: Revert snapshot", op)
|
||||||
|
apply(func(sdb *StateDB) {
|
||||||
|
sdb.RevertSnapshot()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Cross-check the dirty-sets
|
||||||
|
accs1 := stateDbs[0].journal.dirtyAccounts()
|
||||||
|
slices.SortFunc(accs1, func(a, b common.Address) int {
|
||||||
|
return bytes.Compare(a.Bytes(), b.Bytes())
|
||||||
|
})
|
||||||
|
accs2 := stateDbs[1].journal.dirtyAccounts()
|
||||||
|
slices.SortFunc(accs2, func(a, b common.Address) int {
|
||||||
|
return bytes.Compare(a.Bytes(), b.Bytes())
|
||||||
|
})
|
||||||
|
if !slices.Equal(accs1, accs2) {
|
||||||
|
t.Fatalf("mismatched dirty-sets:\n%v\n%v", accs1, accs2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h1, err1 := stateDbs[0].Commit(0, false)
|
||||||
|
h2, err2 := stateDbs[1].Commit(0, false)
|
||||||
|
if err1 != err2 {
|
||||||
|
t.Fatalf("Mismatched errors: %v %v", err1, err2)
|
||||||
|
}
|
||||||
|
if h1 != h2 {
|
||||||
|
t.Fatalf("Mismatched roots: %v %v", h1, h2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzJournals fuzzes the journals.
|
||||||
|
func FuzzJournals(f *testing.F) {
|
||||||
|
f.Fuzz(fuzzJournals)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFuzzJournals runs 200 fuzz-tests
|
||||||
|
func TestFuzzJournals(t *testing.T) {
|
||||||
|
input := make([]byte, 200)
|
||||||
|
for i := 0; i < 200; i++ {
|
||||||
|
rand.Read(input)
|
||||||
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Logf("input: %x", input)
|
||||||
|
fuzzJournals(t, input)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFuzzJournalsSpecific can be used to test a specific input
|
||||||
|
func TestFuzzJournalsSpecific(t *testing.T) {
|
||||||
|
t.Skip("example")
|
||||||
|
input := common.FromHex("71d598d781f65eb7c047fed5d09b1e4e0c1ecad5c447a2149e7d1137fcb1b1d63f4ba6f761918a441a98eb61d69fe011cabfbce00d74bb78539ca9946a602e94d6eabc43c0924ba65ce3e171b476208059d81f33e81d90607e0b6e59d6016840b5c4e9b1a8e9798a5a40be909930658eea351d7a312dba0b1c7199c7e5f62a908a80f7faf29bc0108faae0cf0f497d0f4cd228b7600ef0d88532dfafa6349ea7782f28ad7426eeffc155282a9e58a606d25acd8a730dde61a6e5e887d1ba1fea813bb7f2c6caff25")
|
||||||
|
fuzzJournals(t, input)
|
||||||
|
}
|
|
@ -113,7 +113,7 @@ func (s *stateObject) markSelfdestructed() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stateObject) touch() {
|
func (s *stateObject) touch() {
|
||||||
s.db.journal.touchChange(s.address)
|
s.db.journal.touchChange(s.address, &s.data, s.selfDestructed, s.newContract)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTrie returns the associated storage trie. The trie will be opened if it's
|
// getTrie returns the associated storage trie. The trie will be opened if it's
|
||||||
|
@ -462,7 +462,7 @@ func (s *stateObject) AddBalance(amount *uint256.Int) uint256.Int {
|
||||||
// SetBalance sets the balance for the object, and returns the previous balance.
|
// SetBalance sets the balance for the object, and returns the previous balance.
|
||||||
func (s *stateObject) SetBalance(amount *uint256.Int) uint256.Int {
|
func (s *stateObject) SetBalance(amount *uint256.Int) uint256.Int {
|
||||||
prev := *s.data.Balance
|
prev := *s.data.Balance
|
||||||
s.db.journal.balanceChange(s.address, s.data.Balance)
|
s.db.journal.balanceChange(s.address, &s.data, s.selfDestructed, s.newContract)
|
||||||
s.setBalance(amount)
|
s.setBalance(amount)
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
@ -536,7 +536,7 @@ func (s *stateObject) CodeSize() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
|
func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
|
||||||
s.db.journal.setCode(s.address)
|
s.db.journal.setCode(s.address, &s.data)
|
||||||
s.setCode(codeHash, code)
|
s.setCode(codeHash, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,7 +547,7 @@ func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stateObject) SetNonce(nonce uint64) {
|
func (s *stateObject) SetNonce(nonce uint64) {
|
||||||
s.db.journal.nonceChange(s.address, s.data.Nonce)
|
s.db.journal.nonceChange(s.address, &s.data, s.selfDestructed, s.newContract)
|
||||||
s.setNonce(nonce)
|
s.setNonce(nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,25 +160,26 @@ func TestSnapshot(t *testing.T) {
|
||||||
s := newStateEnv()
|
s := newStateEnv()
|
||||||
|
|
||||||
// snapshot the genesis state
|
// snapshot the genesis state
|
||||||
genesis := s.state.Snapshot()
|
s.state.Snapshot()
|
||||||
|
{
|
||||||
|
// set initial state object value
|
||||||
|
s.state.SetState(stateobjaddr, storageaddr, data1)
|
||||||
|
s.state.Snapshot()
|
||||||
|
{
|
||||||
|
// set a new state object value, revert it and ensure correct content
|
||||||
|
s.state.SetState(stateobjaddr, storageaddr, data2)
|
||||||
|
}
|
||||||
|
s.state.RevertSnapshot()
|
||||||
|
|
||||||
// set initial state object value
|
if v := s.state.GetState(stateobjaddr, storageaddr); v != data1 {
|
||||||
s.state.SetState(stateobjaddr, storageaddr, data1)
|
t.Errorf("wrong storage value %v, want %v", v, data1)
|
||||||
snapshot := s.state.Snapshot()
|
}
|
||||||
|
if v := s.state.GetCommittedState(stateobjaddr, storageaddr); v != (common.Hash{}) {
|
||||||
// set a new state object value, revert it and ensure correct content
|
t.Errorf("wrong committed storage value %v, want %v", v, common.Hash{})
|
||||||
s.state.SetState(stateobjaddr, storageaddr, data2)
|
}
|
||||||
s.state.RevertToSnapshot(snapshot)
|
|
||||||
|
|
||||||
if v := s.state.GetState(stateobjaddr, storageaddr); v != data1 {
|
|
||||||
t.Errorf("wrong storage value %v, want %v", v, data1)
|
|
||||||
}
|
}
|
||||||
if v := s.state.GetCommittedState(stateobjaddr, storageaddr); v != (common.Hash{}) {
|
|
||||||
t.Errorf("wrong committed storage value %v, want %v", v, common.Hash{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// revert up to the genesis state and ensure correct content
|
// revert up to the genesis state and ensure correct content
|
||||||
s.state.RevertToSnapshot(genesis)
|
s.state.RevertSnapshot()
|
||||||
if v := s.state.GetState(stateobjaddr, storageaddr); v != (common.Hash{}) {
|
if v := s.state.GetState(stateobjaddr, storageaddr); v != (common.Hash{}) {
|
||||||
t.Errorf("wrong storage value %v, want %v", v, common.Hash{})
|
t.Errorf("wrong storage value %v, want %v", v, common.Hash{})
|
||||||
}
|
}
|
||||||
|
@ -189,22 +190,23 @@ func TestSnapshot(t *testing.T) {
|
||||||
|
|
||||||
func TestSnapshotEmpty(t *testing.T) {
|
func TestSnapshotEmpty(t *testing.T) {
|
||||||
s := newStateEnv()
|
s := newStateEnv()
|
||||||
s.state.RevertToSnapshot(s.state.Snapshot())
|
s.state.Snapshot()
|
||||||
|
s.state.RevertSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateObjectRevert(t *testing.T) {
|
func TestCreateObjectRevert(t *testing.T) {
|
||||||
state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
|
state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
|
||||||
addr := common.BytesToAddress([]byte("so0"))
|
addr := common.BytesToAddress([]byte("so0"))
|
||||||
snap := state.Snapshot()
|
state.Snapshot()
|
||||||
|
{
|
||||||
state.CreateAccount(addr)
|
state.CreateAccount(addr)
|
||||||
so0 := state.getStateObject(addr)
|
so0 := state.getStateObject(addr)
|
||||||
so0.SetBalance(uint256.NewInt(42))
|
so0.SetBalance(uint256.NewInt(42))
|
||||||
so0.SetNonce(43)
|
so0.SetNonce(43)
|
||||||
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
|
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
|
||||||
state.setStateObject(so0)
|
state.setStateObject(so0)
|
||||||
|
}
|
||||||
state.RevertToSnapshot(snap)
|
state.RevertSnapshot()
|
||||||
if state.Exist(addr) {
|
if state.Exist(addr) {
|
||||||
t.Error("Unexpected account after revert")
|
t.Error("Unexpected account after revert")
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,8 +132,8 @@ type StateDB struct {
|
||||||
transientStorage transientStorage
|
transientStorage transientStorage
|
||||||
|
|
||||||
// Journal of state modifications. This is the backbone of
|
// Journal of state modifications. This is the backbone of
|
||||||
// Snapshot and RevertToSnapshot.
|
// Snapshot and RevertSnapshot.
|
||||||
journal *journal
|
journal journal
|
||||||
|
|
||||||
// State witness if cross validation is needed
|
// State witness if cross validation is needed
|
||||||
witness *stateless.Witness
|
witness *stateless.Witness
|
||||||
|
@ -177,7 +177,7 @@ func New(root common.Hash, db Database) (*StateDB, error) {
|
||||||
mutations: make(map[common.Address]*mutation),
|
mutations: make(map[common.Address]*mutation),
|
||||||
logs: make(map[common.Hash][]*types.Log),
|
logs: make(map[common.Hash][]*types.Log),
|
||||||
preimages: make(map[common.Hash][]byte),
|
preimages: make(map[common.Hash][]byte),
|
||||||
journal: newJournal(),
|
journal: newSparseJournal(),
|
||||||
accessList: newAccessList(),
|
accessList: newAccessList(),
|
||||||
transientStorage: newTransientStorage(),
|
transientStorage: newTransientStorage(),
|
||||||
}
|
}
|
||||||
|
@ -502,7 +502,7 @@ func (s *StateDB) SelfDestruct(addr common.Address) uint256.Int {
|
||||||
// If it is already marked as self-destructed, we do not need to add it
|
// If it is already marked as self-destructed, we do not need to add it
|
||||||
// for journalling a second time.
|
// for journalling a second time.
|
||||||
if !stateObject.selfDestructed {
|
if !stateObject.selfDestructed {
|
||||||
s.journal.destruct(addr)
|
s.journal.destruct(addr, &stateObject.data)
|
||||||
stateObject.markSelfdestructed()
|
stateObject.markSelfdestructed()
|
||||||
}
|
}
|
||||||
return prevBalance
|
return prevBalance
|
||||||
|
@ -642,7 +642,7 @@ func (s *StateDB) CreateContract(addr common.Address) {
|
||||||
obj := s.getStateObject(addr)
|
obj := s.getStateObject(addr)
|
||||||
if !obj.newContract {
|
if !obj.newContract {
|
||||||
obj.newContract = true
|
obj.newContract = true
|
||||||
s.journal.createContract(addr)
|
s.journal.createContract(addr, &obj.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,14 +706,21 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns an identifier for the current revision of the state.
|
// Snapshot starts a new journalled scope.
|
||||||
func (s *StateDB) Snapshot() int {
|
func (s *StateDB) Snapshot() {
|
||||||
return s.journal.snapshot()
|
s.journal.snapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevertToSnapshot reverts all state changes made since the given revision.
|
// DiscardSnapshot removes the ability to roll back the changes in the most
|
||||||
func (s *StateDB) RevertToSnapshot(revid int) {
|
// recent journalled scope. After calling this method, the changes are considered
|
||||||
s.journal.revertToSnapshot(revid, s)
|
// part of the parent scope.
|
||||||
|
func (s *StateDB) DiscardSnapshot() {
|
||||||
|
s.journal.discardSnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevertSnapshot reverts all state changes made in the most recent journalled scope.
|
||||||
|
func (s *StateDB) RevertSnapshot() {
|
||||||
|
s.journal.revertSnapshot(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRefund returns the current value of the refund counter.
|
// GetRefund returns the current value of the refund counter.
|
||||||
|
@ -725,8 +732,9 @@ func (s *StateDB) GetRefund() uint64 {
|
||||||
// the journal as well as the refunds. Finalise, however, will not push any updates
|
// the journal as well as the refunds. Finalise, however, will not push any updates
|
||||||
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties))
|
dirties := s.journal.dirtyAccounts()
|
||||||
for addr := range s.journal.dirties {
|
addressesToPrefetch := make([]common.Address, 0, len(dirties))
|
||||||
|
for _, addr := range dirties {
|
||||||
obj, exist := s.stateObjects[addr]
|
obj, exist := s.stateObjects[addr]
|
||||||
if !exist {
|
if !exist {
|
||||||
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
|
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
|
||||||
|
|
|
@ -141,12 +141,16 @@ func (s *hookedStateDB) Prepare(rules params.Rules, sender, coinbase common.Addr
|
||||||
s.inner.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses)
|
s.inner.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) RevertToSnapshot(i int) {
|
func (s *hookedStateDB) DiscardSnapshot() {
|
||||||
s.inner.RevertToSnapshot(i)
|
s.inner.DiscardSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) Snapshot() int {
|
func (s *hookedStateDB) RevertSnapshot() {
|
||||||
return s.inner.Snapshot()
|
s.inner.RevertSnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *hookedStateDB) Snapshot() {
|
||||||
|
s.inner.Snapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) AddPreimage(hash common.Hash, bytes []byte) {
|
func (s *hookedStateDB) AddPreimage(hash common.Hash, bytes []byte) {
|
||||||
|
@ -254,7 +258,7 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
if s.hooks.OnBalanceChange == nil {
|
if s.hooks.OnBalanceChange == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for addr := range s.inner.journal.dirties {
|
for _, addr := range s.inner.journal.dirtyAccounts() {
|
||||||
obj := s.inner.stateObjects[addr]
|
obj := s.inner.stateObjects[addr]
|
||||||
if obj != nil && obj.selfDestructed {
|
if obj != nil && obj.selfDestructed {
|
||||||
// If ether was sent to account post-selfdestruct it is burnt.
|
// If ether was sent to account post-selfdestruct it is burnt.
|
||||||
|
|
|
@ -55,7 +55,7 @@ func TestUpdateLeaks(t *testing.T) {
|
||||||
sdb = NewDatabase(tdb, nil)
|
sdb = NewDatabase(tdb, nil)
|
||||||
)
|
)
|
||||||
state, _ := New(types.EmptyRootHash, sdb)
|
state, _ := New(types.EmptyRootHash, sdb)
|
||||||
|
state.Snapshot()
|
||||||
// Update it with some accounts
|
// Update it with some accounts
|
||||||
for i := byte(0); i < 255; i++ {
|
for i := byte(0); i < 255; i++ {
|
||||||
addr := common.BytesToAddress([]byte{i})
|
addr := common.BytesToAddress([]byte{i})
|
||||||
|
@ -111,7 +111,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Write modifications to trie.
|
// Write modifications to trie.
|
||||||
transState.IntermediateRoot(false)
|
transState.IntermediateRoot(false)
|
||||||
|
transState.journal.snapshot()
|
||||||
// Overwrite all the data with new values in the transient database.
|
// Overwrite all the data with new values in the transient database.
|
||||||
for i := byte(0); i < 255; i++ {
|
for i := byte(0); i < 255; i++ {
|
||||||
modify(transState, common.Address{i}, i, 99)
|
modify(transState, common.Address{i}, i, 99)
|
||||||
|
@ -228,7 +228,7 @@ func TestCopy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCopyWithDirtyJournal tests if Copy can correct create a equal copied
|
// TestCopyWithDirtyJournal tests if Copy can correct create a equal copied
|
||||||
// stateDB with dirty journal present.
|
// stateDB with dirty linearJournal present.
|
||||||
func TestCopyWithDirtyJournal(t *testing.T) {
|
func TestCopyWithDirtyJournal(t *testing.T) {
|
||||||
db := NewDatabaseForTesting()
|
db := NewDatabaseForTesting()
|
||||||
orig, _ := New(types.EmptyRootHash, db)
|
orig, _ := New(types.EmptyRootHash, db)
|
||||||
|
@ -364,6 +364,12 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||||
{
|
{
|
||||||
name: "SetStorage",
|
name: "SetStorage",
|
||||||
fn: func(a testAction, s *StateDB) {
|
fn: func(a testAction, s *StateDB) {
|
||||||
|
contractHash := s.GetCodeHash(addr)
|
||||||
|
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
|
||||||
|
if emptyCode {
|
||||||
|
// no-op
|
||||||
|
return
|
||||||
|
}
|
||||||
var key, val common.Hash
|
var key, val common.Hash
|
||||||
binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
|
binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
|
||||||
binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
|
binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
|
||||||
|
@ -374,12 +380,26 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||||
{
|
{
|
||||||
name: "SetCode",
|
name: "SetCode",
|
||||||
fn: func(a testAction, s *StateDB) {
|
fn: func(a testAction, s *StateDB) {
|
||||||
// SetCode can only be performed in case the addr does
|
// SetCode cannot be performed if the addr already has code
|
||||||
// not already hold code
|
|
||||||
if c := s.GetCode(addr); len(c) > 0 {
|
if c := s.GetCode(addr); len(c) > 0 {
|
||||||
// no-op
|
// no-op
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// SetCode cannot be performed if the addr has just selfdestructed
|
||||||
|
if obj := s.getStateObject(addr); obj != nil {
|
||||||
|
if obj.selfDestructed {
|
||||||
|
// If it's selfdestructed, we cannot create into it
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SetCode requires the contract to be account + contract to be created first
|
||||||
|
if obj := s.getStateObject(addr); obj == nil {
|
||||||
|
s.createObject(addr)
|
||||||
|
}
|
||||||
|
obj := s.getStateObject(addr)
|
||||||
|
if !obj.newContract {
|
||||||
|
s.CreateContract(addr)
|
||||||
|
}
|
||||||
code := make([]byte, 16)
|
code := make([]byte, 16)
|
||||||
binary.BigEndian.PutUint64(code, uint64(a.args[0]))
|
binary.BigEndian.PutUint64(code, uint64(a.args[0]))
|
||||||
binary.BigEndian.PutUint64(code[8:], uint64(a.args[1]))
|
binary.BigEndian.PutUint64(code[8:], uint64(a.args[1]))
|
||||||
|
@ -405,13 +425,20 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||||
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
|
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
|
||||||
storageRoot := s.GetStorageRoot(addr)
|
storageRoot := s.GetStorageRoot(addr)
|
||||||
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
|
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
|
||||||
|
|
||||||
|
if obj := s.getStateObject(addr); obj != nil {
|
||||||
|
if obj.selfDestructed {
|
||||||
|
// If it's selfdestructed, we cannot create into it
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
|
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
|
||||||
s.CreateContract(addr)
|
s.CreateContract(addr)
|
||||||
// We also set some code here, to prevent the
|
// We also set some code here, to prevent the
|
||||||
// CreateContract action from being performed twice in a row,
|
// CreateContract action from being performed twice in a row,
|
||||||
// which would cause a difference in state when unrolling
|
// which would cause a difference in state when unrolling
|
||||||
// the journal. (CreateContact assumes created was false prior to
|
// the linearJournal. (CreateContact assumes created was false prior to
|
||||||
// invocation, and the journal rollback sets it to false).
|
// invocation, and the linearJournal rollback sets it to false).
|
||||||
s.SetCode(addr, []byte{1})
|
s.SetCode(addr, []byte{1})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -419,6 +446,15 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||||
{
|
{
|
||||||
name: "SelfDestruct",
|
name: "SelfDestruct",
|
||||||
fn: func(a testAction, s *StateDB) {
|
fn: func(a testAction, s *StateDB) {
|
||||||
|
obj := s.getStateObject(addr)
|
||||||
|
// SelfDestruct requires the object to first exist
|
||||||
|
if obj == nil {
|
||||||
|
s.createObject(addr)
|
||||||
|
}
|
||||||
|
obj = s.getStateObject(addr)
|
||||||
|
if !obj.newContract {
|
||||||
|
s.CreateContract(addr)
|
||||||
|
}
|
||||||
s.SelfDestruct(addr)
|
s.SelfDestruct(addr)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -439,15 +475,6 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||||
},
|
},
|
||||||
args: make([]int64, 1),
|
args: make([]int64, 1),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "AddPreimage",
|
|
||||||
fn: func(a testAction, s *StateDB) {
|
|
||||||
preimage := []byte{1}
|
|
||||||
hash := common.BytesToHash(preimage)
|
|
||||||
s.AddPreimage(hash, preimage)
|
|
||||||
},
|
|
||||||
args: make([]int64, 1),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "AddAddressToAccessList",
|
name: "AddAddressToAccessList",
|
||||||
fn: func(a testAction, s *StateDB) {
|
fn: func(a testAction, s *StateDB) {
|
||||||
|
@ -465,6 +492,13 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||||
{
|
{
|
||||||
name: "SetTransientState",
|
name: "SetTransientState",
|
||||||
fn: func(a testAction, s *StateDB) {
|
fn: func(a testAction, s *StateDB) {
|
||||||
|
contractHash := s.GetCodeHash(addr)
|
||||||
|
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
|
||||||
|
if emptyCode {
|
||||||
|
// no-op
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var key, val common.Hash
|
var key, val common.Hash
|
||||||
binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
|
binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
|
||||||
binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
|
binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
|
||||||
|
@ -529,14 +563,13 @@ func (test *snapshotTest) String() string {
|
||||||
func (test *snapshotTest) run() bool {
|
func (test *snapshotTest) run() bool {
|
||||||
// Run all actions and create snapshots.
|
// Run all actions and create snapshots.
|
||||||
var (
|
var (
|
||||||
state, _ = New(types.EmptyRootHash, NewDatabaseForTesting())
|
state, _ = New(types.EmptyRootHash, NewDatabaseForTesting())
|
||||||
snapshotRevs = make([]int, len(test.snapshots))
|
sindex = 0
|
||||||
sindex = 0
|
checkstates = make([]*StateDB, len(test.snapshots))
|
||||||
checkstates = make([]*StateDB, len(test.snapshots))
|
|
||||||
)
|
)
|
||||||
for i, action := range test.actions {
|
for i, action := range test.actions {
|
||||||
if len(test.snapshots) > sindex && i == test.snapshots[sindex] {
|
if len(test.snapshots) > sindex && i == test.snapshots[sindex] {
|
||||||
snapshotRevs[sindex] = state.Snapshot()
|
state.Snapshot()
|
||||||
checkstates[sindex] = state.Copy()
|
checkstates[sindex] = state.Copy()
|
||||||
sindex++
|
sindex++
|
||||||
}
|
}
|
||||||
|
@ -545,7 +578,7 @@ func (test *snapshotTest) run() bool {
|
||||||
// Revert all snapshots in reverse order. Each revert must yield a state
|
// Revert all snapshots in reverse order. Each revert must yield a state
|
||||||
// that is equivalent to fresh state with all actions up the snapshot applied.
|
// that is equivalent to fresh state with all actions up the snapshot applied.
|
||||||
for sindex--; sindex >= 0; sindex-- {
|
for sindex--; sindex >= 0; sindex-- {
|
||||||
state.RevertToSnapshot(snapshotRevs[sindex])
|
state.RevertSnapshot()
|
||||||
if err := test.checkEqual(state, checkstates[sindex]); err != nil {
|
if err := test.checkEqual(state, checkstates[sindex]); err != nil {
|
||||||
test.err = fmt.Errorf("state mismatch after revert to snapshot %d\n%v", sindex, err)
|
test.err = fmt.Errorf("state mismatch after revert to snapshot %d\n%v", sindex, err)
|
||||||
return false
|
return false
|
||||||
|
@ -677,22 +710,23 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||||
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
|
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
|
||||||
state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}))
|
state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}))
|
||||||
}
|
}
|
||||||
if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) {
|
{ // Check the dirty-accounts
|
||||||
getKeys := func(dirty map[common.Address]int) string {
|
have := state.journal.dirtyAccounts()
|
||||||
var keys []common.Address
|
want := checkstate.journal.dirtyAccounts()
|
||||||
out := new(strings.Builder)
|
slices.SortFunc(have, common.Address.Cmp)
|
||||||
for key := range dirty {
|
slices.SortFunc(want, common.Address.Cmp)
|
||||||
keys = append(keys, key)
|
if !slices.Equal(have, want) {
|
||||||
|
getKeys := func(keys []common.Address) string {
|
||||||
|
out := new(strings.Builder)
|
||||||
|
for i, key := range keys {
|
||||||
|
fmt.Fprintf(out, " %d. %v\n", i, key)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
}
|
}
|
||||||
slices.SortFunc(keys, common.Address.Cmp)
|
haveK := getKeys(have)
|
||||||
for i, key := range keys {
|
wantK := getKeys(want)
|
||||||
fmt.Fprintf(out, " %d. %v\n", i, key)
|
return fmt.Errorf("dirty-journal set mismatch.\nhave:\n%v\nwant:\n%v\n", haveK, wantK)
|
||||||
}
|
|
||||||
return out.String()
|
|
||||||
}
|
}
|
||||||
have := getKeys(state.journal.dirties)
|
|
||||||
want := getKeys(checkstate.journal.dirties)
|
|
||||||
return fmt.Errorf("dirty-journal set mismatch.\nhave:\n%v\nwant:\n%v\n", have, want)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -703,14 +737,14 @@ func TestTouchDelete(t *testing.T) {
|
||||||
root, _ := s.state.Commit(0, false)
|
root, _ := s.state.Commit(0, false)
|
||||||
s.state, _ = New(root, s.state.db)
|
s.state, _ = New(root, s.state.db)
|
||||||
|
|
||||||
snapshot := s.state.Snapshot()
|
s.state.Snapshot()
|
||||||
s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified)
|
s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified)
|
||||||
|
|
||||||
if len(s.state.journal.dirties) != 1 {
|
if len(s.state.journal.dirtyAccounts()) != 1 {
|
||||||
t.Fatal("expected one dirty state object")
|
t.Fatal("expected one dirty state object")
|
||||||
}
|
}
|
||||||
s.state.RevertToSnapshot(snapshot)
|
s.state.RevertSnapshot()
|
||||||
if len(s.state.journal.dirties) != 0 {
|
if len(s.state.journal.dirtyAccounts()) != 0 {
|
||||||
t.Fatal("expected no dirty state object")
|
t.Fatal("expected no dirty state object")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -954,9 +988,11 @@ func TestDeleteCreateRevert(t *testing.T) {
|
||||||
state.SelfDestruct(addr)
|
state.SelfDestruct(addr)
|
||||||
state.Finalise(true)
|
state.Finalise(true)
|
||||||
|
|
||||||
id := state.Snapshot()
|
state.Snapshot()
|
||||||
state.SetBalance(addr, uint256.NewInt(2), tracing.BalanceChangeUnspecified)
|
{
|
||||||
state.RevertToSnapshot(id)
|
state.SetBalance(addr, uint256.NewInt(2), tracing.BalanceChangeUnspecified)
|
||||||
|
}
|
||||||
|
state.RevertSnapshot()
|
||||||
|
|
||||||
// Commit the entire state and make sure we don't crash and have the correct state
|
// Commit the entire state and make sure we don't crash and have the correct state
|
||||||
root, _ = state.Commit(0, true)
|
root, _ = state.Commit(0, true)
|
||||||
|
@ -1099,33 +1135,34 @@ func TestStateDBAccessList(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.journal.snapshot() // journal id 0
|
||||||
state.AddAddressToAccessList(addr("aa")) // 1
|
state.AddAddressToAccessList(addr("aa")) // 1
|
||||||
state.AddSlotToAccessList(addr("bb"), slot("01")) // 2,3
|
state.journal.snapshot() // journal id 1
|
||||||
|
state.AddAddressToAccessList(addr("bb")) // 2
|
||||||
|
state.journal.snapshot() // journal id 2
|
||||||
|
state.AddSlotToAccessList(addr("bb"), slot("01")) // 3
|
||||||
|
state.journal.snapshot() // journal id 3
|
||||||
state.AddSlotToAccessList(addr("bb"), slot("02")) // 4
|
state.AddSlotToAccessList(addr("bb"), slot("02")) // 4
|
||||||
|
state.journal.snapshot() // journal id 4
|
||||||
verifyAddrs("aa", "bb")
|
verifyAddrs("aa", "bb")
|
||||||
verifySlots("bb", "01", "02")
|
verifySlots("bb", "01", "02")
|
||||||
|
|
||||||
// Make a copy
|
// Make a copy
|
||||||
stateCopy1 := state.Copy()
|
stateCopy1 := state.Copy()
|
||||||
if exp, got := 4, state.journal.length(); exp != got {
|
|
||||||
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// same again, should cause no journal entries
|
// same again, should cause no linearJournal entries
|
||||||
state.AddSlotToAccessList(addr("bb"), slot("01"))
|
state.AddSlotToAccessList(addr("bb"), slot("01"))
|
||||||
state.AddSlotToAccessList(addr("bb"), slot("02"))
|
state.AddSlotToAccessList(addr("bb"), slot("02"))
|
||||||
state.AddAddressToAccessList(addr("aa"))
|
state.AddAddressToAccessList(addr("aa"))
|
||||||
if exp, got := 4, state.journal.length(); exp != got {
|
|
||||||
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
|
|
||||||
}
|
|
||||||
// some new ones
|
// some new ones
|
||||||
state.AddSlotToAccessList(addr("bb"), slot("03")) // 5
|
state.AddSlotToAccessList(addr("bb"), slot("03")) // 5
|
||||||
|
state.journal.snapshot() // journal id 5
|
||||||
state.AddSlotToAccessList(addr("aa"), slot("01")) // 6
|
state.AddSlotToAccessList(addr("aa"), slot("01")) // 6
|
||||||
state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8
|
state.journal.snapshot() // journal id 6
|
||||||
state.AddAddressToAccessList(addr("cc"))
|
state.AddAddressToAccessList(addr("cc")) // 7
|
||||||
if exp, got := 8, state.journal.length(); exp != got {
|
state.journal.snapshot() // journal id 7
|
||||||
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
|
state.AddSlotToAccessList(addr("cc"), slot("01")) // 8
|
||||||
}
|
|
||||||
|
|
||||||
verifyAddrs("aa", "bb", "cc")
|
verifyAddrs("aa", "bb", "cc")
|
||||||
verifySlots("aa", "01")
|
verifySlots("aa", "01")
|
||||||
|
@ -1133,7 +1170,7 @@ func TestStateDBAccessList(t *testing.T) {
|
||||||
verifySlots("cc", "01")
|
verifySlots("cc", "01")
|
||||||
|
|
||||||
// now start rolling back changes
|
// now start rolling back changes
|
||||||
state.journal.revert(state, 7)
|
state.journal.revertSnapshot(state) // revert to 6
|
||||||
if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok {
|
if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok {
|
||||||
t.Fatalf("slot present, expected missing")
|
t.Fatalf("slot present, expected missing")
|
||||||
}
|
}
|
||||||
|
@ -1141,7 +1178,7 @@ func TestStateDBAccessList(t *testing.T) {
|
||||||
verifySlots("aa", "01")
|
verifySlots("aa", "01")
|
||||||
verifySlots("bb", "01", "02", "03")
|
verifySlots("bb", "01", "02", "03")
|
||||||
|
|
||||||
state.journal.revert(state, 6)
|
state.journal.revertSnapshot(state) // revert to 5
|
||||||
if state.AddressInAccessList(addr("cc")) {
|
if state.AddressInAccessList(addr("cc")) {
|
||||||
t.Fatalf("addr present, expected missing")
|
t.Fatalf("addr present, expected missing")
|
||||||
}
|
}
|
||||||
|
@ -1149,40 +1186,40 @@ func TestStateDBAccessList(t *testing.T) {
|
||||||
verifySlots("aa", "01")
|
verifySlots("aa", "01")
|
||||||
verifySlots("bb", "01", "02", "03")
|
verifySlots("bb", "01", "02", "03")
|
||||||
|
|
||||||
state.journal.revert(state, 5)
|
state.journal.revertSnapshot(state) // revert to 4
|
||||||
if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok {
|
if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok {
|
||||||
t.Fatalf("slot present, expected missing")
|
t.Fatalf("slot present, expected missing")
|
||||||
}
|
}
|
||||||
verifyAddrs("aa", "bb")
|
verifyAddrs("aa", "bb")
|
||||||
verifySlots("bb", "01", "02", "03")
|
verifySlots("bb", "01", "02", "03")
|
||||||
|
|
||||||
state.journal.revert(state, 4)
|
state.journal.revertSnapshot(state) // revert to 3
|
||||||
if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok {
|
if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok {
|
||||||
t.Fatalf("slot present, expected missing")
|
t.Fatalf("slot present, expected missing")
|
||||||
}
|
}
|
||||||
verifyAddrs("aa", "bb")
|
verifyAddrs("aa", "bb")
|
||||||
verifySlots("bb", "01", "02")
|
verifySlots("bb", "01", "02")
|
||||||
|
|
||||||
state.journal.revert(state, 3)
|
state.journal.revertSnapshot(state) // revert to 2
|
||||||
if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok {
|
if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok {
|
||||||
t.Fatalf("slot present, expected missing")
|
t.Fatalf("slot present, expected missing")
|
||||||
}
|
}
|
||||||
verifyAddrs("aa", "bb")
|
verifyAddrs("aa", "bb")
|
||||||
verifySlots("bb", "01")
|
verifySlots("bb", "01")
|
||||||
|
|
||||||
state.journal.revert(state, 2)
|
state.journal.revertSnapshot(state) // revert to 1
|
||||||
if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok {
|
if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok {
|
||||||
t.Fatalf("slot present, expected missing")
|
t.Fatalf("slot present, expected missing")
|
||||||
}
|
}
|
||||||
verifyAddrs("aa", "bb")
|
verifyAddrs("aa", "bb")
|
||||||
|
|
||||||
state.journal.revert(state, 1)
|
state.journal.revertSnapshot(state) // revert to 0
|
||||||
if state.AddressInAccessList(addr("bb")) {
|
if state.AddressInAccessList(addr("bb")) {
|
||||||
t.Fatalf("addr present, expected missing")
|
t.Fatalf("addr present, expected missing")
|
||||||
}
|
}
|
||||||
verifyAddrs("aa")
|
verifyAddrs("aa")
|
||||||
|
|
||||||
state.journal.revert(state, 0)
|
state.journal.revertSnapshot(state)
|
||||||
if state.AddressInAccessList(addr("aa")) {
|
if state.AddressInAccessList(addr("aa")) {
|
||||||
t.Fatalf("addr present, expected missing")
|
t.Fatalf("addr present, expected missing")
|
||||||
}
|
}
|
||||||
|
@ -1253,11 +1290,9 @@ func TestStateDBTransientStorage(t *testing.T) {
|
||||||
key := common.Hash{0x01}
|
key := common.Hash{0x01}
|
||||||
value := common.Hash{0x02}
|
value := common.Hash{0x02}
|
||||||
addr := common.Address{}
|
addr := common.Address{}
|
||||||
|
state.journal.snapshot()
|
||||||
state.SetTransientState(addr, key, value)
|
state.SetTransientState(addr, key, value)
|
||||||
if exp, got := 1, state.journal.length(); exp != got {
|
|
||||||
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
|
|
||||||
}
|
|
||||||
// the retrieved value should equal what was set
|
// the retrieved value should equal what was set
|
||||||
if got := state.GetTransientState(addr, key); got != value {
|
if got := state.GetTransientState(addr, key); got != value {
|
||||||
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
|
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
|
||||||
|
@ -1265,7 +1300,7 @@ func TestStateDBTransientStorage(t *testing.T) {
|
||||||
|
|
||||||
// revert the transient state being set and then check that the
|
// revert the transient state being set and then check that the
|
||||||
// value is now the empty hash
|
// value is now the empty hash
|
||||||
state.journal.revert(state, 0)
|
state.journal.revertSnapshot(state)
|
||||||
if got, exp := state.GetTransientState(addr, key), (common.Hash{}); exp != got {
|
if got, exp := state.GetTransientState(addr, key), (common.Hash{}); exp != got {
|
||||||
t.Fatalf("transient storage mismatch: have %x, want %x", got, exp)
|
t.Fatalf("transient storage mismatch: have %x, want %x", got, exp)
|
||||||
}
|
}
|
||||||
|
@ -1359,21 +1394,75 @@ func TestStorageDirtiness(t *testing.T) {
|
||||||
checkDirty(common.Hash{0x1}, common.Hash{}, false)
|
checkDirty(common.Hash{0x1}, common.Hash{}, false)
|
||||||
|
|
||||||
// the storage change is valid, dirty marker is expected
|
// the storage change is valid, dirty marker is expected
|
||||||
snap := state.Snapshot()
|
state.Snapshot()
|
||||||
state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
|
{
|
||||||
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
|
state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
|
||||||
|
}
|
||||||
// the storage change is reverted, dirtiness should be revoked
|
// the storage change is reverted, dirtiness should be revoked
|
||||||
state.RevertToSnapshot(snap)
|
state.RevertSnapshot()
|
||||||
checkDirty(common.Hash{0x1}, common.Hash{}, false)
|
checkDirty(common.Hash{0x1}, common.Hash{}, false)
|
||||||
|
|
||||||
// the storage is reset back to its original value, dirtiness should be revoked
|
// the storage is reset back to its original value, dirtiness should be revoked
|
||||||
state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
|
state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
|
||||||
snap = state.Snapshot()
|
state.Snapshot()
|
||||||
state.SetState(addr, common.Hash{0x1}, common.Hash{})
|
{
|
||||||
checkDirty(common.Hash{0x1}, common.Hash{}, false)
|
state.SetState(addr, common.Hash{0x1}, common.Hash{})
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{}, false)
|
||||||
|
}
|
||||||
// the storage change is reverted, dirty value should be set back
|
// the storage change is reverted, dirty value should be set back
|
||||||
state.RevertToSnapshot(snap)
|
state.RevertSnapshot()
|
||||||
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
|
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStorageDirtiness2(t *testing.T) {
|
||||||
|
var (
|
||||||
|
disk = rawdb.NewMemoryDatabase()
|
||||||
|
tdb = triedb.NewDatabase(disk, nil)
|
||||||
|
db = NewDatabase(tdb, nil)
|
||||||
|
state, _ = New(types.EmptyRootHash, db)
|
||||||
|
addr = common.HexToAddress("0x1")
|
||||||
|
checkDirty = func(key common.Hash, value common.Hash, dirty bool) {
|
||||||
|
t.Helper()
|
||||||
|
obj := state.getStateObject(addr)
|
||||||
|
v, exist := obj.dirtyStorage[key]
|
||||||
|
if exist != dirty {
|
||||||
|
t.Fatalf("unexpected dirty marker, want: %v, have: %v", dirty, exist)
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v != value {
|
||||||
|
t.Fatalf("unexpected storage slot, want: %x, have: %x", value, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
{ // Initiate a state, where an account has SLOT(1) = 0xA, +nonzero balance
|
||||||
|
state.CreateAccount(addr)
|
||||||
|
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified) // Prevent empty-delete
|
||||||
|
state.SetState(addr, common.Hash{0x1}, common.Hash{0xa})
|
||||||
|
root, err := state.Commit(0, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Init phase done, load it again
|
||||||
|
if state, err = New(root, NewDatabase(tdb, nil)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A no-op storage change, no dirty marker
|
||||||
|
state.SetState(addr, common.Hash{0x1}, common.Hash{0xa})
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{0xa}, false)
|
||||||
|
|
||||||
|
// Enter new scope
|
||||||
|
state.Snapshot()
|
||||||
|
{
|
||||||
|
state.SetState(addr, common.Hash{0x1}, common.Hash{0xb}) // SLOT(1) = 0xB
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{0xb}, true) // Should be flagged dirty
|
||||||
|
}
|
||||||
|
state.RevertSnapshot() // Revert scope
|
||||||
|
|
||||||
|
// the storage change has been set back to original, dirtiness should be revoked
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{0x1}, false)
|
||||||
|
}
|
||||||
|
|
|
@ -190,7 +190,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||||
return nil, gas, ErrInsufficientBalance
|
return nil, gas, ErrInsufficientBalance
|
||||||
}
|
}
|
||||||
snapshot := evm.StateDB.Snapshot()
|
evm.StateDB.Snapshot()
|
||||||
p, isPrecompile := evm.precompile(addr)
|
p, isPrecompile := evm.precompile(addr)
|
||||||
|
|
||||||
if !evm.StateDB.Exist(addr) {
|
if !evm.StateDB.Exist(addr) {
|
||||||
|
@ -198,7 +198,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
// add proof of absence to witness
|
// add proof of absence to witness
|
||||||
wgas := evm.AccessEvents.AddAccount(addr, false)
|
wgas := evm.AccessEvents.AddAccount(addr, false)
|
||||||
if gas < wgas {
|
if gas < wgas {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertSnapshot()
|
||||||
return nil, 0, ErrOutOfGas
|
return nil, 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
gas -= wgas
|
gas -= wgas
|
||||||
|
@ -206,6 +206,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
|
|
||||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
||||||
// Calling a non-existing account, don't do anything.
|
// Calling a non-existing account, don't do anything.
|
||||||
|
evm.StateDB.DiscardSnapshot()
|
||||||
return nil, gas, nil
|
return nil, gas, nil
|
||||||
}
|
}
|
||||||
evm.StateDB.CreateAccount(addr)
|
evm.StateDB.CreateAccount(addr)
|
||||||
|
@ -234,7 +235,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally,
|
// above we revert to the snapshot and consume any gas remaining. Additionally,
|
||||||
// when we're in homestead this also counts for code storage gas errors.
|
// when we're in homestead this also counts for code storage gas errors.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertSnapshot()
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||||
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
|
||||||
|
@ -242,9 +243,8 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
|
|
||||||
gas = 0
|
gas = 0
|
||||||
}
|
}
|
||||||
// TODO: consider clearing up unused snapshots:
|
} else {
|
||||||
//} else {
|
evm.StateDB.DiscardSnapshot()
|
||||||
// evm.StateDB.DiscardSnapshot(snapshot)
|
|
||||||
}
|
}
|
||||||
return ret, gas, err
|
return ret, gas, err
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||||
return nil, gas, ErrInsufficientBalance
|
return nil, gas, ErrInsufficientBalance
|
||||||
}
|
}
|
||||||
var snapshot = evm.StateDB.Snapshot()
|
evm.StateDB.Snapshot()
|
||||||
|
|
||||||
// It is allowed to call precompiles, even via delegatecall
|
// It is allowed to call precompiles, even via delegatecall
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
|
@ -290,7 +290,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertSnapshot()
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||||
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
|
||||||
|
@ -298,6 +298,8 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
|
|
||||||
gas = 0
|
gas = 0
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
evm.StateDB.DiscardSnapshot()
|
||||||
}
|
}
|
||||||
return ret, gas, err
|
return ret, gas, err
|
||||||
}
|
}
|
||||||
|
@ -323,7 +325,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
}
|
}
|
||||||
var snapshot = evm.StateDB.Snapshot()
|
evm.StateDB.Snapshot()
|
||||||
|
|
||||||
// It is allowed to call precompiles, even via delegatecall
|
// It is allowed to call precompiles, even via delegatecall
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
|
@ -337,13 +339,15 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertSnapshot()
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||||
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
|
||||||
}
|
}
|
||||||
gas = 0
|
gas = 0
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
evm.StateDB.DiscardSnapshot()
|
||||||
}
|
}
|
||||||
return ret, gas, err
|
return ret, gas, err
|
||||||
}
|
}
|
||||||
|
@ -369,7 +373,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
// after all empty accounts were deleted, so this is not required. However, if we omit this,
|
// after all empty accounts were deleted, so this is not required. However, if we omit this,
|
||||||
// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
|
// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
|
||||||
// We could change this, but for now it's left for legacy reasons
|
// We could change this, but for now it's left for legacy reasons
|
||||||
var snapshot = evm.StateDB.Snapshot()
|
evm.StateDB.Snapshot()
|
||||||
|
|
||||||
// We do an AddBalance of zero here, just in order to trigger a touch.
|
// We do an AddBalance of zero here, just in order to trigger a touch.
|
||||||
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
|
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
|
||||||
|
@ -395,7 +399,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertSnapshot()
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||||
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
|
||||||
|
@ -403,6 +407,8 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
|
|
||||||
gas = 0
|
gas = 0
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
evm.StateDB.DiscardSnapshot()
|
||||||
}
|
}
|
||||||
return ret, gas, err
|
return ret, gas, err
|
||||||
}
|
}
|
||||||
|
@ -476,7 +482,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
// Create a new account on the state only if the object was not present.
|
// Create a new account on the state only if the object was not present.
|
||||||
// It might be possible the contract code is deployed to a pre-existent
|
// It might be possible the contract code is deployed to a pre-existent
|
||||||
// account with non-zero balance.
|
// account with non-zero balance.
|
||||||
snapshot := evm.StateDB.Snapshot()
|
evm.StateDB.Snapshot()
|
||||||
if !evm.StateDB.Exist(address) {
|
if !evm.StateDB.Exist(address) {
|
||||||
evm.StateDB.CreateAccount(address)
|
evm.StateDB.CreateAccount(address)
|
||||||
}
|
}
|
||||||
|
@ -510,10 +516,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
|
|
||||||
ret, err = evm.initNewContract(contract, address, value)
|
ret, err = evm.initNewContract(contract, address, value)
|
||||||
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertSnapshot()
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
|
contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
evm.StateDB.DiscardSnapshot()
|
||||||
}
|
}
|
||||||
return ret, address, contract.Gas, err
|
return ret, address, contract.Gas, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,8 +88,14 @@ type StateDB interface {
|
||||||
|
|
||||||
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
||||||
|
|
||||||
RevertToSnapshot(int)
|
// Snapshot starts a new journalled scope.
|
||||||
Snapshot() int
|
Snapshot()
|
||||||
|
// RevertSnapshot reverts all state changes made in the most recent journalled scope.
|
||||||
|
RevertSnapshot()
|
||||||
|
// DiscardSnapshot removes the ability to roll back the changes in the most
|
||||||
|
// recent journalled scope. After calling this method, the changes are considered
|
||||||
|
// part of the parent scope.
|
||||||
|
DiscardSnapshot()
|
||||||
|
|
||||||
AddLog(*types.Log)
|
AddLog(*types.Log)
|
||||||
AddPreimage(common.Hash, []byte)
|
AddPreimage(common.Hash, []byte)
|
||||||
|
|
|
@ -230,7 +230,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
||||||
evm.SetTxContext(txContext)
|
evm.SetTxContext(txContext)
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
snap := state.StateDB.Snapshot()
|
state.StateDB.Snapshot()
|
||||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||||
if _, err = st.TransitionDb(); err != nil {
|
if _, err = st.TransitionDb(); err != nil {
|
||||||
b.Fatalf("failed to execute transaction: %v", err)
|
b.Fatalf("failed to execute transaction: %v", err)
|
||||||
|
@ -238,7 +238,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
||||||
if _, err = tracer.GetResult(); err != nil {
|
if _, err = tracer.GetResult(); err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
state.StateDB.RevertToSnapshot(snap)
|
state.StateDB.RevertSnapshot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ func NewJSONLogger(cfg *Config, writer io.Writer) *tracing.Hooks {
|
||||||
l.hooks = &tracing.Hooks{
|
l.hooks = &tracing.Hooks{
|
||||||
OnTxStart: l.OnTxStart,
|
OnTxStart: l.OnTxStart,
|
||||||
OnSystemCallStart: l.onSystemCallStart,
|
OnSystemCallStart: l.onSystemCallStart,
|
||||||
OnExit: l.OnEnd,
|
OnExit: l.OnExit,
|
||||||
OnOpcode: l.OnOpcode,
|
OnOpcode: l.OnOpcode,
|
||||||
OnFault: l.OnFault,
|
OnFault: l.OnFault,
|
||||||
}
|
}
|
||||||
|
@ -152,13 +152,6 @@ func (l *jsonLogger) OnEnter(depth int, typ byte, from common.Address, to common
|
||||||
l.encoder.Encode(frame)
|
l.encoder.Encode(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *jsonLogger) OnEnd(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
|
|
||||||
if depth > 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.OnExit(depth, output, gasUsed, err, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
|
func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
|
||||||
type endLog struct {
|
type endLog struct {
|
||||||
Output string `json:"output"`
|
Output string `json:"output"`
|
||||||
|
|
|
@ -99,7 +99,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
snap := state.StateDB.Snapshot()
|
state.StateDB.Snapshot()
|
||||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||||
res, err := st.TransitionDb()
|
res, err := st.TransitionDb()
|
||||||
|
@ -107,7 +107,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
tracer.OnTxEnd(&types.Receipt{GasUsed: res.UsedGas}, nil)
|
tracer.OnTxEnd(&types.Receipt{GasUsed: res.UsedGas}, nil)
|
||||||
state.StateDB.RevertToSnapshot(snap)
|
state.StateDB.RevertSnapshot()
|
||||||
if have, want := len(tracer.StructLogs()), 244752; have != want {
|
if have, want := len(tracer.StructLogs()), 244752; have != want {
|
||||||
b.Fatalf("trace wrong, want %d steps, have %d", want, have)
|
b.Fatalf("trace wrong, want %d steps, have %d", want, have)
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,13 +305,11 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio
|
||||||
|
|
||||||
// applyTransaction runs the transaction. If execution fails, state and gas pool are reverted.
|
// applyTransaction runs the transaction. If execution fails, state and gas pool are reverted.
|
||||||
func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) {
|
func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) {
|
||||||
var (
|
gp := env.gasPool.Gas()
|
||||||
snap = env.state.Snapshot()
|
env.state.Snapshot()
|
||||||
gp = env.gasPool.Gas()
|
|
||||||
)
|
|
||||||
receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx, &env.header.GasUsed)
|
receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx, &env.header.GasUsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.state.RevertToSnapshot(snap)
|
env.state.RevertSnapshot()
|
||||||
env.gasPool.SetGas(gp)
|
env.gasPool.SetGas(gp)
|
||||||
}
|
}
|
||||||
return receipt, err
|
return receipt, err
|
||||||
|
|
|
@ -316,7 +316,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
||||||
)
|
)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
snapshot := state.StateDB.Snapshot()
|
state.StateDB.Snapshot()
|
||||||
state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
|
state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
@ -333,7 +333,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
||||||
refund += state.StateDB.GetRefund()
|
refund += state.StateDB.GetRefund()
|
||||||
gasUsed += msg.GasLimit - leftOverGas
|
gasUsed += msg.GasLimit - leftOverGas
|
||||||
|
|
||||||
state.StateDB.RevertToSnapshot(snapshot)
|
state.StateDB.RevertSnapshot()
|
||||||
}
|
}
|
||||||
if elapsed < 1 {
|
if elapsed < 1 {
|
||||||
elapsed = 1
|
elapsed = 1
|
||||||
|
|
|
@ -300,17 +300,18 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
|
||||||
tracer.OnTxStart(evm.GetVMContext(), nil, msg.From)
|
tracer.OnTxStart(evm.GetVMContext(), nil, msg.From)
|
||||||
}
|
}
|
||||||
// Execute the message.
|
// Execute the message.
|
||||||
snapshot := st.StateDB.Snapshot()
|
st.StateDB.Snapshot()
|
||||||
gaspool := new(core.GasPool)
|
gaspool := new(core.GasPool)
|
||||||
gaspool.AddGas(block.GasLimit())
|
gaspool.AddGas(block.GasLimit())
|
||||||
vmRet, err := core.ApplyMessage(evm, msg, gaspool)
|
vmRet, err := core.ApplyMessage(evm, msg, gaspool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
st.StateDB.RevertToSnapshot(snapshot)
|
st.StateDB.RevertSnapshot()
|
||||||
if tracer := evm.Config.Tracer; tracer != nil && tracer.OnTxEnd != nil {
|
if tracer := evm.Config.Tracer; tracer != nil && tracer.OnTxEnd != nil {
|
||||||
evm.Config.Tracer.OnTxEnd(nil, err)
|
evm.Config.Tracer.OnTxEnd(nil, err)
|
||||||
}
|
}
|
||||||
return st, common.Hash{}, 0, err
|
return st, common.Hash{}, 0, err
|
||||||
}
|
}
|
||||||
|
st.StateDB.DiscardSnapshot()
|
||||||
// Add 0-value mining reward. This only makes a difference in the cases
|
// Add 0-value mining reward. This only makes a difference in the cases
|
||||||
// where
|
// where
|
||||||
// - the coinbase self-destructed, or
|
// - the coinbase self-destructed, or
|
||||||
|
|
Loading…
Reference in New Issue