diff --git a/core/blockchain.go b/core/blockchain.go index f19a25f59811..4101b4585ea0 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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 diff --git a/core/state/statedb.go b/core/state/statedb.go index df5365a50fb3..51adee555308 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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. diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go new file mode 100644 index 000000000000..36047352a6ea --- /dev/null +++ b/core/state/statedb_hooked.go @@ -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 . + +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) + } + } + } +} diff --git a/core/state/statedb_hooked_test.go b/core/state/statedb_hooked_test.go new file mode 100644 index 000000000000..9abd76b02db8 --- /dev/null +++ b/core/state/statedb_hooked_test.go @@ -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 . + +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) + } + } +} diff --git a/core/state/statedb_logger.go b/core/state/statedb_logger.go deleted file mode 100644 index 8f57b994dc27..000000000000 --- a/core/state/statedb_logger.go +++ /dev/null @@ -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 . - -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) - } -} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index e0bda355cd5e..25f6f72582a1 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -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) - } -} diff --git a/core/state_processor.go b/core/state_processor.go index b51e3e3ecb4f..ec499f892875 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -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 diff --git a/core/types.go b/core/types.go index 41997cfda4dc..bed20802ab51 100644 --- a/core/types.go +++ b/core/types.go @@ -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. diff --git a/core/vm/interface.go b/core/vm/interface.go index 13bb17adb0ee..6f88b84c0aa8 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -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 diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 5b6945f54f0c..7d8191c25bd6 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -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 { diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 395ab9c97c0a..5b9c80959662 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -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{ diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index 7a6e1751e87d..0ec3c367bc5b 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -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) diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index 90f59225dfd0..c6cf10a48346 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -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) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 00b3d28b969f..a25c92a2bcfb 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -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() { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 5474ceefe5b0..81b4633d42cf 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -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() }