core/state: move state log mechanism to a separate layer (#30569)

This PR moves the logging/tracing-facilities out of `*state.StateDB`,
in to a wrapping struct which implements `vm.StateDB` instead.

In most places, it is a pretty straight-forward change: 
- First, hoisting the invocations from state objects up to the statedb. 
- Then making the mutation-methods simply return the previous value, so
that the external logging layer could log everything.

Some internal code uses the direct object-accessors to mutate the state,
particularly in testing and in setting up state overrides, which means
that these changes are unobservable for the hooked layer. Thus, configuring
the overrides are not necessarily part of the API we want to publish.

The trickiest part about the layering is that when the selfdestructs are
finally deleted during `Finalise`, there's the possibility that someone
sent some ether to it, which is burnt at that point, and thus needs to
be logged. The hooked layer reaches into the inner layer to figure out
these events.

In package `vm`, the conversion from `state.StateDB + hooks` into a
hooked `vm.StateDB` is performed where needed.

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
pull/30650/head
Martin HS 1 month ago committed by GitHub
parent a5fe7353cf
commit 459bb4a647
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      consensus/beacon/consensus.go
  2. 3
      consensus/clique/clique.go
  3. 3
      consensus/consensus.go
  4. 5
      consensus/ethash/consensus.go
  5. 10
      consensus/misc/dao.go
  6. 1
      core/blockchain.go
  7. 41
      core/state/state_object.go
  8. 13
      core/state/state_test.go
  9. 60
      core/state/statedb.go
  10. 242
      core/state/statedb_hooked.go
  11. 130
      core/state/statedb_hooked_test.go
  12. 16
      core/state/statedb_test.go
  13. 3
      core/state/sync_test.go
  14. 41
      core/state_processor.go
  15. 2
      core/vm/instructions.go
  16. 18
      core/vm/interface.go
  17. 1
      eth/tracers/api.go
  18. 26
      eth/tracers/internal/tracetest/calltrace_test.go
  19. 1
      eth/tracers/internal/tracetest/flat_calltrace_test.go
  20. 1
      eth/tracers/internal/tracetest/prestate_test.go
  21. 3
      eth/tracers/internal/tracetest/supply_test.go
  22. 8
      eth/tracers/logger/logger_test.go
  23. 14
      internal/ethapi/api.go
  24. 1
      internal/ethapi/api_test.go
  25. 11
      internal/ethapi/simulate.go

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
@ -349,7 +350,7 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H
}
// Finalize implements consensus.Engine and processes withdrawals on top.
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body) {
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) {
if !beacon.IsPoSHeader(header) {
beacon.ethone.Finalize(chain, header, state, body)
return

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
@ -573,7 +574,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header
// Finalize implements consensus.Engine. There is no post-transaction
// consensus rules in clique, do nothing here.
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body) {
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) {
// No block rewards in PoA, so the state remains as is
}

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
@ -88,7 +89,7 @@ type Engine interface {
//
// Note: The state database might be updated to reflect any consensus rules
// that happen at finalization (e.g. block rewards).
Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body)
Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body)
// FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
// rewards or process withdrawals) and assembles the final block.

