diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 95d59a04fa..d6ab18d805 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -192,7 +192,8 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { utils.Fatalf("Failed to create tracer %q: %v", name, err) } - cfg.Eth.VMTracer = t + cfg.Eth.VMTracer = t.VMLogger + cfg.Eth.LiveLogger = t } } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2490e79fe5..abff65a09f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2207,7 +2207,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh if err != nil { Fatalf("Failed to create tracer %q: %v", name, err) } - vmcfg.Tracer = t + vmcfg.LiveLogger = t + vmcfg.Tracer = t.VMLogger } } // Disable transaction indexing/unindexing by default. diff --git a/core/blockchain.go b/core/blockchain.go index e08c939f1e..bb5a36ba44 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -39,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/syncx" @@ -284,7 +285,7 @@ type BlockChain struct { processor Processor // Block transaction processor interface forker *ForkChoice vmConfig vm.Config - logger BlockchainLogger + logger *live.LiveLogger } // NewBlockChain returns a fully initialised block chain using information @@ -296,15 +297,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis } // Open trie database with provided config triedb := trie.NewDatabase(db, cacheConfig.triedbConfig()) - var logger BlockchainLogger - if vmConfig.Tracer != nil { - l, ok := vmConfig.Tracer.(BlockchainLogger) - if ok { - logger = l - } else { - log.Warn("only extended tracers are supported for live mode") - } - } + // Setup the genesis block, commit the provided genesis specification // to database if the genesis block is not present yet, or load the // stored one from database. @@ -336,7 +329,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks), engine: engine, vmConfig: vmConfig, - logger: logger, + logger: vmConfig.LiveLogger, } bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit)) bc.forker = NewForkChoice(bc, shouldPreserve) @@ -463,7 +456,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis } } - if bc.logger != nil { + if bc.logger != nil && bc.logger.OnGenesisBlock != nil { if block := bc.CurrentBlock(); block.Number.Uint64() == 0 { alloc, err := getGenesisState(bc.db, block.Hash()) if err != nil { @@ -514,7 +507,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis } rawdb.WriteChainConfig(db, genesisHash, chainConfig) } - if bc.logger != nil { + if bc.logger != nil && bc.logger.OnBlockchainInit != nil { bc.logger.OnBlockchainInit(chainConfig) } // Start tx indexer/unindexer if required. @@ -1819,7 +1812,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } stats.processed++ - if bc.logger != nil { + if bc.logger != nil && bc.logger.OnSkippedBlock != nil { bc.logger.OnSkippedBlock(BlockEvent{ Block: block, TD: bc.GetTd(block.ParentHash(), block.NumberU64()-1), @@ -1951,7 +1944,7 @@ type blockProcessingResult struct { // processBlock executes and validates the given block. If there was no error // it writes the block and associated state to database. func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) { - if bc.logger != nil { + if bc.logger != nil && bc.logger.OnBlockStart != nil { td := bc.GetTd(block.ParentHash(), block.NumberU64()-1) bc.logger.OnBlockStart(BlockEvent{ Block: block, @@ -1959,6 +1952,8 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s Finalized: bc.CurrentFinalBlock(), Safe: bc.CurrentSafeBlock(), }) + } + if bc.logger != nil && bc.logger.OnBlockEnd != nil { defer func() { bc.logger.OnBlockEnd(blockEndErr) }() diff --git a/core/state/state_object.go b/core/state/state_object.go index f9cc0109a2..b6decf7717 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -239,7 +239,7 @@ func (s *stateObject) SetState(key, value common.Hash) { key: key, prevalue: prev, }) - if s.db.logger != nil { + if s.db.logger != nil && s.db.logger.OnStorageChange != nil { s.db.logger.OnStorageChange(s.address, key, prev, value) } s.setState(key, value) @@ -430,7 +430,7 @@ func (s *stateObject) SetBalance(amount *big.Int, reason BalanceChangeReason) { account: &s.address, prev: new(big.Int).Set(s.data.Balance), }) - if s.db.logger != nil { + if s.db.logger != nil && s.db.logger.OnBalanceChange != nil { s.db.logger.OnBalanceChange(s.address, s.Balance(), amount, reason) } s.setBalance(amount) @@ -510,7 +510,7 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { prevhash: s.CodeHash(), prevcode: prevcode, }) - if s.db.logger != nil { + if s.db.logger != nil && s.db.logger.OnCodeChange != nil { s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code) } s.setCode(codeHash, code) @@ -527,7 +527,7 @@ func (s *stateObject) SetNonce(nonce uint64) { account: &s.address, prev: s.data.Nonce, }) - if s.db.logger != nil { + if s.db.logger != nil && s.db.logger.OnNonceChange != nil { s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce) } s.setNonce(nonce) diff --git a/core/state/statedb.go b/core/state/statedb.go index 0c2a5b5aef..5eabbcaeb3 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" @@ -77,7 +78,7 @@ type StateDB struct { prefetcher *triePrefetcher trie Trie hasher crypto.KeccakState - logger StateLogger + logger *live.LiveLogger snaps *snapshot.Tree // Nil if snapshot is not available snap snapshot.Snapshot // Nil if snapshot is not available @@ -188,7 +189,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) } // SetLogger sets the logger for account update hooks. -func (s *StateDB) SetLogger(l StateLogger) { +func (s *StateDB) SetLogger(l *live.LiveLogger) { s.logger = l } @@ -232,7 +233,7 @@ func (s *StateDB) AddLog(log *types.Log) { log.TxHash = s.thash log.TxIndex = uint(s.txIndex) log.Index = s.logSize - if s.logger != nil { + if s.logger != nil && s.logger.OnLog != nil { s.logger.OnLog(log) } s.logs[s.thash] = append(s.logs[s.thash], log) @@ -479,7 +480,7 @@ func (s *StateDB) SelfDestruct(addr common.Address) { prev: stateObject.selfDestructed, prevbalance: new(big.Int).Set(stateObject.Balance()), }) - if s.logger != nil && prev.Sign() > 0 { + if s.logger != nil && s.logger.OnBalanceChange != nil && prev.Sign() > 0 { s.logger.OnBalanceChange(addr, prev, n, BalanceDecreaseSelfdestruct) } stateObject.markSelfdestructed() @@ -868,7 +869,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { obj.deleted = true // If ether was sent to account post-selfdestruct it is burnt. - if bal := obj.Balance(); s.logger != nil && obj.selfDestructed && bal.Sign() != 0 { + if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 { s.logger.OnBalanceChange(obj.address, bal, new(big.Int), BalanceDecreaseSelfdestructBurn) } // We need to maintain account deletions explicitly (will remain diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 3eff0d25db..7750517e06 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -20,15 +20,17 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/log" ) // Config are the configuration options for the Interpreter type Config struct { Tracer EVMLogger // Opcode logger - NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) - EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages - ExtraEips []int // Additional EIPS that are to be enabled + LiveLogger *live.LiveLogger + NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) + EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages + ExtraEips []int // Additional EIPS that are to be enabled } // ScopeContext contains the things that are per-call, such as stack and memory, diff --git a/eth/backend.go b/eth/backend.go index 1ba7552d76..abb6a7e7c2 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -193,6 +193,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, Tracer: config.VMTracer, + LiveLogger: config.LiveLogger, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 3ff1ae0013..b5e2f2368c 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" @@ -153,7 +154,8 @@ type Config struct { EnablePreimageRecording bool // Enables VM tracing - VMTracer vm.EVMLogger + VMTracer vm.EVMLogger + LiveLogger *live.LiveLogger // Miscellaneous options DocRoot string `toml:"-"` diff --git a/eth/tracers/directory/live/dir.go b/eth/tracers/directory/live/dir.go index 39d1e99caa..f6656ac79f 100644 --- a/eth/tracers/directory/live/dir.go +++ b/eth/tracers/directory/live/dir.go @@ -3,11 +3,44 @@ package live import ( "encoding/json" "errors" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" ) -type ctorFunc func(config json.RawMessage) (core.BlockchainLogger, error) +type LiveLogger struct { + VMLogger vm.EVMLogger + + /* + - Chain events - + */ + OnBlockchainInit func(chainConfig *params.ChainConfig) + // OnBlockStart is called before executing `block`. + // `td` is the total difficulty prior to `block`. + OnBlockStart func(event core.BlockEvent) + OnBlockEnd func(err error) + // OnSkippedBlock indicates a block was skipped during processing + // due to it being known previously. This can happen e.g. when recovering + // from a crash. + OnSkippedBlock func(event core.BlockEvent) + OnGenesisBlock func(genesis *types.Block, alloc core.GenesisAlloc) + + /* + - State events - + */ + OnBalanceChange func(addr common.Address, prev, new *big.Int, reason state.BalanceChangeReason) + OnNonceChange func(addr common.Address, prev, new uint64) + OnCodeChange func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) + OnStorageChange func(addr common.Address, slot common.Hash, prev, new common.Hash) + OnLog func(log *types.Log) +} + +type ctorFunc func(config json.RawMessage) (*LiveLogger, error) // Directory is the collection of tracers which can be used // during normal block import operations. @@ -23,7 +56,7 @@ func (d *directory) Register(name string, f ctorFunc) { } // New instantiates a tracer by name. -func (d *directory) New(name string, config json.RawMessage) (core.BlockchainLogger, error) { +func (d *directory) New(name string, config json.RawMessage) (*LiveLogger, error) { if f, ok := d.elems[name]; ok { return f(config) } diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index fe835e1d81..f9b8e19def 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -23,8 +23,21 @@ func init() { // as soon as we have a real live tracer. type noop struct{} -func newNoopTracer(_ json.RawMessage) (core.BlockchainLogger, error) { - return &noop{}, nil +func newNoopTracer(_ json.RawMessage) (*live.LiveLogger, error) { + t := &noop{} + return &live.LiveLogger{ + VMLogger: t, + OnBlockchainInit: t.OnBlockchainInit, + OnBlockStart: t.OnBlockStart, + OnBlockEnd: t.OnBlockEnd, + OnSkippedBlock: t.OnSkippedBlock, + OnGenesisBlock: t.OnGenesisBlock, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, + }, nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation.