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