@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
@ -503,7 +504,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H
}
// Finalize implements consensus.Engine, accumulating the block and uncle rewards.
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body) {
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) {
// Accumulate any block and uncle rewards
accumulateRewards(chain.Config(), state, header, body.Uncles)
}
@ -566,7 +567,7 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
// accumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(config *params.ChainConfig, stateDB *state.StateDB, header *types.Header, uncles []*types.Header) {
func accumulateRewards(config *params.ChainConfig, stateDB vm.StateDB, header *types.Header, uncles []*types.Header) {
// Select the correct block reward based on chain progression
blockReward := FrontierBlockReward
if config.IsByzantium(header.Number) {

@ -21,11 +21,10 @@ import (
"errors"
"math/big"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
var (
@ -74,7 +73,7 @@ func VerifyDAOHeaderExtraData(config *params.ChainConfig, header *types.Header)
// ApplyDAOHardFork modifies the state database according to the DAO hard-fork
// rules, transferring all balances of a set of DAO accounts to a single refund
// contract.
func ApplyDAOHardFork(statedb *state.StateDB) {
func ApplyDAOHardFork(statedb vm.StateDB) {
// Retrieve the contract to refund balances into
if !statedb.Exist(params.DAORefundContract) {
statedb.CreateAccount(params.DAORefundContract)
@ -82,7 +81,8 @@ func ApplyDAOHardFork(statedb *state.StateDB) {
// Move every DAO account and extra-balance account funds into the refund contract
for _, addr := range params.DAODrainList() {
statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), tracing.BalanceIncreaseDaoContract)
statedb.SetBalance(addr, new(uint256.Int), tracing.BalanceDecreaseDaoAccount)
balance := statedb.GetBalance(addr)
statedb.AddBalance(params.DAORefundContract, balance, tracing.BalanceIncreaseDaoContract)
statedb.SubBalance(addr, balance, tracing.BalanceDecreaseDaoAccount)
}
}

@ -1774,7 +1774,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
if err != nil {
return nil, it.index, err
}
statedb.SetLogger(bc.logger)
// If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly

@ -23,7 +23,6 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
@ -208,19 +207,18 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
}
// SetState updates a value in account storage.
func (s *stateObject) SetState(key, value common.Hash) {
// It returns the previous value
func (s *stateObject) SetState(key, value common.Hash) common.Hash {
// If the new value is the same as old, don't set. Otherwise, track only the
// dirty changes, supporting reverting all of it back to no change.
prev, origin := s.getState(key)
if prev == value {
return
return prev
}
// New value is different, update and journal the change
s.db.journal.storageChange(s.address, key, prev, origin)
s.setState(key, value, origin)
if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
s.db.logger.OnStorageChange(s.address, key, prev, value)
}
return prev
}
// setState updates a value in account dirty storage. The dirtiness will be
@ -448,33 +446,25 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
// AddBalance adds amount to s's balance.
// It is used to add funds to the destination account of a transfer.
func (s *stateObject) AddBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) {
// returns the previous balance
func (s *stateObject) AddBalance(amount *uint256.Int) uint256.Int {
// EIP161: We must check emptiness for the objects such that the account
// clearing (0,0,0 objects) can take effect.
if amount.IsZero() {
if s.empty() {
s.touch()
}
return
return *(s.Balance())
}
s.SetBalance(new(uint256.Int).Add(s.Balance(), amount), reason)
return s.SetBalance(new(uint256.Int).Add(s.Balance(), amount))
}
// SubBalance removes amount from s's balance.
// It is used to remove funds from the origin account of a transfer.
func (s *stateObject) SubBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) {
if amount.IsZero() {
return
}
s.SetBalance(new(uint256.Int).Sub(s.Balance(), amount), reason)
}
func (s *stateObject) SetBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) {
// SetBalance sets the balance for the object, and returns the previous balance.
func (s *stateObject) SetBalance(amount *uint256.Int) uint256.Int {
prev := *s.data.Balance
s.db.journal.balanceChange(s.address, s.data.Balance)
if s.db.logger != nil && s.db.logger.OnBalanceChange != nil {
s.db.logger.OnBalanceChange(s.address, s.Balance().ToBig(), amount.ToBig(), reason)
}
s.setBalance(amount)
return prev
}
func (s *stateObject) setBalance(amount *uint256.Int) {
@ -547,10 +537,6 @@ func (s *stateObject) CodeSize() int {
func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
s.db.journal.setCode(s.address)
if s.db.logger != nil && s.db.logger.OnCodeChange != nil {
// TODO remove prevcode from this callback
s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), nil, codeHash, code)
}
s.setCode(codeHash, code)
}
@ -562,9 +548,6 @@ func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
func (s *stateObject) SetNonce(nonce uint64) {
s.db.journal.nonceChange(s.address, s.data.Nonce)
if s.db.logger != nil && s.db.logger.OnNonceChange != nil {
s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce)
}
s.setNonce(nonce)
}

@ -23,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/triedb"
@ -48,11 +47,11 @@ func TestDump(t *testing.T) {
// generate a few entries
obj1 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01}))
obj1.AddBalance(uint256.NewInt(22), tracing.BalanceChangeUnspecified)
obj1.AddBalance(uint256.NewInt(22))
obj2 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02}))
obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
obj3 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x02}))
obj3.SetBalance(uint256.NewInt(44), tracing.BalanceChangeUnspecified)
obj3.SetBalance(uint256.NewInt(44))
// write some of them to the trie
s.state.updateStateObject(obj1)
@ -106,13 +105,13 @@ func TestIterativeDump(t *testing.T) {
// generate a few entries
obj1 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01}))
obj1.AddBalance(uint256.NewInt(22), tracing.BalanceChangeUnspecified)
obj1.AddBalance(uint256.NewInt(22))
obj2 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02}))
obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
obj3 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x02}))
obj3.SetBalance(uint256.NewInt(44), tracing.BalanceChangeUnspecified)
obj3.SetBalance(uint256.NewInt(44))
obj4 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x00}))
obj4.AddBalance(uint256.NewInt(1337), tracing.BalanceChangeUnspecified)
obj4.AddBalance(uint256.NewInt(1337))
// write some of them to the trie
s.state.updateStateObject(obj1)
@ -200,7 +199,7 @@ func TestCreateObjectRevert(t *testing.T) {
state.CreateAccount(addr)
so0 := state.getStateObject(addr)
so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified)
so0.SetBalance(uint256.NewInt(42))
so0.SetNonce(43)
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
state.setStateObject(so0)

