diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c05e76e1a5..6a2d788a4a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -54,6 +54,7 @@ import ( "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/directory" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/remotedb" "github.com/ethereum/go-ethereum/ethstats" @@ -518,9 +519,9 @@ var ( Usage: "Record information useful for VM and contract debugging", Category: flags.VMCategory, } - VMTraceFlag = &cli.BoolFlag{ + VMTraceFlag = &cli.StringFlag{ Name: "vmtrace", - Usage: "Record internal VM operations (costly)", + Usage: "Name of tracer which should record internal VM operations (costly)", Category: flags.VMCategory, } @@ -1723,9 +1724,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // TODO(fjl): force-enable this in --dev mode cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) } - if ctx.IsSet(VMTraceFlag.Name) { - cfg.LiveTrace = ctx.Bool(VMTraceFlag.Name) - } if ctx.IsSet(RPCGlobalGasCapFlag.Name) { cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name) @@ -2143,7 +2141,13 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh } vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name)} if ctx.IsSet(VMTraceFlag.Name) { - vmcfg.Tracer = tracers.NewPrinter() + if name := ctx.String(VMTraceFlag.Name); name != "" { + t, err := directory.LiveDirectory.New(name) + if err != nil { + Fatalf("Failed to create tracer %q: %v", name, err) + } + vmcfg.Tracer = t + } } // Disable transaction indexing/unindexing by default. chain, err := core.NewBlockChain(chainDb, cache, gspec, nil, engine, vmcfg, nil, nil) diff --git a/core/blockchain.go b/core/blockchain.go index 3ee20a9aa3..75c4e3f9bf 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -406,6 +406,21 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis } } + if bc.logger != nil { + if block := bc.CurrentBlock(); block.Number.Uint64() == 0 { + alloc, err := getGenesisState(bc.db, block.Hash()) + if err != nil { + return nil, fmt.Errorf("failed to get genesis state: %w", err) + } + + if alloc == nil { + return nil, fmt.Errorf("live blockchain tracer requires genesis alloc to be set") + } + + bc.logger.OnGenesisBlock(bc.genesisBlock, alloc) + } + } + // Load any existing snapshot, regenerating it if loading failed if bc.cacheConfig.SnapshotLimit > 0 { // If the chain was rewound past the snapshot persistent layer (causing @@ -1759,120 +1774,117 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) // Process block using the parent state as reference point pstart := time.Now() - if bc.logger != nil { - td := bc.GetTd(block.ParentHash(), block.NumberU64()-1) - bc.logger.OnBlockStart(block, td, bc.CurrentFinalBlock(), bc.CurrentSafeBlock()) - } - receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) - if err != nil { - bc.reportBlock(block, receipts, err) + // The traced section of block import. + err, stop := func() (blockEndErr error, _ bool) { + if bc.logger != nil { + td := bc.GetTd(block.ParentHash(), block.NumberU64()-1) + bc.logger.OnBlockStart(block, td, bc.CurrentFinalBlock(), bc.CurrentSafeBlock()) + defer func() { + bc.logger.OnBlockEnd(blockEndErr) + }() + } + receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) + if err != nil { + bc.reportBlock(block, receipts, err) + followupInterrupt.Store(true) + return err, true + } + ptime := time.Since(pstart) + + vstart := time.Now() + if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { + bc.reportBlock(block, receipts, err) + followupInterrupt.Store(true) + return err, true + } + vtime := time.Since(vstart) + proctime := time.Since(start) // processing + validation + + // Update the metrics touched during block processing and validation + accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) + storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) + snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing) + snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing) + accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) + storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) + accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) + storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) + triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing + trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update + trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read + trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read + blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing + blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation + + // Write the block to the chain and get the status. + var ( + wstart = time.Now() + status WriteStatus + ) + if !setHead { + // Don't set the head, only insert the block + err = bc.writeBlockWithState(block, receipts, statedb) + } else { + status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) + } followupInterrupt.Store(true) - if bc.logger != nil { - bc.logger.OnBlockEnd(err) + if err != nil { + return err, true } - return it.index, err - } - ptime := time.Since(pstart) + // Update the metrics touched during block commit + accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them + storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them + snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them + triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them - vstart := time.Now() - if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { - bc.reportBlock(block, receipts, err) - followupInterrupt.Store(true) - if bc.logger != nil { - bc.logger.OnBlockEnd(err) + blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) + blockInsertTimer.UpdateSince(start) + + // Report the import stats before returning the various results + stats.processed++ + stats.usedGas += usedGas + + dirty, _ := bc.triedb.Size() + stats.report(chain, it.index, dirty, setHead) + + if !setHead { + // After merge we expect few side chains. Simply count + // all blocks the CL gives us for GC processing time + bc.gcproc += proctime + + return nil, true // Direct block insertion of a single block } - return it.index, err - } - vtime := time.Since(vstart) - proctime := time.Since(start) // processing + validation + switch status { + case CanonStatTy: + log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(), + "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), + "elapsed", common.PrettyDuration(time.Since(start)), + "root", block.Root()) - // Update the metrics touched during block processing and validation - accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) - storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) - snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing) - snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing) - accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) - storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) - accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) - storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) - triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing - trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update - trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read - trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read - blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing - blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation + lastCanon = block - // Write the block to the chain and get the status. - var ( - wstart = time.Now() - status WriteStatus - ) - if !setHead { - // Don't set the head, only insert the block - err = bc.writeBlockWithState(block, receipts, statedb) - } else { - status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) - } - followupInterrupt.Store(true) - if err != nil { - if bc.logger != nil { - bc.logger.OnBlockEnd(err) + // Only count canonical blocks for GC processing time + bc.gcproc += proctime + + case SideStatTy: + log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(), + "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), + "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), + "root", block.Root()) + + default: + // This in theory is impossible, but lets be nice to our future selves and leave + // a log, instead of trying to track down blocks imports that don't emit logs. + log.Warn("Inserted block with unknown status", "number", block.Number(), "hash", block.Hash(), + "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), + "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), + "root", block.Root()) } + return nil, false + }() + if err != nil || stop { return it.index, err } - // Update the metrics touched during block commit - accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them - storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them - snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them - triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them - - blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) - blockInsertTimer.UpdateSince(start) - - // Report the import stats before returning the various results - stats.processed++ - stats.usedGas += usedGas - - dirty, _ := bc.triedb.Size() - stats.report(chain, it.index, dirty, setHead) - - if bc.logger != nil { - bc.logger.OnBlockEnd(nil) - } - - if !setHead { - // After merge we expect few side chains. Simply count - // all blocks the CL gives us for GC processing time - bc.gcproc += proctime - - return it.index, nil // Direct block insertion of a single block - } - switch status { - case CanonStatTy: - log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(), - "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), - "elapsed", common.PrettyDuration(time.Since(start)), - "root", block.Root()) - - lastCanon = block - - // Only count canonical blocks for GC processing time - bc.gcproc += proctime - - case SideStatTy: - log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(), - "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), - "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), - "root", block.Root()) - - default: - // This in theory is impossible, but lets be nice to our future selves and leave - // a log, instead of trying to track down blocks imports that don't emit logs. - log.Warn("Inserted block with unknown status", "number", block.Number(), "hash", block.Hash(), - "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), - "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), - "root", block.Root()) - } } // Any blocks remaining here? The only ones we care about are the future ones diff --git a/core/genesis.go b/core/genesis.go index e274f5a79b..54ec15cba4 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -176,36 +176,49 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas return nil } -// CommitGenesisState loads the stored genesis state with the given block -// hash and commits it into the provided trie database. -func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error { - var alloc GenesisAlloc +func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc GenesisAlloc, err error) { blob := rawdb.ReadGenesisStateSpec(db, blockhash) if len(blob) != 0 { if err := alloc.UnmarshalJSON(blob); err != nil { - return err - } - } else { - // Genesis allocation is missing and there are several possibilities: - // the node is legacy which doesn't persist the genesis allocation or - // the persisted allocation is just lost. - // - supported networks(mainnet, testnets), recover with defined allocations - // - private network, can't recover - var genesis *Genesis - switch blockhash { - case params.MainnetGenesisHash: - genesis = DefaultGenesisBlock() - case params.GoerliGenesisHash: - genesis = DefaultGoerliGenesisBlock() - case params.SepoliaGenesisHash: - genesis = DefaultSepoliaGenesisBlock() - } - if genesis != nil { - alloc = genesis.Alloc - } else { - return errors.New("not found") + return nil, err } + + return alloc, nil } + + // Genesis allocation is missing and there are several possibilities: + // the node is legacy which doesn't persist the genesis allocation or + // the persisted allocation is just lost. + // - supported networks(mainnet, testnets), recover with defined allocations + // - private network, can't recover + var genesis *Genesis + switch blockhash { + case params.MainnetGenesisHash: + genesis = DefaultGenesisBlock() + case params.GoerliGenesisHash: + genesis = DefaultGoerliGenesisBlock() + case params.SepoliaGenesisHash: + genesis = DefaultSepoliaGenesisBlock() + } + if genesis != nil { + return genesis.Alloc, nil + } + + return nil, nil +} + +// CommitGenesisState loads the stored genesis state with the given block +// hash and commits it into the provided trie database. +func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error { + alloc, err := getGenesisState(db, blockhash) + if err != nil { + return err + } + + if alloc == nil { + return errors.New("not found") + } + return alloc.flush(db, triedb, blockhash, nil) } diff --git a/core/state/statedb.go b/core/state/statedb.go index 3f52b9f736..b3ee524fc1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -56,6 +56,8 @@ func (n *proofList) Delete(key []byte) error { // StateLogger is used to collect state update traces from EVM transaction // execution. +// The following hooks are invoked post execution. I.e. looking up state +// after the hook should reflect the new value. // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. type StateLogger interface { diff --git a/eth/backend.go b/eth/backend.go index 6cb5611d32..667200bced 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -44,7 +44,6 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" - "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -194,9 +193,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { Preimages: config.Preimages, } ) - if config.LiveTrace { - vmConfig.Tracer = tracers.NewPrinter() - } // Override the chain config with provided settings. var overrides core.ChainOverrides if config.OverrideCancun != nil { diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 55dd56316e..4bc8b8dc6c 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -140,9 +140,6 @@ type Config struct { // Enables tracking of SHA3 preimages in the VM EnablePreimageRecording bool - // LiveTrace will enable tracing during normal chain processing. - LiveTrace bool - // Miscellaneous options DocRoot string `toml:"-"` diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 1d3c57dc7c..69fe38ba20 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -37,6 +37,7 @@ import ( "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/eth/tracers/directory" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -272,7 +273,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee()) - txctx := &Context{ + txctx := &directory.Context{ BlockHash: task.block.Hash(), BlockNumber: task.block.Number(), TxIndex: i, @@ -591,7 +592,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac // process that generates states in one thread and traces txes // in separate worker threads. if config != nil && config.Tracer != nil && *config.Tracer != "" { - if isJS := DefaultDirectory.IsJS(*config.Tracer); isJS { + if isJS := directory.DefaultDirectory.IsJS(*config.Tracer); isJS { return api.traceBlockParallel(ctx, block, statedb, config) } } @@ -607,7 +608,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac for i, tx := range txs { // Generate the next state snapshot fast without tracing msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) - txctx := &Context{ + txctx := &directory.Context{ BlockHash: blockHash, BlockNumber: block.Number(), TxIndex: i, @@ -650,7 +651,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat // Fetch and execute the next transaction trace tasks for task := range jobs { msg, _ := core.TransactionToMessage(txs[task.index], signer, block.BaseFee()) - txctx := &Context{ + txctx := &directory.Context{ BlockHash: blockHash, BlockNumber: block.Number(), TxIndex: task.index, @@ -859,7 +860,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * return nil, err } - txctx := &Context{ + txctx := &directory.Context{ BlockHash: blockHash, BlockNumber: block.Number(), TxIndex: int(index), @@ -927,15 +928,15 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc if config != nil { traceConfig = &config.TraceConfig } - return api.traceTx(ctx, tx, msg, new(Context), vmctx, statedb, traceConfig) + return api.traceTx(ctx, tx, msg, new(directory.Context), vmctx, statedb, traceConfig) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *directory.Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { var ( - tracer Tracer + tracer directory.Tracer err error timeout = defaultTraceTimeout txContext = core.NewEVMTxContext(message) @@ -946,7 +947,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor // Default tracer is the struct logger tracer = logger.NewStructLogger(config.Config) if config.Tracer != nil { - tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig) + tracer, err = directory.DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig) if err != nil { return nil, err } diff --git a/eth/tracers/directory/live.go b/eth/tracers/directory/live.go new file mode 100644 index 0000000000..dc6927b142 --- /dev/null +++ b/eth/tracers/directory/live.go @@ -0,0 +1,30 @@ +package directory + +import ( + "errors" + + "github.com/ethereum/go-ethereum/core" +) + +type ctorFunc func() (core.BlockchainLogger, error) + +// LiveDirectory is the collection of tracers which can be used +// during normal block import operations. +var LiveDirectory = liveDirectory{elems: make(map[string]ctorFunc)} + +type liveDirectory struct { + elems map[string]ctorFunc +} + +// Register registers a tracer constructor by name. +func (d *liveDirectory) Register(name string, f ctorFunc) { + d.elems[name] = f +} + +// New instantiates a tracer by name. +func (d *liveDirectory) New(name string) (core.BlockchainLogger, error) { + if f, ok := d.elems[name]; ok { + return f() + } + return nil, errors.New("not found") +} diff --git a/eth/tracers/noop.go b/eth/tracers/directory/noop.go similarity index 99% rename from eth/tracers/noop.go rename to eth/tracers/directory/noop.go index 428c13fef4..973b1abb47 100644 --- a/eth/tracers/noop.go +++ b/eth/tracers/directory/noop.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package tracers +package directory import ( "encoding/json" diff --git a/eth/tracers/tracers.go b/eth/tracers/directory/tracers.go similarity index 80% rename from eth/tracers/tracers.go rename to eth/tracers/directory/tracers.go index c82fcf92ac..b16d9d2fe6 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/directory/tracers.go @@ -14,13 +14,13 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// Package tracers is a manager for transaction tracing engines. -package tracers +// Package directory provides functionality to lookup tracers by name. +// It also includes utility functions that are imported by the other +// tracing packages. +package directory import ( "encoding/json" - "errors" - "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -101,27 +101,3 @@ func (d *directory) IsJS(name string) bool { // JS eval will execute JS code return true } - -const ( - memoryPadLimit = 1024 * 1024 -) - -// GetMemoryCopyPadded returns offset + size as a new slice. -// It zero-pads the slice if it extends beyond memory bounds. -func GetMemoryCopyPadded(m *vm.Memory, offset, size int64) ([]byte, error) { - if offset < 0 || size < 0 { - return nil, errors.New("offset or size must not be negative") - } - if int(offset+size) < m.Len() { // slice fully inside memory - return m.GetCopy(offset, size), nil - } - paddingNeeded := int(offset+size) - m.Len() - if paddingNeeded > memoryPadLimit { - return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded) - } - cpy := make([]byte, size) - if overlap := int64(m.Len()) - offset; overlap > 0 { - copy(cpy, m.GetPtr(offset, overlap)) - } - return cpy, nil -} diff --git a/eth/tracers/directory/util.go b/eth/tracers/directory/util.go new file mode 100644 index 0000000000..92de735291 --- /dev/null +++ b/eth/tracers/directory/util.go @@ -0,0 +1,47 @@ +// Copyright 2023 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 directory + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/core/vm" +) + +const ( + memoryPadLimit = 1024 * 1024 +) + +// GetMemoryCopyPadded returns offset + size as a new slice. +// It zero-pads the slice if it extends beyond memory bounds. +func GetMemoryCopyPadded(m *vm.Memory, offset, size int64) ([]byte, error) { + if offset < 0 || size < 0 { + return nil, errors.New("offset or size must not be negative") + } + if int(offset+size) < m.Len() { // slice fully inside memory + return m.GetCopy(offset, size), nil + } + paddingNeeded := int(offset+size) - m.Len() + if paddingNeeded > memoryPadLimit { + return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded) + } + cpy := make([]byte, size) + if overlap := int64(m.Len()) - offset; overlap > 0 { + copy(cpy, m.GetPtr(offset, overlap)) + } + return cpy, nil +} diff --git a/eth/tracers/directory/util_test.go b/eth/tracers/directory/util_test.go new file mode 100644 index 0000000000..c72096708d --- /dev/null +++ b/eth/tracers/directory/util_test.go @@ -0,0 +1,60 @@ +// Copyright 2023 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 directory + +import ( + "testing" + + "github.com/ethereum/go-ethereum/core/vm" +) + +func TestMemCopying(t *testing.T) { + for i, tc := range []struct { + memsize int64 + offset int64 + size int64 + wantErr string + wantSize int + }{ + {0, 0, 100, "", 100}, // Should pad up to 100 + {0, 100, 0, "", 0}, // No need to pad (0 size) + {100, 50, 100, "", 100}, // Should pad 100-150 + {100, 50, 5, "", 5}, // Wanted range fully within memory + {100, -50, 0, "offset or size must not be negative", 0}, // Errror + {0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Errror + {10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Errror + + } { + mem := vm.NewMemory() + mem.Resize(uint64(tc.memsize)) + cpy, err := GetMemoryCopyPadded(mem, tc.offset, tc.size) + if want := tc.wantErr; want != "" { + if err == nil { + t.Fatalf("test %d: want '%v' have no error", i, want) + } + if have := err.Error(); want != have { + t.Fatalf("test %d: want '%v' have '%v'", i, want, have) + } + continue + } + if err != nil { + t.Fatalf("test %d: unexpected error: %v", i, err) + } + if want, have := tc.wantSize, len(cpy); have != want { + t.Fatalf("test %d: want %v have %v", i, want, have) + } + } +} diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index a0001118a8..d342f2109d 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/directory" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" @@ -141,7 +141,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { } _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) ) - tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) + tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), test.TracerConfig) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -247,7 +247,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil) + tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), nil) if err != nil { b.Fatalf("failed to create call tracer: %v", err) } @@ -283,8 +283,8 @@ func TestInternals(t *testing.T) { BaseFee: new(big.Int), } ) - mkTracer := func(name string, cfg json.RawMessage) tracers.Tracer { - tr, err := tracers.DefaultDirectory.New(name, nil, cfg) + mkTracer := func(name string, cfg json.RawMessage) directory.Tracer { + tr, err := directory.DefaultDirectory.New(name, nil, cfg) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -294,7 +294,7 @@ func TestInternals(t *testing.T) { for _, tc := range []struct { name string code []byte - tracer tracers.Tracer + tracer directory.Tracer want string }{ { diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index f8ca103314..be552ccfd5 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -16,11 +16,9 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/directory" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" - - // Force-load the native, to trigger registration - "github.com/ethereum/go-ethereum/eth/tracers" ) // flatCallTrace is the result of a callTracerParity run. @@ -103,7 +101,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) // Create the tracer, the EVM environment and run it - tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) + tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), test.TracerConfig) if err != nil { return fmt.Errorf("failed to create call tracer: %v", err) } diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index 74d5492d52..dc2e7293b9 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -29,7 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/directory" "github.com/ethereum/go-ethereum/tests" ) @@ -110,7 +110,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { } _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) ) - tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) + tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), test.TracerConfig) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index 8ed55ef1c6..c21b01525a 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -24,12 +24,12 @@ import ( "github.com/dop251/goja" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers/directory" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/tracers" jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers" ) @@ -42,16 +42,16 @@ func init() { if err != nil { panic(err) } - type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error) + type ctorFn = func(*directory.Context, json.RawMessage) (directory.Tracer, error) lookup := func(code string) ctorFn { - return func(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + return func(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { return newJsTracer(code, ctx, cfg) } } for name, code := range assetTracers { - tracers.DefaultDirectory.Register(name, lookup(code), true) + directory.DefaultDirectory.Register(name, lookup(code), true) } - tracers.DefaultDirectory.RegisterJSEval(newJsTracer) + directory.DefaultDirectory.RegisterJSEval(newJsTracer) } // bigIntProgram is compiled once and the exported function mostly invoked to convert @@ -96,7 +96,7 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b // jsTracer is an implementation of the Tracer interface which evaluates // JS functions on the relevant EVM hooks. It uses Goja as its JS engine. type jsTracer struct { - tracers.NoopTracer + directory.NoopTracer vm *goja.Runtime env *vm.EVM @@ -136,7 +136,7 @@ type jsTracer struct { // The methods `result` and `fault` are required to be present. // The methods `step`, `enter`, and `exit` are optional, but note that // `enter` and `exit` always go together. -func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { +func newJsTracer(code string, ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { vm := goja.New() // By default field names are exported to JS as is, i.e. capitalized. vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) @@ -145,7 +145,7 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer ctx: make(map[string]goja.Value), } if ctx == nil { - ctx = new(tracers.Context) + ctx = new(directory.Context) } if ctx.BlockHash != (common.Hash{}) { t.ctx["blockHash"] = vm.ToValue(ctx.BlockHash.Bytes()) @@ -576,7 +576,7 @@ func (mo *memoryObj) slice(begin, end int64) ([]byte, error) { if end < begin || begin < 0 { return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end) } - slice, err := tracers.GetMemoryCopyPadded(mo.memory, begin, end-begin) + slice, err := directory.GetMemoryCopyPadded(mo.memory, begin, end-begin) if err != nil { return nil, err } diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 4a7d6c5bcd..0706690c89 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -28,7 +28,7 @@ import ( "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/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/directory" "github.com/ethereum/go-ethereum/params" ) @@ -61,7 +61,7 @@ func testCtx() *vmContext { return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} } -func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) { +func runTrace(tracer directory.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) { var ( env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Tracer: tracer}) gasLimit uint64 = 31000 @@ -264,14 +264,14 @@ func TestIsPrecompile(t *testing.T) { func TestEnterExit(t *testing.T) { // test that either both or none of enter() and exit() are defined - if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context), nil); err == nil { + if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(directory.Context), nil); err == nil { t.Fatal("tracer creation should've failed without exit() definition") } - if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context), nil); err != nil { + if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(directory.Context), nil); err != nil { t.Fatal(err) } // test that the enter and exit method are correctly invoked and the values passed - tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context), nil) + tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(directory.Context), nil) if err != nil { t.Fatal(err) } @@ -293,7 +293,7 @@ func TestEnterExit(t *testing.T) { func TestSetup(t *testing.T) { // Test empty config - _, err := newJsTracer(`{setup: function(cfg) { if (cfg !== "{}") { throw("invalid empty config") } }, fault: function() {}, result: function() {}}`, new(tracers.Context), nil) + _, err := newJsTracer(`{setup: function(cfg) { if (cfg !== "{}") { throw("invalid empty config") } }, fault: function() {}, result: function() {}}`, new(directory.Context), nil) if err != nil { t.Error(err) } @@ -303,12 +303,12 @@ func TestSetup(t *testing.T) { t.Fatal(err) } // Test no setup func - _, err = newJsTracer(`{fault: function() {}, result: function() {}}`, new(tracers.Context), cfg) + _, err = newJsTracer(`{fault: function() {}, result: function() {}}`, new(directory.Context), cfg) if err != nil { t.Fatal(err) } // Test config value - tracer, err := newJsTracer("{config: null, setup: function(cfg) { this.config = JSON.parse(cfg) }, step: function() {}, fault: function() {}, result: function() { return this.config.foo }}", new(tracers.Context), cfg) + tracer, err := newJsTracer("{config: null, setup: function(cfg) { this.config = JSON.parse(cfg) }, step: function() {}, fault: function() {}, result: function() { return this.config.foo }}", new(directory.Context), cfg) if err != nil { t.Fatal(err) } diff --git a/eth/tracers/printer.go b/eth/tracers/live/printer.go similarity index 93% rename from eth/tracers/printer.go rename to eth/tracers/live/printer.go index 3f82107dad..1f51a6fc3f 100644 --- a/eth/tracers/printer.go +++ b/eth/tracers/live/printer.go @@ -1,4 +1,4 @@ -package tracers +package live import ( "encoding/json" @@ -8,14 +8,20 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "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/eth/tracers/directory" ) +func init() { + directory.LiveDirectory.Register("printer", newPrinter) +} + type Printer struct{} -func NewPrinter() *Printer { - return &Printer{} +func newPrinter() (core.BlockchainLogger, error) { + return &Printer{}, nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. @@ -91,7 +97,7 @@ func (p *Printer) OnGenesisBlock(b *types.Block, alloc core.GenesisAlloc) { fmt.Printf("OnGenesisBlock: b=%v, allocLength=%d\n", b.NumberU64(), len(alloc)) } -func (p *Printer) OnBalanceChange(a common.Address, prev, new *big.Int) { +func (p *Printer) OnBalanceChange(a common.Address, prev, new *big.Int, reason state.BalanceChangeReason) { fmt.Printf("OnBalanceChange: a=%v, prev=%v, new=%v\n", a, prev, new) } diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 0c246bef4c..7df66066b5 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -17,11 +17,10 @@ package logger import ( - "math/big" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/directory" ) // accessList is an accumulator for the set of accounts and storage slots an EVM @@ -103,6 +102,7 @@ func (al accessList) accessList() types.AccessList { // AccessListTracer is a tracer that accumulates touched accounts and storage // slots into an internal set. type AccessListTracer struct { + directory.NoopTracer excl map[common.Address]struct{} // Set of account to exclude from the list list accessList // Set of accounts and storage slots touched } @@ -132,9 +132,6 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi } } -func (a *AccessListTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { -} - // CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist. func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { stack := scope.Stack @@ -158,37 +155,6 @@ func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6 } } -func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { -} - -func (*AccessListTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {} - -func (*AccessListTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {} - -func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { -} - -func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {} - -func (*AccessListTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction) {} - -func (*AccessListTracer) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (*AccessListTracer) OnBalanceChange(a common.Address, prev, new *big.Int) {} - -func (*AccessListTracer) OnNonceChange(a common.Address, prev, new uint64) {} - -func (*AccessListTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { -} - -func (*AccessListTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {} - -func (*AccessListTracer) OnLog(log *types.Log) {} - -func (*AccessListTracer) OnNewAccount(a common.Address) {} - // AccessList returns the current accesslist maintained by the tracer. func (a *AccessListTracer) AccessList() types.AccessList { return a.list.accessList() diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 3858bbd8f7..22294252af 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -28,9 +28,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" - "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/eth/tracers/directory" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -107,6 +107,7 @@ func (s *StructLog) ErrorString() string { // a track record of modified storage which is used in reporting snapshots of the // contract their storage. type StructLogger struct { + directory.NoopTracer cfg Config env *vm.EVM @@ -139,10 +140,6 @@ func (l *StructLogger) Reset() { l.err = nil } -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { -} - // CaptureState logs a new structured log message and pushes it out to the environment // // CaptureState also tracks SLOAD/SSTORE ops to track storage change. @@ -211,16 +208,6 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s l.logs = append(l.logs, log) } -// CaptureFault implements the EVMLogger interface to trace an execution fault -// while running an opcode. -func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { -} - -// CaptureKeccakPreimage is called during the KECCAK256 opcode. -func (l *StructLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {} - -func (l *StructLogger) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - // CaptureEnd is called after the call finishes to finalize the tracing. func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { l.output = output @@ -233,12 +220,6 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { } } -func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { -} - -func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) { -} - func (l *StructLogger) GetResult() (json.RawMessage, error) { // Tracing aborted if l.reason != nil { @@ -280,20 +261,6 @@ func (l *StructLogger) CaptureTxEnd(receipt *types.Receipt, err error) { l.usedGas = receipt.GasUsed } -func (l *StructLogger) OnBalanceChange(a common.Address, prev, new *big.Int, reason state.BalanceChangeReason) { -} - -func (l *StructLogger) OnNonceChange(a common.Address, prev, new uint64) {} - -func (l *StructLogger) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { -} - -func (l *StructLogger) OnStorageChange(a common.Address, k, prev, new common.Hash) {} - -func (l *StructLogger) OnLog(log *types.Log) {} - -func (l *StructLogger) OnNewAccount(a common.Address) {} - // StructLogs returns the captured log entries. func (l *StructLogger) StructLogs() []StructLog { return l.logs } @@ -351,6 +318,7 @@ func WriteLogs(writer io.Writer, logs []*types.Log) { } type mdLogger struct { + directory.NoopTracer out io.Writer cfg *Config env *vm.EVM @@ -408,37 +376,11 @@ func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) } -func (t *mdLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {} - -func (t *mdLogger) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n", output, gasUsed, err) } -func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { -} - -func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} - -func (*mdLogger) CaptureTxStart(env *vm.EVM, tx *types.Transaction) {} - -func (*mdLogger) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (*mdLogger) OnBalanceChange(a common.Address, prev, new *big.Int) {} - -func (*mdLogger) OnNonceChange(a common.Address, prev, new uint64) {} - -func (*mdLogger) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { -} - -func (*mdLogger) OnStorageChange(a common.Address, k, prev, new common.Hash) {} - -func (*mdLogger) OnLog(log *types.Log) {} - -func (*mdLogger) OnNewAccount(a common.Address) {} - // ExecutionResult groups all structured logs emitted by the EVM // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 6e687c7e64..1ea014a0d6 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -19,15 +19,16 @@ package logger import ( "encoding/json" "io" - "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/directory" ) type JSONLogger struct { + directory.NoopTracer encoder *json.Encoder cfg *Config env *vm.EVM @@ -43,9 +44,6 @@ func NewJSONLogger(cfg *Config, writer io.Writer) *JSONLogger { return l } -func (l *JSONLogger) CaptureStart(from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { -} - func (l *JSONLogger) CaptureFault(pc uint64, op vm.OpCode, gas uint64, cost uint64, scope *vm.ScopeContext, depth int, err error) { // TODO: Add rData to this interface as well l.CaptureState(pc, op, gas, cost, scope, nil, depth, err) @@ -78,11 +76,6 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco l.encoder.Encode(log) } -// CaptureKeccakPreimage is called during the KECCAK256 opcode. -func (l *JSONLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {} - -func (l *JSONLogger) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - // CaptureEnd is triggered at end of execution. func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { type endLog struct { @@ -97,26 +90,6 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg}) } -func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { -} - -func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} - func (l *JSONLogger) CaptureTxStart(env *vm.EVM, tx *types.Transaction) { l.env = env } - -func (l *JSONLogger) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (*JSONLogger) OnBalanceChange(a common.Address, prev, new *big.Int) {} - -func (*JSONLogger) OnNonceChange(a common.Address, prev, new uint64) {} - -func (*JSONLogger) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { -} - -func (*JSONLogger) OnStorageChange(a common.Address, k, prev, new common.Hash) {} - -func (*JSONLogger) OnLog(log *types.Log) {} - -func (*JSONLogger) OnNewAccount(a common.Address) {} diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index 4a7db6e72f..e6c3be41cd 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -60,6 +60,7 @@ func TestStoreCapture(t *testing.T) { ) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} var index common.Hash + logger.CaptureTxStart(env, nil) logger.CaptureStart(common.Address{}, contract.Address(), false, nil, 0, nil) _, err := env.Interpreter().Run(contract, []byte{}, false) if err != nil { diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 25152a098e..b25ff06ae2 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -25,11 +25,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/directory" ) func init() { - tracers.DefaultDirectory.Register("4byteTracer", newFourByteTracer, false) + directory.DefaultDirectory.Register("4byteTracer", newFourByteTracer, false) } // fourByteTracer searches for 4byte-identifiers, and collects them for post-processing. @@ -47,7 +47,7 @@ func init() { // 0xc281d19e-0: 1 // } type fourByteTracer struct { - tracers.NoopTracer + directory.NoopTracer env *vm.EVM ids map[string]int // ids aggregates the 4byte ids found interrupt atomic.Bool // Atomic flag to signal execution interruption @@ -57,7 +57,7 @@ type fourByteTracer struct { // newFourByteTracer returns a native go tracer which collects // 4 byte-identifiers of a tx, and implements vm.EVMLogger. -func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { +func newFourByteTracer(ctx *directory.Context, _ json.RawMessage) (directory.Tracer, error) { t := &fourByteTracer{ ids: make(map[string]int), } diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 3af18cd3ce..bb2ea19b54 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -27,13 +27,13 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/directory" ) //go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go func init() { - tracers.DefaultDirectory.Register("callTracer", newCallTracer, false) + directory.DefaultDirectory.Register("callTracer", newCallTracer, false) } type callLog struct { @@ -99,7 +99,7 @@ type callFrameMarshaling struct { } type callTracer struct { - tracers.NoopTracer + directory.NoopTracer callstack []callFrame config callTracerConfig gasLimit uint64 @@ -115,7 +115,7 @@ type callTracerConfig struct { // newCallTracer returns a native go tracer which tracks // call frames of a tx, and implements vm.EVMLogger. -func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { +func newCallTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { var config callTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index 084df593a8..d5aebc00fd 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -27,14 +27,14 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/directory" ) //go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go //go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go func init() { - tracers.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false) + directory.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false) } var parityErrorMapping = map[string]string{ @@ -109,12 +109,12 @@ type flatCallResultMarshaling struct { // flatCallTracer reports call frame information of a tx in a flat format, i.e. // as opposed to the nested format of `callTracer`. type flatCallTracer struct { - tracers.NoopTracer + directory.NoopTracer tracer *callTracer config flatCallTracerConfig - ctx *tracers.Context // Holds tracer context data - reason error // Textual reason for the interruption - activePrecompiles []common.Address // Updated on CaptureStart based on given rules + ctx *directory.Context // Holds tracer context data + reason error // Textual reason for the interruption + activePrecompiles []common.Address // Updated on CaptureStart based on given rules } type flatCallTracerConfig struct { @@ -123,7 +123,7 @@ type flatCallTracerConfig struct { } // newFlatCallTracer returns a new flatCallTracer. -func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { +func newFlatCallTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { var config flatCallTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { @@ -133,7 +133,7 @@ func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace // Create inner call tracer with default configuration, don't forward // the OnlyTopCall or WithLog to inner for now - tracer, err := tracers.DefaultDirectory.New("callTracer", ctx, nil) + tracer, err := directory.DefaultDirectory.New("callTracer", ctx, nil) if err != nil { return nil, err } @@ -244,7 +244,7 @@ func (t *flatCallTracer) isPrecompiled(addr common.Address) bool { return false } -func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) { +func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *directory.Context) (output []flatCallFrame, err error) { var frame *flatCallFrame switch input.Type { case vm.CREATE, vm.CREATE2: @@ -343,7 +343,7 @@ func newFlatSelfdestruct(input *callFrame) *flatCallFrame { } } -func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *tracers.Context) { +func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *directory.Context) { if ctx == nil { return } diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index da2381b880..dab284a3a3 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -24,32 +24,32 @@ import ( "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/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/directory" ) func init() { - tracers.DefaultDirectory.Register("muxTracer", newMuxTracer, false) + directory.DefaultDirectory.Register("muxTracer", newMuxTracer, false) } // muxTracer is a go implementation of the Tracer interface which // runs multiple tracers in one go. type muxTracer struct { names []string - tracers []tracers.Tracer + tracers []directory.Tracer } // newMuxTracer returns a new mux tracer. -func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { +func newMuxTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { var config map[string]json.RawMessage if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { return nil, err } } - objects := make([]tracers.Tracer, 0, len(config)) + objects := make([]directory.Tracer, 0, len(config)) names := make([]string, 0, len(config)) for k, v := range config { - t, err := tracers.DefaultDirectory.New(k, ctx, v) + t, err := directory.DefaultDirectory.New(k, ctx, v) if err != nil { return nil, err } diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 9375279cb4..71cd7ce791 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -28,14 +28,14 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/directory" "github.com/ethereum/go-ethereum/log" ) //go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go func init() { - tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false) + directory.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false) } type stateMap = map[common.Address]*account @@ -57,7 +57,7 @@ type accountMarshaling struct { } type prestateTracer struct { - tracers.NoopTracer + directory.NoopTracer env *vm.EVM pre stateMap post stateMap @@ -74,7 +74,7 @@ type prestateTracerConfig struct { DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications } -func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { +func newPrestateTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { var config prestateTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { @@ -143,7 +143,7 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, case stackLen >= 4 && op == vm.CREATE2: offset := stackData[stackLen-2] size := stackData[stackLen-3] - init, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(offset.Uint64()), int64(size.Uint64())) + init, err := directory.GetMemoryCopyPadded(scope.Memory, int64(offset.Uint64()), int64(size.Uint64())) if err != nil { log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size) return diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 759e3a4dd3..b9c0ac526d 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -109,41 +109,3 @@ func BenchmarkTransactionTrace(b *testing.B) { tracer.Reset() } } - -func TestMemCopying(t *testing.T) { - for i, tc := range []struct { - memsize int64 - offset int64 - size int64 - wantErr string - wantSize int - }{ - {0, 0, 100, "", 100}, // Should pad up to 100 - {0, 100, 0, "", 0}, // No need to pad (0 size) - {100, 50, 100, "", 100}, // Should pad 100-150 - {100, 50, 5, "", 5}, // Wanted range fully within memory - {100, -50, 0, "offset or size must not be negative", 0}, // Errror - {0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Errror - {10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Errror - - } { - mem := vm.NewMemory() - mem.Resize(uint64(tc.memsize)) - cpy, err := GetMemoryCopyPadded(mem, tc.offset, tc.size) - if want := tc.wantErr; want != "" { - if err == nil { - t.Fatalf("test %d: want '%v' have no error", i, want) - } - if have := err.Error(); want != have { - t.Fatalf("test %d: want '%v' have '%v'", i, want, have) - } - continue - } - if err != nil { - t.Fatalf("test %d: unexpected error: %v", i, err) - } - if want, have := tc.wantSize, len(cpy); have != want { - t.Fatalf("test %d: want %v have %v", i, want, have) - } - } -}