core/state: make inner statedb explicit, implement alternative logging burn

core/state, core/vm: refactor statedb hooking

core/state: trace consensus finalize and system calls

eth/tracers/internal/tracetest: fix tests after refactor

core/state: some renaming and cleanup of statedb-hooking system

core/state: remove unecessary methods, implement hooked subbalance, more testing
This commit is contained in:
Martin Holst Swende 2024-10-18 12:01:05 +02:00
parent 4de4fa4b60
commit 8ca0cd582d
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
15 changed files with 425 additions and 256 deletions

View file

@ -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
@ -1893,13 +1892,10 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
bc.logger.OnBlockEnd(blockEndErr)
}()
}
var wStateDb = vm.StateDB(statedb)
if w := statedb.Wrapped(); w != nil {
wStateDb = w
}
// Process block using the parent state as reference point
pstart := time.Now()
res, err := bc.processor.Process(block, wStateDb, bc.vmConfig)
res, err := bc.processor.Process(block, statedb, bc.vmConfig)
if err != nil {
bc.reportBlock(block, res, err)
return nil, err

View file

@ -81,11 +81,7 @@ type StateDB struct {
db Database
prefetcher *triePrefetcher
trie Trie
onSelfDestructBurn func(common.Address, *uint256.Int)
shim *stateDBLogger
reader Reader
reader Reader
// originalRoot is the pre-state root, before any changes were made.
// It will be updated when the Commit is called.
@ -192,19 +188,6 @@ func New(root common.Hash, db Database) (*StateDB, error) {
return sdb, nil
}
func (s *StateDB) SetBurnCallback(fn func(common.Address, *uint256.Int)) {
s.onSelfDestructBurn = fn
}
// SetLogger sets the logger for account update hooks.
func (s *StateDB) SetLogger(l *tracing.Hooks) {
s.shim = newStateDBLogger(s, l)
}
func (s *StateDB) Wrapped() *stateDBLogger {
return s.shim
}
// 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.
@ -754,11 +737,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(); bal.Sign() != 0 && obj.selfDestructed && s.onSelfDestructBurn != nil {
s.onSelfDestructBurn(obj.address, bal)
}
// We need to maintain account deletions explicitly (will remain
// set indefinitely). Note only the first occurred self-destruct
// event is tracked.

View file

@ -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 {
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 {
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 {
prev := s.inner.Selfdestruct6780(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) 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)
}
}
}
}

View file

@ -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)
}
}
}

View file