@ -21,7 +21,6 @@ import (
"errors"
"fmt"
"maps"
"math/big"
"slices"
"sync"
"sync/atomic"
@ -81,7 +80,6 @@ type StateDB struct {
db Database
prefetcher *triePrefetcher
trie Trie
logger *tracing.Hooks
reader Reader
// originalRoot is the pre-state root, before any changes were made.
@ -189,11 +187,6 @@ func New(root common.Hash, db Database) (*StateDB, error) {
return sdb, nil
}
// SetLogger sets the logger for account update hooks.
func (s *StateDB) SetLogger(l *tracing.Hooks) {
s.logger = l
}
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot.
@ -247,9 +240,6 @@ func (s *StateDB) AddLog(log *types.Log) {
log.TxHash = s.thash
log.TxIndex = uint(s.txIndex)
log.Index = s.logSize
if s.logger != nil && s.logger.OnLog != nil {
s.logger.OnLog(log)
}
s.logs[s.thash] = append(s.logs[s.thash], log)
s.logSize++
}
@ -409,25 +399,30 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
*/
// AddBalance adds amount to the account associated with addr.
func (s *StateDB) AddBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) {
func (s *StateDB) AddBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) uint256.Int {
stateObject := s.getOrNewStateObject(addr)
if stateObject != nil {
stateObject.AddBalance(amount, reason)
if stateObject == nil {
return uint256.Int{}
}
return stateObject.AddBalance(amount)
}
// SubBalance subtracts amount from the account associated with addr.
func (s *StateDB) SubBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) {
func (s *StateDB) SubBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) uint256.Int {
stateObject := s.getOrNewStateObject(addr)
if stateObject != nil {
stateObject.SubBalance(amount, reason)
if stateObject == nil {
return uint256.Int{}
}
if amount.IsZero() {
return *(stateObject.Balance())
}
return stateObject.SetBalance(new(uint256.Int).Sub(stateObject.Balance(), amount))
}
func (s *StateDB) SetBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) {
stateObject := s.getOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetBalance(amount, reason)
stateObject.SetBalance(amount)
}
}
@ -445,11 +440,11 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) {
}
}
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
stateObject := s.getOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetState(key, value)
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) common.Hash {
if stateObject := s.getOrNewStateObject(addr); stateObject != nil {
return stateObject.SetState(key, value)
}
return common.Hash{}
}
// SetStorage replaces the entire storage for the specified account with given
@ -477,7 +472,7 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common
if obj != nil {
newObj.SetCode(common.BytesToHash(obj.CodeHash()), obj.code)
newObj.SetNonce(obj.Nonce())
newObj.SetBalance(obj.Balance(), tracing.BalanceChangeUnspecified)
newObj.SetBalance(obj.Balance())
}
}
@ -486,15 +481,17 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common
//
// The account's state object is still available until the state is committed,
// getStateObject will return a non-nil account after SelfDestruct.
func (s *StateDB) SelfDestruct(addr common.Address) {
func (s *StateDB) SelfDestruct(addr common.Address) uint256.Int {
stateObject := s.getStateObject(addr)
var prevBalance uint256.Int
if stateObject == nil {
return
return prevBalance
}
prevBalance = *(stateObject.Balance())
// Regardless of whether it is already destructed or not, we do have to
// journal the balance-change, if we set it to zero here.
if !stateObject.Balance().IsZero() {
stateObject.SetBalance(new(uint256.Int), tracing.BalanceDecreaseSelfdestruct)
stateObject.SetBalance(new(uint256.Int))
}
// If it is already marked as self-destructed, we do not need to add it
// for journalling a second time.
@ -502,16 +499,18 @@ func (s *StateDB) SelfDestruct(addr common.Address) {
s.journal.destruct(addr)
stateObject.markSelfdestructed()
}
return prevBalance
}
func (s *StateDB) Selfdestruct6780(addr common.Address) {
func (s *StateDB) SelfDestruct6780(addr common.Address) (uint256.Int, bool) {
stateObject := s.getStateObject(addr)
if stateObject == nil {
return
return uint256.Int{}, false
}
if stateObject.newContract {
s.SelfDestruct(addr)
return s.SelfDestruct(addr), true
}
return *(stateObject.Balance()), false
}
// SetTransientState sets transient storage for a given account. It
@ -735,11 +734,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
delete(s.stateObjects, obj.address)
s.markDelete(addr)
// If ether was sent to account post-selfdestruct it is burnt.
if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 {
s.logger.OnBalanceChange(obj.address, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
}
// We need to maintain account deletions explicitly (will remain
// set indefinitely). Note only the first occurred self-destruct
// event is tracked.

@ -0,0 +1,242 @@
// 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 (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
// hookedStateDB represents a statedb which emits calls to tracing-hooks
// on state operations.
type hookedStateDB struct {
inner *StateDB
hooks *tracing.Hooks
}
// NewHookedState wraps the given stateDb with the given hooks
func NewHookedState(stateDb *StateDB, hooks *tracing.Hooks) *hookedStateDB {
s := &hookedStateDB{stateDb, hooks}
if s.hooks == nil {
s.hooks = new(tracing.Hooks)
}
return s
}
func (s *hookedStateDB) CreateAccount(addr common.Address) {
s.inner.CreateAccount(addr)
}
func (s *hookedStateDB) CreateContract(addr common.Address) {
s.inner.CreateContract(addr)
}
func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int {
return s.inner.GetBalance(addr)
}
func (s *hookedStateDB) GetNonce(addr common.Address) uint64 {
return s.inner.GetNonce(addr)
}
func (s *hookedStateDB) GetCodeHash(addr common.Address) common.Hash {
return s.inner.GetCodeHash(addr)
}
func (s *hookedStateDB) GetCode(addr common.Address) []byte {
return s.inner.GetCode(addr)
}
func (s *hookedStateDB) GetCodeSize(addr common.Address) int {
return s.inner.GetCodeSize(addr)
}
func (s *hookedStateDB) AddRefund(u uint64) {
s.inner.AddRefund(u)
}
func (s *hookedStateDB) SubRefund(u uint64) {
s.inner.SubRefund(u)
}
func (s *hookedStateDB) GetRefund() uint64 {
return s.inner.GetRefund()
}
func (s *hookedStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
return s.inner.GetCommittedState(addr, hash)
}
func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
return s.inner.GetState(addr, hash)
}
func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash {
return s.inner.GetStorageRoot(addr)
}
func (s *hookedStateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash {
return s.inner.GetTransientState(addr, key)
}
func (s *hookedStateDB) SetTransientState(addr common.Address, key, value common.Hash) {
s.inner.SetTransientState(addr, key, value)
}
func (s *hookedStateDB) HasSelfDestructed(addr common.Address) bool {
return s.inner.HasSelfDestructed(addr)
}
func (s *hookedStateDB) Exist(addr common.Address) bool {
return s.inner.Exist(addr)
}
func (s *hookedStateDB) Empty(addr common.Address) bool {
return s.inner.Empty(addr)
}
func (s *hookedStateDB) AddressInAccessList(addr common.Address) bool {
return s.inner.AddressInAccessList(addr)
}
func (s *hookedStateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) {
return s.inner.SlotInAccessList(addr, slot)
}
func (s *hookedStateDB) AddAddressToAccessList(addr common.Address) {
s.inner.AddAddressToAccessList(addr)
}
func (s *hookedStateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
s.inner.AddSlotToAccessList(addr, slot)
}
func (s *hookedStateDB) PointCache() *utils.PointCache {
return s.inner.PointCache()
}
func (s *hookedStateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) {
s.inner.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses)
}
func (s *hookedStateDB) RevertToSnapshot(i int) {
s.inner.RevertToSnapshot(i)
}
func (s *hookedStateDB) Snapshot() int {
return s.inner.Snapshot()
}
func (s *hookedStateDB) AddPreimage(hash common.Hash, bytes []byte) {
s.inner.Snapshot()
}
func (s *hookedStateDB) Witness() *stateless.Witness {
return s.inner.Witness()
}
func (s *hookedStateDB) SubBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) uint256.Int {
prev := s.inner.SubBalance(addr, amount, reason)
if s.hooks.OnBalanceChange != nil && !amount.IsZero() {
newBalance := new(uint256.Int).Sub(&prev, amount)
s.hooks.OnBalanceChange(addr, prev.ToBig(), newBalance.ToBig(), reason)
}
return prev
}
func (s *hookedStateDB) AddBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) uint256.Int {
prev := s.inner.AddBalance(addr, amount, reason)
if s.hooks.OnBalanceChange != nil && !amount.IsZero() {
newBalance := new(uint256.Int).Add(&prev, amount)
s.hooks.OnBalanceChange(addr, prev.ToBig(), newBalance.ToBig(), reason)
}
return prev
}
func (s *hookedStateDB) SetNonce(address common.Address, nonce uint64) {
s.inner.SetNonce(address, nonce)
if s.hooks.OnNonceChange != nil {
s.hooks.OnNonceChange(address, nonce-1, nonce)
}
}
func (s *hookedStateDB) SetCode(address common.Address, code []byte) {
s.inner.SetCode(address, code)
if s.hooks.OnCodeChange != nil {
s.hooks.OnCodeChange(address, types.EmptyCodeHash, nil, crypto.Keccak256Hash(code), code)
}
}
func (s *hookedStateDB) SetState(address common.Address, key common.Hash, value common.Hash) common.Hash {
prev := s.inner.SetState(address, key, value)
if s.hooks.OnStorageChange != nil && prev != value {
s.hooks.OnStorageChange(address, key, prev, value)
}
return prev
}
func (s *hookedStateDB) SelfDestruct(address common.Address) uint256.Int {
prev := s.inner.SelfDestruct(address)
if !prev.IsZero() {
if s.hooks.OnBalanceChange != nil {
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
}
}
return prev
}
func (s *hookedStateDB) SelfDestruct6780(address common.Address) (uint256.Int, bool) {
prev, changed := s.inner.SelfDestruct6780(address)
if !prev.IsZero() && changed {
if s.hooks.OnBalanceChange != nil {
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
}
}
return prev, changed
}
func (s *hookedStateDB) AddLog(log *types.Log) {
// The inner will modify the log (add fields), so invoke that first
s.inner.AddLog(log)
if s.hooks.OnLog != nil {
s.hooks.OnLog(log)
}
}
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
defer s.inner.Finalise(deleteEmptyObjects)
if s.hooks.OnBalanceChange == nil {
return
}
for addr := range s.inner.journal.dirties {
obj := s.inner.stateObjects[addr]
if obj != nil && obj.selfDestructed {
// If ether was sent to account post-selfdestruct it is burnt.
if bal := obj.Balance(); bal.Sign() != 0 {
s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
}
}
}
}

@ -0,0 +1,130 @@
// 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 (
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
)
// This method tests that the 'burn' from sending-to-selfdestructed accounts
// is accounted for.
// (There is also a higher-level test in eth/tracers: TestSupplySelfDestruct )
func TestBurn(t *testing.T) {
// Note: burn can happen even after EIP-6780, if within one single transaction,
// the following occur:
// 1. contract B creates contract A
// 2. contract A is destructed
// 3. constract B sends ether to A
var burned = new(uint256.Int)
s, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
hooked := NewHookedState(s, &tracing.Hooks{
OnBalanceChange: func(addr common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
if reason == tracing.BalanceDecreaseSelfdestructBurn {
burned.Add(burned, uint256.MustFromBig(prev))
}
},
})
createAndDestroy := func(addr common.Address) {
hooked.AddBalance(addr, uint256.NewInt(100), tracing.BalanceChangeUnspecified)
hooked.CreateContract(addr)
hooked.SelfDestruct(addr)
// sanity-check that balance is now 0
if have, want := hooked.GetBalance(addr), new(uint256.Int); !have.Eq(want) {
t.Fatalf("post-destruct balance wrong: have %v want %v", have, want)
}
}
addA := common.Address{0xaa}
addB := common.Address{0xbb}
addC := common.Address{0xcc}
// Tx 1: create and destroy address A and B in one tx
createAndDestroy(addA)
createAndDestroy(addB)
hooked.AddBalance(addA, uint256.NewInt(200), tracing.BalanceChangeUnspecified)
hooked.AddBalance(addB, uint256.NewInt(200), tracing.BalanceChangeUnspecified)
hooked.Finalise(true)
// Tx 2: create and destroy address C, then commit
createAndDestroy(addC)
hooked.AddBalance(addC, uint256.NewInt(200), tracing.BalanceChangeUnspecified)
hooked.Finalise(true)
s.Commit(0, false)
if have, want := burned, uint256.NewInt(600); !have.Eq(want) {
t.Fatalf("burn-count wrong, have %v want %v", have, want)
}
}
// TestHooks is a basic sanity-check of all hooks
func TestHooks(t *testing.T) {
inner, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
inner.SetTxContext(common.Hash{0x11}, 100) // For the log
var result []string
var wants = []string{
"0xaa00000000000000000000000000000000000000.balance: 0->100 (BalanceChangeUnspecified)",
"0xaa00000000000000000000000000000000000000.balance: 100->50 (BalanceChangeTransfer)",
"0xaa00000000000000000000000000000000000000.nonce: 1336->1337",
"0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728)",
"0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000000 ->0x0000000000000000000000000000000000000000000000000000000000000011",
"0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000011 ->0x0000000000000000000000000000000000000000000000000000000000000022",
"log 100",
}
emitF := func(format string, a ...any) {
result = append(result, fmt.Sprintf(format, a...))
}
sdb := NewHookedState(inner, &tracing.Hooks{
OnBalanceChange: func(addr common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
emitF("%v.balance: %v->%v (%v)", addr, prev, new, reason)
},
OnNonceChange: func(addr common.Address, prev, new uint64) {
emitF("%v.nonce: %v->%v", addr, prev, new)
},
OnCodeChange: func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) {
emitF("%v.code: %#x (%v) ->%#x (%v)", addr, prevCode, prevCodeHash, code, codeHash)
},
OnStorageChange: func(addr common.Address, slot common.Hash, prev, new common.Hash) {
emitF("%v.storage slot %v: %v ->%v", addr, slot, prev, new)
},
OnLog: func(log *types.Log) {
emitF("log %v", log.TxIndex)
},
})
sdb.AddBalance(common.Address{0xaa}, uint256.NewInt(100), tracing.BalanceChangeUnspecified)
sdb.SubBalance(common.Address{0xaa}, uint256.NewInt(50), tracing.BalanceChangeTransfer)
sdb.SetNonce(common.Address{0xaa}, 1337)
sdb.SetCode(common.Address{0xaa}, []byte{0x13, 37})
sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x11"))
sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x22"))
sdb.SetTransientState(common.Address{0xaa}, common.HexToHash("0x02"), common.HexToHash("0x01"))
sdb.SetTransientState(common.Address{0xaa}, common.HexToHash("0x02"), common.HexToHash("0x02"))
sdb.AddLog(&types.Log{
Address: common.Address{0xbb},
})
for i, want := range wants {
if have := result[i]; have != want {
t.Fatalf("error event %d, have\n%v\nwant%v\n", i, have, want)
}
}
}