@ -1,104 +0,0 @@
// 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/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/holiman/uint256"
)
type stateDBLogger struct {
*StateDB
hooks *tracing.Hooks
}
func newStateDBLogger(db *StateDB, hooks *tracing.Hooks) *stateDBLogger {
s := &stateDBLogger{db, hooks}
if s.hooks == nil {
s.hooks = new(tracing.Hooks)
}
db.SetBurnCallback(func(address common.Address, amount *uint256.Int) {
if s.hooks.OnBalanceChange != nil {
s.hooks.OnBalanceChange(address, amount.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
}
})
return s
}
func (s *stateDBLogger) AddBalance(address common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) uint256.Int {
prev := s.StateDB.AddBalance(address, amount, reason)
if s.hooks.OnBalanceChange != nil {
newBalance := new(uint256.Int).Add(&prev, amount)
s.hooks.OnBalanceChange(address, prev.ToBig(), newBalance.ToBig(), reason)
}
return prev
}
func (s *stateDBLogger) SetNonce(address common.Address, nonce uint64) {
s.StateDB.SetNonce(address, nonce)
if s.hooks.OnNonceChange != nil {
s.hooks.OnNonceChange(address, nonce-1, nonce)
}
}
func (s *stateDBLogger) SetCode(address common.Address, code []byte) {
s.StateDB.SetCode(address, code)
if s.hooks.OnCodeChange != nil {
s.hooks.OnCodeChange(address, types.EmptyCodeHash, nil, crypto.Keccak256Hash(code), code)
}
}
func (s *stateDBLogger) SetState(address common.Address, key common.Hash, value common.Hash) common.Hash {
prev := s.StateDB.SetState(address, key, value)
if s.hooks.OnStorageChange != nil && prev != value {
s.hooks.OnStorageChange(address, key, prev, value)
}
return prev
}
func (s *stateDBLogger) SelfDestruct(address common.Address) uint256.Int {
prev := s.StateDB.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 *stateDBLogger) Selfdestruct6780(address common.Address) uint256.Int {
prev := s.StateDB.Selfdestruct6780(address)
if !prev.IsZero() {
if s.hooks.OnBalanceChange != nil {
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
}
}
return prev
}
func (s *stateDBLogger) AddLog(log *types.Log) {
// The inner will modify the log (add fields), so invoke that first
s.StateDB.AddLog(log)
if s.hooks.OnLog != nil {
s.hooks.OnLog(log)
}
}

View file

@ -22,7 +22,6 @@ import (
"fmt"
"maps"
"math"
"math/big"
"math/rand"
"reflect"
"slices"
@ -1378,53 +1377,3 @@ func TestStorageDirtiness(t *testing.T) {
state.RevertToSnapshot(snap)
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
}
// 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())
sdblog := newStateDBLogger(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) {
sdblog.AddBalance(addr, uint256.NewInt(100), tracing.BalanceChangeUnspecified)
sdblog.CreateContract(addr)
sdblog.SelfDestruct(addr)
// sanity-check that balance is now 0
if have, want := sdblog.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)
sdblog.AddBalance(addA, uint256.NewInt(200), tracing.BalanceChangeUnspecified)
sdblog.AddBalance(addB, uint256.NewInt(200), tracing.BalanceChangeUnspecified)
sdblog.Finalise(true)
// Tx 2: create and destroy address C, then commit
createAndDestroy(addC)
sdblog.AddBalance(addC, uint256.NewInt(200), tracing.BalanceChangeUnspecified)
sdblog.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)
}
}

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc"
"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"
@ -52,7 +53,7 @@ func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StatePro
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.Config) (*ProcessResult, error) {
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
var (
receipts types.Receipts
usedGas = new(uint64)
@ -98,7 +99,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.
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 vm.StateDB, cfg vm.
}
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,
@ -130,19 +134,21 @@ func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database
// 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 vm.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)
}()
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) {
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)
@ -153,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()
}
@ -163,7 +169,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
}
// MakeReceipt generates the receipt object for a transaction given its execution result.
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb vm.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
// Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas}
@ -204,7 +210,7 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb vm.StateDB, block
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb vm.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee)
if err != nil {
return nil, err

View file

@ -48,7 +48,7 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
Process(block *types.Block, statedb vm.StateDB, cfg vm.Config) (*ProcessResult, error)
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error)
}
// ProcessResult contains the values computed by Process.

View file

@ -20,7 +20,6 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
@ -91,32 +90,9 @@ type StateDB interface {
AddPreimage(common.Hash, []byte)
Witness() *stateless.Witness
// Finalise must be invoked at the end of a transaction
Finalise(bool)
// IntermediateRoot computes the current root hash of the state trie.
// It is called in
// - between transactions (pre-byzantium)
// - nowadays only after all transactions,
// in order to obtain the state root hash .
IntermediateRoot(deleteEmptyObjects bool) common.Hash
// GetLogs returns the logs matching the specified transaction hash, and annotates
// them with the given blockNumber and blockHash.
GetLogs(txHash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log
// TxIndex returns the current transaction index set by SetTxContext.
TxIndex() int
// SetTxContext sets the current transaction hash and index which are
// used when the EVM emits new state logs. It should be invoked before
// transaction execution.
SetTxContext(txHash common.Hash, txIndex int)
// GetTrie returns the account trie. Verkle-related
GetTrie() state.Trie
// AccessEvents returns the access-events collected for verkle-witness processing.
AccessEvents() *state.AccessEvents
// Error returns any memorized database failure occurred previously.
Error() error
}
// CallContext provides a basic interface for the EVM calling conventions. The EVM

View file

@ -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 {

View file

@ -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,19 +117,17 @@ 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(state.StateDB.Wrapped())
if logState == nil {
logState = state.StateDB
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 {
@ -353,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,
@ -362,12 +361,11 @@ 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(state.StateDB.Wrapped())
if logState == nil {
logState = state.StateDB
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{

View file

@ -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)

View file

@ -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)

View file

@ -1208,11 +1208,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 vm.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() {
@ -1222,9 +1227,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() {

View file

@ -187,10 +187,9 @@ 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())
logState := vm.StateDB(sim.state.Wrapped())
if logState == nil {
logState = vm.StateDB(sim.state)
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.
@ -209,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), logState)
result, err := applyMessageWithEVM(ctx, evm, msg, logState, 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
@ -218,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()
}