@ -170,7 +170,7 @@ func TestCopy(t *testing.T) {
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
obj.AddBalance(uint256.NewInt(uint64(i)))
orig.updateStateObject(obj)
}
orig.Finalise(false)
@ -187,9 +187,9 @@ func TestCopy(t *testing.T) {
copyObj := copy.getOrNewStateObject(common.BytesToAddress([]byte{i}))
ccopyObj := ccopy.getOrNewStateObject(common.BytesToAddress([]byte{i}))
origObj.AddBalance(uint256.NewInt(2*uint64(i)), tracing.BalanceChangeUnspecified)
copyObj.AddBalance(uint256.NewInt(3*uint64(i)), tracing.BalanceChangeUnspecified)
ccopyObj.AddBalance(uint256.NewInt(4*uint64(i)), tracing.BalanceChangeUnspecified)
origObj.AddBalance(uint256.NewInt(2 * uint64(i)))
copyObj.AddBalance(uint256.NewInt(3 * uint64(i)))
ccopyObj.AddBalance(uint256.NewInt(4 * uint64(i)))
orig.updateStateObject(origObj)
copy.updateStateObject(copyObj)
@ -236,7 +236,7 @@ func TestCopyWithDirtyJournal(t *testing.T) {
// Fill up the initial states
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
obj.AddBalance(uint256.NewInt(uint64(i)))
obj.data.Root = common.HexToHash("0xdeadbeef")
orig.updateStateObject(obj)
}
@ -246,7 +246,9 @@ func TestCopyWithDirtyJournal(t *testing.T) {
// modify all in memory without finalizing
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.SubBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
amount := uint256.NewInt(uint64(i))
obj.SetBalance(new(uint256.Int).Sub(obj.Balance(), amount))
orig.updateStateObject(obj)
}
cpy := orig.Copy()
@ -280,7 +282,7 @@ func TestCopyObjectState(t *testing.T) {
// Fill up the initial states
for i := byte(0); i < 5; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
obj.AddBalance(uint256.NewInt(uint64(i)))
obj.data.Root = common.HexToHash("0xdeadbeef")
orig.updateStateObject(obj)
}

@ -22,7 +22,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
@ -62,7 +61,7 @@ func makeTestState(scheme string) (ethdb.Database, Database, *triedb.Database, c
obj := state.getOrNewStateObject(common.BytesToAddress([]byte{i}))
acc := &testAccount{address: common.BytesToAddress([]byte{i})}
obj.AddBalance(uint256.NewInt(uint64(11*i)), tracing.BalanceChangeUnspecified)
obj.AddBalance(uint256.NewInt(uint64(11 * i)))
acc.balance = uint256.NewInt(uint64(11 * i))
obj.SetNonce(uint64(42 * i))

@ -75,6 +75,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
// Apply pre-execution system calls.
context = NewEVMBlockContext(header, p.chain, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
@ -98,7 +99,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
}
var tracingStateDB = vm.StateDB(statedb)
if hooks := cfg.Tracer; hooks != nil {
tracingStateDB = state.NewHookedState(statedb, hooks)
}
// Read requests if Prague is enabled.
var requests [][]byte
if p.config.IsPrague(block.Number(), block.Time()) {
@ -109,15 +113,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
}
requests = append(requests, depositRequests)
// EIP-7002 withdrawals
withdrawalRequests := ProcessWithdrawalQueue(vmenv, statedb)
withdrawalRequests := ProcessWithdrawalQueue(vmenv, tracingStateDB)
requests = append(requests, withdrawalRequests)
// EIP-7251 consolidations
consolidationRequests := ProcessConsolidationQueue(vmenv, statedb)
consolidationRequests := ProcessConsolidationQueue(vmenv, tracingStateDB)
requests = append(requests, consolidationRequests)
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.engine.Finalize(p.chain, header, statedb, block.Body())
p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body())
return &ProcessResult{
Receipts: receipts,
@ -131,17 +135,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
// and uses the input parameters for its environment similar to ApplyTransaction. However,
// this method takes an already created EVM instance as input.
func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnTxStart != nil {
evm.Config.Tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
if evm.Config.Tracer.OnTxEnd != nil {
defer func() {
evm.Config.Tracer.OnTxEnd(receipt, err)
}()
var tracingStateDB = vm.StateDB(statedb)
if hooks := evm.Config.Tracer; hooks != nil {
tracingStateDB = state.NewHookedState(statedb, hooks)
if hooks.OnTxStart != nil {
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From)
}
if hooks.OnTxEnd != nil {
defer func() { hooks.OnTxEnd(receipt, err) }()
}
}
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb)
evm.Reset(txContext, tracingStateDB)
// Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, msg, gp)
@ -152,7 +159,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
// Update the state with pending changes.
var root []byte
if config.IsByzantium(blockNumber) {
statedb.Finalise(true)
tracingStateDB.Finalise(true)
} else {
root = statedb.IntermediateRoot(config.IsEIP158(blockNumber)).Bytes()
}
@ -217,7 +224,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
// contract. This method is exported to be used in tests.
func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) {
func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb vm.StateDB) {
if tracer := vmenv.Config.Tracer; tracer != nil {
if tracer.OnSystemCallStart != nil {
tracer.OnSystemCallStart()
@ -243,7 +250,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat
// ProcessParentBlockHash stores the parent block hash in the history storage contract
// as per EIP-2935.
func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state.StateDB) {
func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb vm.StateDB) {
if tracer := vmenv.Config.Tracer; tracer != nil {
if tracer.OnSystemCallStart != nil {
tracer.OnSystemCallStart()
@ -269,17 +276,17 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state.
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
// It returns the opaque request data returned by the contract.
func ProcessWithdrawalQueue(vmenv *vm.EVM, statedb *state.StateDB) []byte {
func ProcessWithdrawalQueue(vmenv *vm.EVM, statedb vm.StateDB) []byte {
return processRequestsSystemCall(vmenv, statedb, 0x01, params.WithdrawalQueueAddress)
}
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
// It returns the opaque request data returned by the contract.
func ProcessConsolidationQueue(vmenv *vm.EVM, statedb *state.StateDB) []byte {
func ProcessConsolidationQueue(vmenv *vm.EVM, statedb vm.StateDB) []byte {
return processRequestsSystemCall(vmenv, statedb, 0x02, params.ConsolidationQueueAddress)
}
func processRequestsSystemCall(vmenv *vm.EVM, statedb *state.StateDB, requestType byte, addr common.Address) []byte {
func processRequestsSystemCall(vmenv *vm.EVM, statedb vm.StateDB, requestType byte, addr common.Address) []byte {
if tracer := vmenv.Config.Tracer; tracer != nil {
if tracer.OnSystemCallStart != nil {
tracer.OnSystemCallStart()

@ -919,7 +919,7 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct)
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address())
interpreter.evm.StateDB.SelfDestruct6780(scope.Contract.Address())
if tracer := interpreter.evm.Config.Tracer; tracer != nil {
if tracer.OnEnter != nil {
tracer.OnEnter(interpreter.evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig())

@ -33,8 +33,8 @@ type StateDB interface {
CreateAccount(common.Address)
CreateContract(common.Address)
SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)
AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)
SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int
AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int
GetBalance(common.Address) *uint256.Int
GetNonce(common.Address) uint64
@ -51,16 +51,21 @@ type StateDB interface {
GetCommittedState(common.Address, common.Hash) common.Hash
GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash)
SetState(common.Address, common.Hash, common.Hash) common.Hash
GetStorageRoot(addr common.Address) common.Hash
GetTransientState(addr common.Address, key common.Hash) common.Hash
SetTransientState(addr common.Address, key, value common.Hash)
SelfDestruct(common.Address)
SelfDestruct(common.Address) uint256.Int
HasSelfDestructed(common.Address) bool
Selfdestruct6780(common.Address)
// SelfDestruct6780 is post-EIP6780 selfdestruct, which means that it's a
// send-all-to-beneficiary, unless the contract was created in this same
// transaction, in which case it will be destructed.
// This method returns the prior balance, along with a boolean which is
// true iff the object was indeed destructed.
SelfDestruct6780(common.Address) (uint256.Int, bool)
// Exist reports whether the given account exists in state.
// Notably this should also return true for self-destructed accounts.
@ -90,6 +95,9 @@ type StateDB interface {
AddPreimage(common.Hash, []byte)
Witness() *stateless.Witness
// Finalise must be invoked at the end of a transaction
Finalise(bool)
}
// CallContext provides a basic interface for the EVM calling conventions. The EVM

@ -1018,7 +1018,6 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
}
// The actual TxContext will be created as part of ApplyTransactionWithEVM.
vmenv := vm.NewEVM(vmctx, vm.TxContext{GasPrice: message.GasPrice, BlobFeeCap: message.BlobGasFeeCap}, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
statedb.SetLogger(tracer.Hooks)
// Define a meaningful timeout of a single transaction trace
if config.Timeout != nil {

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
@ -116,21 +117,23 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
var (
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
context = test.Context.toBlockContext(test.Genesis)
state = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
st = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
)
state.Close()
st.Close()
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig, test.Genesis.Config)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
state.StateDB.SetLogger(tracer.Hooks)
logState := vm.StateDB(st.StateDB)
if tracer.Hooks != nil {
logState = state.NewHookedState(st.StateDB, tracer.Hooks)
}
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
evm := vm.NewEVM(context, core.NewEVMTxContext(msg), logState, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
@ -349,7 +352,7 @@ func TestInternals(t *testing.T) {
},
} {
t.Run(tc.name, func(t *testing.T) {
state := tests.MakePreState(rawdb.NewMemoryDatabase(),
st := tests.MakePreState(rawdb.NewMemoryDatabase(),
types.GenesisAlloc{
to: types.Account{
Code: tc.code,
@ -358,8 +361,13 @@ func TestInternals(t *testing.T) {
Balance: big.NewInt(500000000000000),
},
}, false, rawdb.HashScheme)
defer state.Close()
state.StateDB.SetLogger(tc.tracer.Hooks)
defer st.Close()
logState := vm.StateDB(st.StateDB)
if hooks := tc.tracer.Hooks; hooks != nil {
logState = state.NewHookedState(st.StateDB, hooks)
}
tx, err := types.SignNewTx(key, signer, &types.LegacyTx{
To: &to,
Value: big.NewInt(0),
@ -373,7 +381,7 @@ func TestInternals(t *testing.T) {
Origin: origin,
GasPrice: tx.GasPrice(),
}
evm := vm.NewEVM(context, txContext, state.StateDB, config, vm.Config{Tracer: tc.tracer.Hooks})
evm := vm.NewEVM(context, txContext, logState, config, vm.Config{Tracer: tc.tracer.Hooks})
msg, err := core.TransactionToMessage(tx, signer, big.NewInt(0))
if err != nil {
t.Fatalf("test %v: failed to create message: %v", tc.name, err)

@ -94,7 +94,6 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
return fmt.Errorf("failed to create call tracer: %v", err)
}
state.StateDB.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil {
return fmt.Errorf("failed to prepare transaction for tracing: %v", err)

@ -102,7 +102,6 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
t.Fatalf("failed to create call tracer: %v", err)
}
state.StateDB.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err)

@ -597,6 +597,7 @@ func testSupplyTracer(t *testing.T, genesis *core.Genesis, gen func(*core.BlockG
}
func compareAsJSON(t *testing.T, expected interface{}, actual interface{}) {
t.Helper()
want, err := json.Marshal(expected)
if err != nil {
t.Fatalf("failed to marshal expected value to JSON: %v", err)
@ -608,6 +609,6 @@ func compareAsJSON(t *testing.T, expected interface{}, actual interface{}) {
}
if !bytes.Equal(want, have) {
t.Fatalf("incorrect supply info: expected %s, got %s", string(want), string(have))
t.Fatalf("incorrect supply info:\nwant %s\nhave %s", string(want), string(have))
}
}

@ -49,9 +49,11 @@ type dummyStatedb struct {
state.StateDB
}
func (*dummyStatedb) GetRefund() uint64 { return 1337 }
func (*dummyStatedb) GetState(_ common.Address, _ common.Hash) common.Hash { return common.Hash{} }
func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) {}
func (*dummyStatedb) GetRefund() uint64 { return 1337 }
func (*dummyStatedb) GetState(_ common.Address, _ common.Hash) common.Hash { return common.Hash{} }
func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) common.Hash {
return common.Hash{}
}
func TestStoreCapture(t *testing.T) {
var (

@ -1209,11 +1209,16 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s
if precompiles != nil {
evm.SetPrecompiles(precompiles)
}
return applyMessageWithEVM(ctx, evm, msg, state, timeout, gp)
res, err := applyMessageWithEVM(ctx, evm, msg, timeout, gp)
// If an internal state error occurred, let that have precedence. Otherwise,
// a "trie root missing" type of error will masquerade as e.g. "insufficient gas"
if err := state.Error(); err != nil {
return nil, err
}
return res, err
}
func applyMessageWithEVM(ctx context.Context, evm *vm.EVM, msg *core.Message, state *state.StateDB, timeout time.Duration, gp *core.GasPool) (*core.ExecutionResult, error) {
func applyMessageWithEVM(ctx context.Context, evm *vm.EVM, msg *core.Message, timeout time.Duration, gp *core.GasPool) (*core.ExecutionResult, error) {
// Wait for the context to be done and cancel the evm. Even if the
// EVM has finished, cancelling may be done (repeatedly)
go func() {
@ -1223,9 +1228,6 @@ func applyMessageWithEVM(ctx context.Context, evm *vm.EVM, msg *core.Message, st
// Execute the message.
result, err := core.ApplyMessage(evm, msg, gp)
if err := state.Error(); err != nil {
return nil, err
}
// If the timer caused an abort, return an appropriate error message
if evm.Cancelled() {

@ -2192,6 +2192,7 @@ func TestSimulateV1(t *testing.T) {
t.Fatalf("failed to unmarshal result: %v", err)
}
if !reflect.DeepEqual(have, tc.want) {
t.Log(string(resBytes))
t.Errorf("test %s, result mismatch, have\n%v\n, want\n%v\n", tc.name, have, tc.want)
}
})

@ -187,7 +187,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
}
evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, sim.state, sim.chainConfig, *vmConfig)
)
sim.state.SetLogger(tracer.Hooks())
var tracingStateDB = vm.StateDB(sim.state)
if hooks := tracer.Hooks(); hooks != nil {
tracingStateDB = state.NewHookedState(sim.state, hooks)
}
// It is possible to override precompiles with EVM bytecode, or
// move them to another address.
if precompiles != nil {
@ -205,8 +208,8 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
tracer.reset(tx.Hash(), uint(i))
// EoA check is always skipped, even in validation mode.
msg := call.ToMessage(header.BaseFee, !sim.validate, true)
evm.Reset(core.NewEVMTxContext(msg), sim.state)
result, err := applyMessageWithEVM(ctx, evm, msg, sim.state, timeout, sim.gp)
evm.Reset(core.NewEVMTxContext(msg), tracingStateDB)
result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp)
if err != nil {
txErr := txValidationError(err)
return nil, nil, txErr
@ -214,7 +217,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
// Update the state with pending changes.
var root []byte
if sim.chainConfig.IsByzantium(blockContext.BlockNumber) {
sim.state.Finalise(true)
tracingStateDB.Finalise(true)
} else {
root = sim.state.IntermediateRoot(sim.chainConfig.IsEIP158(blockContext.BlockNumber)).Bytes()
}

Loading…
Cancel
Save