diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 2cdf559737b..8a8e3ca48c9 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -46,9 +46,9 @@ import ( "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/event" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/turbo/services" @@ -715,7 +715,7 @@ func (b *SimulatedBackend) callContract(_ context.Context, call ethereum.CallMsg } // Set infinite balance to the fake caller account. from := statedb.GetOrNewStateObject(call.From) - from.SetBalance(uint256.NewInt(0).SetAllOne(), evmtypes.BalanceChangeUnspecified) + from.SetBalance(uint256.NewInt(0).SetAllOne(), tracing.BalanceChangeUnspecified) // Execute the call. msg := callMsg{call} diff --git a/cmd/devnet/devnet/node.go b/cmd/devnet/devnet/node.go index b8aa1be220b..e697a081b63 100644 --- a/cmd/devnet/devnet/node.go +++ b/cmd/devnet/devnet/node.go @@ -139,7 +139,7 @@ func (n *devnetNode) EnableMetrics(int) { // run configures, creates and serves an erigon node func (n *devnetNode) run(ctx *cli.Context) error { var logger log.Logger - var tracer tracers.Tracer + var tracer *tracers.Tracer var err error var metricsMux *http.ServeMux diff --git a/cmd/erigon/main.go b/cmd/erigon/main.go index 081821d4663..b4a505c0969 100644 --- a/cmd/erigon/main.go +++ b/cmd/erigon/main.go @@ -43,7 +43,7 @@ func main() { func runErigon(cliCtx *cli.Context) error { var logger log.Logger - var tracer tracers.Tracer + var tracer *tracers.Tracer var err error var metricsMux *http.ServeMux diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 355ec9e2a91..8e520748110 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -29,8 +29,8 @@ import ( "github.com/ledgerwatch/erigon/common/math" "github.com/ledgerwatch/erigon/consensus/ethash" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/turbo/rpchelper" ) @@ -84,7 +84,7 @@ func MakePreState(chainRules *chain.Rules, tx kv.RwTx, accounts types.GenesisAll statedb.SetCode(addr, a.Code) statedb.SetNonce(addr, a.Nonce) balance, _ := uint256.FromBig(a.Balance) - statedb.SetBalance(addr, balance, evmtypes.BalanceIncreaseGenesisBalance) + statedb.SetBalance(addr, balance, tracing.BalanceIncreaseGenesisBalance) for k, v := range a.Storage { key := k val := uint256.NewInt(0).SetBytes(v.Bytes()) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 14401d0f94b..7c91ff484c2 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -30,6 +30,7 @@ import ( "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/common/datadir" "github.com/ledgerwatch/erigon/core/state/temporal" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/log/v3" "github.com/urfave/cli/v2" @@ -100,7 +101,7 @@ func Main(ctx *cli.Context) error { err error baseDir = "" ) - var getTracer func(txIndex int, txHash libcommon.Hash) (vm.EVMLogger, error) + var getTracer func(txIndex int, txHash libcommon.Hash) (*tracing.Hooks, error) // If user specified a basedir, make sure it exists if ctx.IsSet(OutputBasedir.Name) { @@ -127,7 +128,7 @@ func Main(ctx *cli.Context) error { prevFile.Close() } }() - getTracer = func(txIndex int, txHash libcommon.Hash) (vm.EVMLogger, error) { + getTracer = func(txIndex int, txHash libcommon.Hash) (*tracing.Hooks, error) { if prevFile != nil { prevFile.Close() } @@ -136,10 +137,10 @@ func Main(ctx *cli.Context) error { return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err2)) } prevFile = traceFile - return trace_logger.NewJSONLogger(logConfig, traceFile), nil + return trace_logger.NewJSONLogger(logConfig, traceFile).Hooks, nil } } else { - getTracer = func(txIndex int, txHash libcommon.Hash) (tracer vm.EVMLogger, err error) { + getTracer = func(txIndex int, txHash libcommon.Hash) (tracer *tracing.Hooks, err error) { return nil, nil } } diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 6a86f7781fd..2889e13d260 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -135,7 +135,7 @@ func runCmd(ctx *cli.Context) error { } var ( - tracer tracers.Tracer + tracer *tracers.Tracer debugLogger *logger.StructLogger statedb *state.IntraBlockState chainConfig *chain.Config @@ -147,7 +147,7 @@ func runCmd(ctx *cli.Context) error { tracer = logger.NewJSONLogger(logconfig, os.Stdout) } else if ctx.Bool(DebugFlag.Name) { debugLogger = logger.NewStructLogger(logconfig) - tracer = debugLogger + tracer = debugLogger.Tracer() } else { debugLogger = logger.NewStructLogger(logconfig) } @@ -238,7 +238,7 @@ func runCmd(ctx *cli.Context) error { Coinbase: genesisConfig.Coinbase, BlockNumber: new(big.Int).SetUint64(genesisConfig.Number), EVMConfig: vm.Config{ - Tracer: tracer, + Tracer: tracer.Hooks, Debug: ctx.Bool(DebugFlag.Name) || ctx.Bool(MachineFlag.Name), }, } diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 151da18661f..94a5a86c8d3 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -75,9 +75,9 @@ func stateTestCmd(ctx *cli.Context) error { Debug: ctx.Bool(DebugFlag.Name) || ctx.Bool(MachineFlag.Name), } if machineFriendlyOutput { - cfg.Tracer = logger.NewJSONLogger(config, os.Stderr) + cfg.Tracer = logger.NewJSONLogger(config, os.Stderr).Hooks } else if ctx.Bool(DebugFlag.Name) { - cfg.Tracer = logger.NewStructLogger(config) + cfg.Tracer = logger.NewStructLogger(config).Hooks() } if len(ctx.Args().First()) != 0 { diff --git a/cmd/integration/commands/state_stages.go b/cmd/integration/commands/state_stages.go index 5e342009b0b..f7f441e517c 100644 --- a/cmd/integration/commands/state_stages.go +++ b/cmd/integration/commands/state_stages.go @@ -248,8 +248,10 @@ func syncBySmallSteps(db kv.RwDB, miningConfig params.MiningConfig, ctx context. stopAt = 1 } + var structLogger *logger.StructLogger traceStart := func() { - vmConfig.Tracer = logger.NewStructLogger(&logger.LogConfig{}) + structLogger = logger.NewStructLogger(&logger.LogConfig{}) + vmConfig.Tracer = structLogger.Hooks() vmConfig.Debug = true } traceStop := func(id int) { @@ -262,7 +264,7 @@ func syncBySmallSteps(db kv.RwDB, miningConfig params.MiningConfig, ctx context. } encoder := json.NewEncoder(w) encoder.SetIndent(" ", " ") - for _, l := range logger.FormatLogs(vmConfig.Tracer.(*logger.StructLogger).StructLogs()) { + for _, l := range logger.FormatLogs(structLogger.StructLogs()) { if err2 := encoder.Encode(l); err2 != nil { panic(err2) } diff --git a/cmd/state/commands/opcode_tracer.go b/cmd/state/commands/opcode_tracer.go index 61ca8b2bbbb..a9ae193d750 100644 --- a/cmd/state/commands/opcode_tracer.go +++ b/cmd/state/commands/opcode_tracer.go @@ -6,7 +6,6 @@ import ( "encoding/gob" "encoding/json" "fmt" - "math/big" "os" "os/signal" "strconv" @@ -29,10 +28,11 @@ import ( "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/eth/ethconfig" + "github.com/ledgerwatch/erigon/eth/tracers" "github.com/ledgerwatch/erigon/turbo/rpchelper" "github.com/ledgerwatch/erigon/turbo/snapshotsync/freezeblocks" ) @@ -115,7 +115,7 @@ type opcodeTracer struct { saveBblocks bool blockNumber uint64 depth int - env *vm.EVM + env *tracing.VMContext } func NewOpcodeTracer(blockNum uint64, saveOpcodes bool, saveBblocks bool) *opcodeTracer { @@ -164,13 +164,23 @@ type blockTxs struct { Txs slicePtrTx } -func (ot *opcodeTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { +func (ot *opcodeTracer) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: ot.OnTxStart, + OnEnter: ot.OnEnter, + OnExit: ot.OnExit, + OnFault: ot.OnFault, + OnOpcode: ot.OnOpcode, + }, + } +} + +func (ot *opcodeTracer) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { ot.env = env ot.depth = 0 } -func (ot *opcodeTracer) CaptureTxEnd(receipt *types.Receipt, err error) {} - func (ot *opcodeTracer) captureStartOrEnter(from, to libcommon.Address, create bool, input []byte) { //fmt.Fprint(ot.summary, ot.lastLine) @@ -199,14 +209,9 @@ func (ot *opcodeTracer) captureStartOrEnter(from, to libcommon.Address, create b ot.stack = append(ot.stack, &newTx) } -func (ot *opcodeTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - ot.depth = 0 - ot.captureStartOrEnter(from, to, create, input) -} - -func (ot *opcodeTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - ot.depth++ - ot.captureStartOrEnter(from, to, create, input) +func (ot *opcodeTracer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + ot.depth = depth + ot.captureStartOrEnter(from, to, vm.OpCode(typ) == vm.CREATE, input) } func (ot *opcodeTracer) captureEndOrExit(err error) { @@ -241,18 +246,13 @@ func (ot *opcodeTracer) captureEndOrExit(err error) { } } -func (ot *opcodeTracer) CaptureEnd(output []byte, usedGas uint64, err error, reverted bool) { +func (ot *opcodeTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { ot.captureEndOrExit(err) + ot.depth = depth } -func (ot *opcodeTracer) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { - ot.captureEndOrExit(err) - ot.depth-- -} - -func (ot *opcodeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, opDepth int, err error) { +func (ot *opcodeTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, opDepth int, err error) { //CaptureState sees the system as it is before the opcode is run. It seems to never get an error. - contract := scope.Contract //sanity check if pc > uint64(MaxUint16) { @@ -284,8 +284,8 @@ func (ot *opcodeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, currentEntry.TxHash = new(libcommon.Hash) currentEntry.TxHash.SetBytes(currentTxHash.Bytes()) currentEntry.CodeHash = new(libcommon.Hash) - currentEntry.CodeHash.SetBytes(contract.CodeHash.Bytes()) - currentEntry.CodeSize = len(contract.Code) + currentEntry.CodeHash.SetBytes(scope.CodeHash().Bytes()) + currentEntry.CodeSize = len(scope.Code()) if ot.saveOpcodes { currentEntry.Opcodes = make([]opcode, 0, 200) } @@ -313,7 +313,7 @@ func (ot *opcodeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, //sanity check if currentEntry.OpcodeFault != "" { panic(fmt.Sprintf("Running opcodes but fault is already set. txFault=%s, opFault=%v, op=%s", - currentEntry.OpcodeFault, err, op.String())) + currentEntry.OpcodeFault, err, vm.OpCode(op).String())) } // if it is a Fault, check whether we already have a record of the opcode. If so, just add the flag to it @@ -325,11 +325,11 @@ func (ot *opcodeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, faultAndRepeated := false - if pc16 == currentEntry.lastPc16 && op == currentEntry.lastOp { + if pc16 == currentEntry.lastPc16 && vm.OpCode(op) == currentEntry.lastOp { //it's a repeated opcode. We assume this only happens when it's a Fault. if err == nil { panic(fmt.Sprintf("Duplicate opcode with no fault. bn=%d txaddr=%s pc=%x op=%s", - ot.blockNumber, currentEntry.TxAddr, pc, op.String())) + ot.blockNumber, currentEntry.TxAddr, pc, vm.OpCode(op).String())) } faultAndRepeated = true //ot.fsumWriter.WriteString("Fault for EXISTING opcode\n") @@ -341,7 +341,7 @@ func (ot *opcodeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, } else { // it's a new opcode if ot.saveOpcodes { - newOpcode := opcode{pc16, op, errstr} + newOpcode := opcode{pc16, vm.OpCode(op), errstr} currentEntry.Opcodes = append(currentEntry.Opcodes, newOpcode) } } @@ -372,56 +372,25 @@ func (ot *opcodeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, //sanity check // we're starting a bblock, so either we're in PC=0 or we have OP=JUMPDEST - if pc16 != 0 && op.String() != "JUMPDEST" { + if pc16 != 0 && vm.OpCode(op).String() != "JUMPDEST" { panic(fmt.Sprintf("Bad bblock? lastpc=%x, lastOp=%s; pc=%x, op=%s; bn=%d txaddr=%s tx=%d-%s", - currentEntry.lastPc16, currentEntry.lastOp.String(), pc, op.String(), ot.blockNumber, currentEntry.TxAddr, currentEntry.Depth, currentEntry.TxHash.String())) + currentEntry.lastPc16, currentEntry.lastOp.String(), pc, vm.OpCode(op).String(), ot.blockNumber, currentEntry.TxAddr, currentEntry.Depth, currentEntry.TxHash.String())) } } } } currentEntry.lastPc16 = pc16 - currentEntry.lastOp = op + currentEntry.lastOp = vm.OpCode(op) } -func (ot *opcodeTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, opDepth int, err error) { +func (ot *opcodeTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, opDepth int, err error) { // CaptureFault sees the system as it is after the fault happens // CaptureState might have already recorded the opcode before it failed. Let's centralize the processing there. - ot.CaptureState(pc, op, gas, cost, scope, nil, opDepth, err) - -} - -func (ot *opcodeTracer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain2.Config) { -} - -func (ot *opcodeTracer) OnBlockEnd(err error) { + ot.OnOpcode(pc, op, gas, cost, scope, nil, opDepth, err) } -func (ot *opcodeTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { -} - -func (ot *opcodeTracer) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (ot *opcodeTracer) OnBeaconBlockRootEnd() {} - -func (ot *opcodeTracer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (ot *opcodeTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (ot *opcodeTracer) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (ot *opcodeTracer) OnNonceChange(a libcommon.Address, prev, new uint64) {} - -func (ot *opcodeTracer) OnCodeChange(a libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (ot *opcodeTracer) OnStorageChange(a libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { -} - -func (ot *opcodeTracer) OnLog(log *types.Log) {} - // GetResult returns an empty json object. func (ot *opcodeTracer) GetResult() (json.RawMessage, error) { return json.RawMessage(`{}`), nil @@ -474,7 +443,7 @@ func OpcodeTracer(genesis *types.Genesis, blockNum uint64, chaindata string, num blockReader := freezeblocks.NewBlockReader(freezeblocks.NewRoSnapshots(ethconfig.BlocksFreezing{Enabled: false}, "", 0, log.New()), nil /* BorSnapshots */) chainConfig := genesis.Config - vmConfig := vm.Config{Tracer: ot, Debug: true} + vmConfig := vm.Config{Tracer: ot.Tracer().Hooks, Debug: true} noOpWriter := state.NewNoopWriter() diff --git a/cmd/state/exec3/calltracer_v3.go b/cmd/state/exec3/calltracer_v3.go index b46f05a2263..8bee0355223 100644 --- a/cmd/state/exec3/calltracer_v3.go +++ b/cmd/state/exec3/calltracer_v3.go @@ -2,14 +2,11 @@ package exec3 import ( "encoding/json" - "math/big" "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" + "github.com/ledgerwatch/erigon/core/tracing" + "github.com/ledgerwatch/erigon/eth/tracers" ) type CallTracer struct { @@ -20,68 +17,29 @@ type CallTracer struct { func NewCallTracer() *CallTracer { return &CallTracer{} } + +func (ct *CallTracer) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnEnter: ct.OnEnter, + }, + } +} + func (ct *CallTracer) Reset() { ct.froms, ct.tos = nil, nil } + func (ct *CallTracer) Froms() map[libcommon.Address]struct{} { return ct.froms } func (ct *CallTracer) Tos() map[libcommon.Address]struct{} { return ct.tos } -func (ct *CallTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) {} - -func (ct *CallTracer) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (ct *CallTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - if ct.froms == nil { - ct.froms = map[libcommon.Address]struct{}{} - ct.tos = map[libcommon.Address]struct{}{} - } - ct.froms[from], ct.tos[to] = struct{}{}, struct{}{} -} -func (ct *CallTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (ct *CallTracer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { if ct.froms == nil { ct.froms = map[libcommon.Address]struct{}{} ct.tos = map[libcommon.Address]struct{}{} } ct.froms[from], ct.tos[to] = struct{}{}, struct{}{} } -func (ct *CallTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { -} -func (ct *CallTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { -} -func (ct *CallTracer) CaptureEnd(output []byte, usedGas uint64, err error, reverted bool) { -} -func (ct *CallTracer) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { -} - -func (ct *CallTracer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { -} - -func (ct *CallTracer) OnBlockEnd(err error) { -} - -func (ct *CallTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { -} - -func (ct *CallTracer) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (ct *CallTracer) OnBeaconBlockRootEnd() {} - -func (ct *CallTracer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (ct *CallTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (ct *CallTracer) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (ct *CallTracer) OnNonceChange(a libcommon.Address, prev, new uint64) {} - -func (ct *CallTracer) OnCodeChange(a libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (ct *CallTracer) OnStorageChange(a libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { -} - -func (ct *CallTracer) OnLog(log *types.Log) {} // GetResult returns an empty json object. func (ct *CallTracer) GetResult() (json.RawMessage, error) { diff --git a/cmd/state/exec3/state.go b/cmd/state/exec3/state.go index b89e86974a3..b6d02e8e797 100644 --- a/cmd/state/exec3/state.go +++ b/cmd/state/exec3/state.go @@ -187,7 +187,7 @@ func (rw *Worker) RunTxTaskNoLock(txTask *exec22.TxTask) { rw.taskGasPool.Reset(txTask.Tx.GetGas()) rw.callTracer.Reset() - vmConfig := vm.Config{Debug: true, Tracer: rw.callTracer, SkipAnalysis: txTask.SkipAnalysis} + vmConfig := vm.Config{Debug: true, Tracer: rw.callTracer.Tracer().Hooks, SkipAnalysis: txTask.SkipAnalysis} ibs.SetTxContext(txHash, txTask.BlockHash, txTask.TxIndex) msg := txTask.TxAsMessage diff --git a/consensus/aura/aura.go b/consensus/aura/aura.go index c701b40d1b4..1ab82f4202e 100644 --- a/consensus/aura/aura.go +++ b/consensus/aura/aura.go @@ -35,8 +35,8 @@ import ( "github.com/ledgerwatch/erigon/consensus/clique" "github.com/ledgerwatch/erigon/consensus/ethash" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/rpc" ) @@ -632,7 +632,7 @@ func (c *AuRa) Prepare(chain consensus.ChainHeaderReader, header *types.Header, } func (c *AuRa) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header, - state *state.IntraBlockState, syscallCustom consensus.SysCallCustom, logger log.Logger, eLogger consensus.EngineLogger, + state *state.IntraBlockState, syscallCustom consensus.SysCallCustom, logger log.Logger, eLogger *tracing.Hooks, ) { blockNum := header.Number.Uint64() @@ -693,7 +693,7 @@ func (c *AuRa) applyRewards(header *types.Header, state *state.IntraBlockState, return err } for _, r := range rewards { - state.AddBalance(r.Beneficiary, &r.Amount, evmtypes.BalanceIncreaseRewardMineBlock) + state.AddBalance(r.Beneficiary, &r.Amount, tracing.BalanceIncreaseRewardMineBlock) } return nil } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index ef0b2f54dd5..9ddfee535b7 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -44,6 +44,7 @@ import ( "github.com/ledgerwatch/erigon/common/debug" "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/crypto" @@ -367,7 +368,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header } func (c *Clique) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header, - state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, eLogger consensus.EngineLogger) { + state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, eLogger *tracing.Hooks) { } func (c *Clique) CalculateRewards(config *chain.Config, header *types.Header, uncles []*types.Header, syscall consensus.SystemCall, diff --git a/consensus/consensus.go b/consensus/consensus.go index 16e38509e92..df3a3ed910d 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -26,6 +26,7 @@ import ( "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/rpc" @@ -89,12 +90,6 @@ type Call func(contract libcommon.Address, data []byte) ([]byte, error) // different semantics which could lead e.g. to different reward values. type RewardKind uint16 -// EngineLogger interface for logging blockchain events -type EngineLogger interface { - OnBeaconBlockRootStart(root libcommon.Hash) - OnBeaconBlockRootEnd() -} - const ( // RewardAuthor - attributed to the block author. RewardAuthor RewardKind = 0 @@ -155,7 +150,7 @@ type EngineWriter interface { // Initialize runs any pre-transaction state modifications (e.g. epoch start) Initialize(config *chain.Config, chain ChainHeaderReader, header *types.Header, - state *state.IntraBlockState, syscall SysCallCustom, logger log.Logger, eLogger EngineLogger) + state *state.IntraBlockState, syscall SysCallCustom, logger log.Logger, eLogger *tracing.Hooks) // Finalize runs any post-transaction state modifications (e.g. block rewards) // but does not assemble the block. diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index a74f70808eb..c506cef01e7 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -37,8 +37,8 @@ import ( "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/consensus/misc" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rlp" ) @@ -551,7 +551,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H } func (ethash *Ethash) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header, - state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, eLogger consensus.EngineLogger) { + state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, eLogger *tracing.Hooks) { if config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(header.Number) == 0 { misc.ApplyDAOHardFork(state) } @@ -666,8 +666,8 @@ func accumulateRewards(config *chain.Config, state *state.IntraBlockState, heade minerReward, uncleRewards := AccumulateRewards(config, header, uncles) for i, uncle := range uncles { if i < len(uncleRewards) { - state.AddBalance(uncle.Coinbase, &uncleRewards[i], evmtypes.BalanceIncreaseRewardMineUncle) + state.AddBalance(uncle.Coinbase, &uncleRewards[i], tracing.BalanceIncreaseRewardMineUncle) } } - state.AddBalance(header.Coinbase, &minerReward, evmtypes.BalanceIncreaseRewardMineBlock) + state.AddBalance(header.Coinbase, &minerReward, tracing.BalanceIncreaseRewardMineBlock) } diff --git a/consensus/merge/merge.go b/consensus/merge/merge.go index f0c26f41790..ee0b6c5ef2f 100644 --- a/consensus/merge/merge.go +++ b/consensus/merge/merge.go @@ -14,8 +14,8 @@ import ( "github.com/ledgerwatch/erigon/consensus/aura" "github.com/ledgerwatch/erigon/consensus/misc" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/log/v3" @@ -146,11 +146,11 @@ func (s *Merge) Finalize(config *chain.Config, header *types.Header, state *stat for _, r := range rewards { switch r.Kind { case consensus.RewardAuthor: - state.AddBalance(r.Beneficiary, &r.Amount, evmtypes.BalanceIncreaseRewardMineBlock) + state.AddBalance(r.Beneficiary, &r.Amount, tracing.BalanceIncreaseRewardMineBlock) case consensus.RewardUncle: - state.AddBalance(r.Beneficiary, &r.Amount, evmtypes.BalanceIncreaseRewardMineUncle) + state.AddBalance(r.Beneficiary, &r.Amount, tracing.BalanceIncreaseRewardMineUncle) default: - state.AddBalance(r.Beneficiary, &r.Amount, evmtypes.BalanceChangeUnspecified) + state.AddBalance(r.Beneficiary, &r.Amount, tracing.BalanceChangeUnspecified) } } @@ -162,7 +162,7 @@ func (s *Merge) Finalize(config *chain.Config, header *types.Header, state *stat } else { for _, w := range withdrawals { amountInWei := new(uint256.Int).Mul(uint256.NewInt(w.Amount), uint256.NewInt(params.GWei)) - state.AddBalance(w.Address, amountInWei, evmtypes.BalanceIncreaseWithdrawal) + state.AddBalance(w.Address, amountInWei, tracing.BalanceIncreaseWithdrawal) } } } @@ -280,7 +280,7 @@ func (s *Merge) IsServiceTransaction(sender libcommon.Address, syscall consensus } func (s *Merge) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header, - state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, eLogger consensus.EngineLogger, + state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, eLogger *tracing.Hooks, ) { if !misc.IsPoSHeader(header) { s.eth1Engine.Initialize(config, chain, header, state, syscall, logger, eLogger) @@ -288,7 +288,7 @@ func (s *Merge) Initialize(config *chain.Config, chain consensus.ChainHeaderRead if chain.Config().IsCancun(header.Time) { misc.ApplyBeaconRootEip4788(header.ParentBeaconBlockRoot, func(addr libcommon.Address, data []byte) ([]byte, error) { return syscall(addr, data, state, header, false /* constCall */) - }, eLogger) + }) } } diff --git a/consensus/misc/dao.go b/consensus/misc/dao.go index ce7bc945f37..5d20982ac10 100644 --- a/consensus/misc/dao.go +++ b/consensus/misc/dao.go @@ -25,8 +25,8 @@ import ( "github.com/ledgerwatch/erigon-lib/chain" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/params" ) @@ -77,7 +77,7 @@ func ApplyDAOHardFork(statedb *state.IntraBlockState) { // Move every DAO account and extra-balance account funds into the refund contract for _, addr := range params.DAODrainList() { - statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), evmtypes.BalanceIncreaseDaoContract) - statedb.SetBalance(addr, new(uint256.Int), evmtypes.BalanceDecreaseDaoAccount) + statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), tracing.BalanceIncreaseDaoContract) + statedb.SetBalance(addr, new(uint256.Int), tracing.BalanceDecreaseDaoAccount) } } diff --git a/consensus/misc/eip4788.go b/consensus/misc/eip4788.go index 9de2dd0d483..26293004b28 100644 --- a/consensus/misc/eip4788.go +++ b/consensus/misc/eip4788.go @@ -8,14 +8,7 @@ import ( "github.com/ledgerwatch/erigon/params" ) -func ApplyBeaconRootEip4788(parentBeaconBlockRoot *libcommon.Hash, syscall consensus.SystemCall, eLogger consensus.EngineLogger) { - if eLogger != nil { - eLogger.OnBeaconBlockRootStart(*parentBeaconBlockRoot) - defer func() { - eLogger.OnBeaconBlockRootEnd() - }() - } - +func ApplyBeaconRootEip4788(parentBeaconBlockRoot *libcommon.Hash, syscall consensus.SystemCall) { _, err := syscall(params.BeaconRootsAddress, parentBeaconBlockRoot.Bytes()) if err != nil { log.Warn("Failed to call beacon roots contract", "err", err) diff --git a/core/blockchain.go b/core/blockchain.go index 13f294c66e6..b2c79552265 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -20,7 +20,6 @@ package core import ( "encoding/json" "fmt" - "math/big" "time" "github.com/ledgerwatch/log/v3" @@ -36,6 +35,7 @@ import ( "github.com/ledgerwatch/erigon/common/u256" "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/evmtypes" @@ -56,19 +56,6 @@ const ( SysCallGasLimit = uint64(30_000_000) ) -// BlockchainLogger is used to collect traces during chain processing. -// Please make a copy of the referenced types if you intend to retain them. -type BlockchainLogger interface { - vm.EVMLogger - state.StateLogger - consensus.EngineLogger - // OnBlockStart is called before executing `block`. - // `td` is the total difficulty prior to `block`. - OnBlockStart(block *types.Block, td *big.Int, finalized *types.Header, safe *types.Header, chainConfig *chain.Config) - OnBlockEnd(err error) - OnGenesisBlock(genesis *types.Block, alloc types.GenesisAlloc) -} - type RejectedTx struct { Index int `json:"index" gencodec:"required"` Err string `json:"error" gencodec:"required"` @@ -96,24 +83,14 @@ func ExecuteBlockEphemerally( blockHashFunc func(n uint64) libcommon.Hash, engine consensus.Engine, block *types.Block, stateReader state.StateReader, stateWriter state.WriterWithChangeSets, - chainReader consensus.ChainReader, getTracer func(txIndex int, txHash libcommon.Hash) (vm.EVMLogger, error), + chainReader consensus.ChainReader, getTracer func(txIndex int, txHash libcommon.Hash) (*tracing.Hooks, error), logger log.Logger, ) (res *EphemeralExecResult, executeBlockErr error) { - var bcLogger BlockchainLogger - if vmConfig.Tracer != nil { - l, ok := vmConfig.Tracer.(BlockchainLogger) - if ok { - bcLogger = l - } else { - log.Warn("only extended tracers are supported for live mode") - } - } - defer blockExecutionTimer.ObserveDuration(time.Now()) block.Uncles() ibs := state.New(stateReader) - ibs.SetLogger(bcLogger) + ibs.SetLogger(vmConfig.Tracer) header := block.Header() usedGas := new(uint64) @@ -127,15 +104,22 @@ func ExecuteBlockEphemerally( receipts types.Receipts ) - if bcLogger != nil { + if vmConfig.Tracer != nil && vmConfig.Tracer.OnBlockStart != nil { td := chainReader.GetTd(block.ParentHash(), block.NumberU64()-1) - bcLogger.OnBlockStart(block, td, chainReader.CurrentFinalizedHeader(), chainReader.CurrentSafeHeader(), chainConfig) + vmConfig.Tracer.OnBlockStart(tracing.BlockEvent{ + Block: block, + TD: td, + Finalized: chainReader.CurrentFinalizedHeader(), + Safe: chainReader.CurrentSafeHeader(), + }) + } + if vmConfig.Tracer != nil && vmConfig.Tracer.OnBlockEnd != nil { defer func() { - bcLogger.OnBlockEnd(executeBlockErr) + vmConfig.Tracer.OnBlockEnd(executeBlockErr) }() } - if err := InitializeBlockExecution(engine, chainReader, block.Header(), chainConfig, ibs, logger, bcLogger); err != nil { + if err := InitializeBlockExecution(engine, chainReader, block.Header(), chainConfig, ibs, logger, vmConfig.Tracer); err != nil { return nil, err } @@ -144,23 +128,24 @@ func ExecuteBlockEphemerally( noop := state.NewNoopWriter() for i, tx := range block.Transactions() { ibs.SetTxContext(tx.Hash(), block.Hash(), i) - writeTrace := false + // writeTrace := false if vmConfig.Debug && vmConfig.Tracer == nil { tracer, err := getTracer(i, tx.Hash()) if err != nil { return nil, fmt.Errorf("could not obtain tracer: %w", err) } vmConfig.Tracer = tracer - writeTrace = true + // writeTrace = true } receipt, _, err := ApplyTransaction(chainConfig, blockHashFunc, engine, nil, gp, ibs, noop, header, tx, usedGas, usedBlobGas, *vmConfig) - if writeTrace { - if ftracer, ok := vmConfig.Tracer.(vm.FlushableTracer); ok { - ftracer.Flush(tx) - } - - vmConfig.Tracer = nil - } + // Todo: check how to implement flushable tracer + // if writeTrace { + // if ftracer, ok := vmConfig.Tracer.(vm.FlushableTracer); ok { + // ftracer.Flush(tx) + // } + + // vmConfig.Tracer = nil + // } if err != nil { if !vmConfig.StatelessExec { return nil, fmt.Errorf("could not apply tx %d from block %d [%v]: %w", i, block.NumberU64(), tx.Hash().Hex(), err) @@ -379,7 +364,7 @@ func FinalizeBlockExecution( } func InitializeBlockExecution(engine consensus.Engine, chain consensus.ChainHeaderReader, header *types.Header, - cc *chain.Config, ibs *state.IntraBlockState, logger log.Logger, bcLogger BlockchainLogger, + cc *chain.Config, ibs *state.IntraBlockState, logger log.Logger, bcLogger *tracing.Hooks, ) error { engine.Initialize(cc, chain, header, ibs, func(contract libcommon.Address, data []byte, ibState *state.IntraBlockState, header *types.Header, constCall bool) ([]byte, error) { return SysCallContract(contract, data, cc, ibState, header, engine, constCall) diff --git a/core/evm.go b/core/evm.go index 2c9a56af1cc..43e64c73719 100644 --- a/core/evm.go +++ b/core/evm.go @@ -27,6 +27,7 @@ import ( "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/consensus/merge" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm/evmtypes" ) @@ -127,9 +128,9 @@ func CanTransfer(db evmtypes.IntraBlockState, addr libcommon.Address, amount *ui // Transfer subtracts amount from sender and adds amount to recipient using the given Db func Transfer(db evmtypes.IntraBlockState, sender, recipient libcommon.Address, amount *uint256.Int, bailout bool) { if !bailout { - db.SubBalance(sender, amount, evmtypes.BalanceChangeTransfer) + db.SubBalance(sender, amount, tracing.BalanceChangeTransfer) } - db.AddBalance(recipient, amount, evmtypes.BalanceChangeTransfer) + db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer) } // BorTransfer transfer in Bor @@ -139,9 +140,9 @@ func BorTransfer(db evmtypes.IntraBlockState, sender, recipient libcommon.Addres input2 := db.GetBalance(recipient).Clone() if !bailout { - db.SubBalance(sender, amount, evmtypes.BalanceChangeTransfer) + db.SubBalance(sender, amount, tracing.BalanceChangeTransfer) } - db.AddBalance(recipient, amount, evmtypes.BalanceChangeTransfer) + db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer) // get outputs after output1 := db.GetBalance(sender).Clone() diff --git a/core/genesis_write.go b/core/genesis_write.go index 438573383e6..0601fad8ce9 100644 --- a/core/genesis_write.go +++ b/core/genesis_write.go @@ -45,8 +45,8 @@ import ( "github.com/ledgerwatch/erigon/consensus/merge" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/eth/ethconfig" "github.com/ledgerwatch/erigon/params" @@ -66,11 +66,11 @@ import ( // error is a *params.ConfigCompatError and the new, unwritten config is returned. // // The returned chain configuration is never nil. -func CommitGenesisBlock(db kv.RwDB, genesis *types.Genesis, tmpDir string, logger log.Logger, bcLogger BlockchainLogger) (*chain.Config, *types.Block, error) { +func CommitGenesisBlock(db kv.RwDB, genesis *types.Genesis, tmpDir string, logger log.Logger, bcLogger *tracing.Hooks) (*chain.Config, *types.Block, error) { return CommitGenesisBlockWithOverride(db, genesis, nil, tmpDir, logger, bcLogger) } -func CommitGenesisBlockWithOverride(db kv.RwDB, genesis *types.Genesis, overrideCancunTime *big.Int, tmpDir string, logger log.Logger, bcLogger BlockchainLogger) (*chain.Config, *types.Block, error) { +func CommitGenesisBlockWithOverride(db kv.RwDB, genesis *types.Genesis, overrideCancunTime *big.Int, tmpDir string, logger log.Logger, bcLogger *tracing.Hooks) (*chain.Config, *types.Block, error) { tx, err := db.BeginRw(context.Background()) if err != nil { return nil, nil, err @@ -87,7 +87,7 @@ func CommitGenesisBlockWithOverride(db kv.RwDB, genesis *types.Genesis, override return c, b, nil } -func WriteGenesisBlock(tx kv.RwTx, genesis *types.Genesis, overrideCancunTime *big.Int, tmpDir string, logger log.Logger, bcLogger BlockchainLogger) (*chain.Config, *types.Block, error) { +func WriteGenesisBlock(tx kv.RwTx, genesis *types.Genesis, overrideCancunTime *big.Int, tmpDir string, logger log.Logger, bcLogger *tracing.Hooks) (*chain.Config, *types.Block, error) { var storedBlock *types.Block if genesis != nil && genesis.Config == nil { return params.AllProtocolChanges, nil, types.ErrGenesisNoConfig @@ -181,7 +181,7 @@ func WriteGenesisBlock(tx kv.RwTx, genesis *types.Genesis, overrideCancunTime *b return newCfg, storedBlock, nil } -func WriteGenesisState(g *types.Genesis, tx kv.RwTx, tmpDir string, logger log.Logger, bcLogger BlockchainLogger) (*types.Block, *state.IntraBlockState, error) { +func WriteGenesisState(g *types.Genesis, tx kv.RwTx, tmpDir string, logger log.Logger, bcLogger *tracing.Hooks) (*types.Block, *state.IntraBlockState, error) { block, statedb, err := GenesisToBlock(g, tmpDir, logger, bcLogger) if err != nil { return nil, nil, err @@ -230,7 +230,7 @@ func WriteGenesisState(g *types.Genesis, tx kv.RwTx, tmpDir string, logger log.L } return block, statedb, nil } -func MustCommitGenesis(g *types.Genesis, db kv.RwDB, tmpDir string, logger log.Logger, bcLogger BlockchainLogger) *types.Block { +func MustCommitGenesis(g *types.Genesis, db kv.RwDB, tmpDir string, logger log.Logger, bcLogger *tracing.Hooks) *types.Block { tx, err := db.BeginRw(context.Background()) if err != nil { panic(err) @@ -249,7 +249,7 @@ func MustCommitGenesis(g *types.Genesis, db kv.RwDB, tmpDir string, logger log.L // Write writes the block and state of a genesis specification to the database. // The block is committed as the canonical head block. -func write(tx kv.RwTx, g *types.Genesis, tmpDir string, logger log.Logger, bcLogger BlockchainLogger) (*types.Block, *state.IntraBlockState, error) { +func write(tx kv.RwTx, g *types.Genesis, tmpDir string, logger log.Logger, bcLogger *tracing.Hooks) (*types.Block, *state.IntraBlockState, error) { block, statedb, err2 := WriteGenesisState(g, tx, tmpDir, logger, bcLogger) if err2 != nil { return block, statedb, err2 @@ -325,7 +325,7 @@ type GenAccount struct { Balance *big.Int } -func GenesisWithAccounts(db kv.RwDB, accs []GenAccount, tmpDir string, logger log.Logger, bcLogger BlockchainLogger) *types.Block { +func GenesisWithAccounts(db kv.RwDB, accs []GenAccount, tmpDir string, logger log.Logger, bcLogger *tracing.Hooks) *types.Block { g := types.Genesis{Config: params.TestChainConfig} allocs := make(map[libcommon.Address]types.GenesisAccount) for _, acc := range accs { @@ -494,7 +494,7 @@ func DeveloperGenesisBlock(period uint64, faucet libcommon.Address) *types.Genes // ToBlock creates the genesis block and writes state of a genesis specification // to the given database (or discards it if nil). -func GenesisToBlock(g *types.Genesis, tmpDir string, logger log.Logger, bcLogger BlockchainLogger) (*types.Block, *state.IntraBlockState, error) { +func GenesisToBlock(g *types.Genesis, tmpDir string, logger log.Logger, bcLogger *tracing.Hooks) (*types.Block, *state.IntraBlockState, error) { _ = g.Alloc //nil-check head := &types.Header{ @@ -595,7 +595,7 @@ func GenesisToBlock(g *types.Genesis, tmpDir string, logger log.Logger, bcLogger } // This is not actually logged via tracer because OnGenesisBlock // already captures the allocations. - statedb.AddBalance(addr, balance, evmtypes.BalanceIncreaseGenesisBalance) + statedb.AddBalance(addr, balance, tracing.BalanceIncreaseGenesisBalance) statedb.SetCode(addr, account.Code) statedb.SetNonce(addr, account.Nonce) for key, value := range account.Storage { diff --git a/core/state/intra_block_state.go b/core/state/intra_block_state.go index 415039574fc..3d69a161672 100644 --- a/core/state/intra_block_state.go +++ b/core/state/intra_block_state.go @@ -27,9 +27,9 @@ import ( libcommon "github.com/ledgerwatch/erigon-lib/common" types2 "github.com/ledgerwatch/erigon-lib/types" "github.com/ledgerwatch/erigon/common/u256" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/types/accounts" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/turbo/trie" ) @@ -39,20 +39,6 @@ type revision struct { journalIndex int } -// 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 { - OnBalanceChange(addr libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) - OnNonceChange(addr libcommon.Address, prev, new uint64) - OnCodeChange(addr libcommon.Address, prevCodeHash libcommon.Hash, prevCode []byte, codeHash libcommon.Hash, code []byte) - OnStorageChange(addr libcommon.Address, slot *libcommon.Hash, prev, new uint256.Int) - OnLog(log *types.Log) -} - // SystemAddress - sender address for internal state updates. var SystemAddress = libcommon.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") @@ -103,7 +89,7 @@ type IntraBlockState struct { validRevisions []revision nextRevisionID int trace bool - logger StateLogger + logger *tracing.Hooks balanceInc map[libcommon.Address]*BalanceIncrease // Map of balance increases (without first reading the account) } @@ -123,7 +109,7 @@ func New(stateReader StateReader) *IntraBlockState { } // SetLogger sets the logger for account update hooks. -func (sdb *IntraBlockState) SetLogger(l StateLogger) { +func (sdb *IntraBlockState) SetLogger(l *tracing.Hooks) { sdb.logger = l } @@ -168,7 +154,7 @@ func (sdb *IntraBlockState) AddLog(log2 *types.Log) { log2.BlockHash = sdb.bhash log2.TxIndex = uint(sdb.txIndex) log2.Index = sdb.logSize - if sdb.logger != nil { + if sdb.logger != nil && sdb.logger.OnLog != nil { sdb.logger.OnLog(log2) } sdb.logs[sdb.thash] = append(sdb.logs[sdb.thash], log2) @@ -324,7 +310,7 @@ func (sdb *IntraBlockState) HasSelfdestructed(addr libcommon.Address) bool { // AddBalance adds amount to the account associated with addr. // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account -func (sdb *IntraBlockState) AddBalance(addr libcommon.Address, amount *uint256.Int, reason evmtypes.BalanceChangeReason) { +func (sdb *IntraBlockState) AddBalance(addr libcommon.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) { if sdb.trace { fmt.Printf("AddBalance %x, %d\n", addr, amount) } @@ -357,7 +343,7 @@ func (sdb *IntraBlockState) AddBalance(addr libcommon.Address, amount *uint256.I // SubBalance subtracts amount from the account associated with addr. // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account -func (sdb *IntraBlockState) SubBalance(addr libcommon.Address, amount *uint256.Int, reason evmtypes.BalanceChangeReason) { +func (sdb *IntraBlockState) SubBalance(addr libcommon.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) { if sdb.trace { fmt.Printf("SubBalance %x, %d\n", addr, amount) } @@ -369,7 +355,7 @@ func (sdb *IntraBlockState) SubBalance(addr libcommon.Address, amount *uint256.I } // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account -func (sdb *IntraBlockState) SetBalance(addr libcommon.Address, amount *uint256.Int, reason evmtypes.BalanceChangeReason) { +func (sdb *IntraBlockState) SetBalance(addr libcommon.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) { stateObject := sdb.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SetBalance(amount, reason) @@ -444,8 +430,8 @@ func (sdb *IntraBlockState) Selfdestruct(addr libcommon.Address) bool { prevbalance: prevBalance, }) - if sdb.logger != nil && !prevBalance.IsZero() { - sdb.logger.OnBalanceChange(addr, &prevBalance, uint256.NewInt(0), evmtypes.BalanceDecreaseSelfdestruct) + if sdb.logger != nil && sdb.logger.OnBalanceChange != nil && !prevBalance.IsZero() { + sdb.logger.OnBalanceChange(addr, &prevBalance, uint256.NewInt(0), tracing.BalanceDecreaseSelfdestruct) } stateObject.markSelfdestructed() @@ -639,12 +625,12 @@ func (sdb *IntraBlockState) GetRefund() uint64 { return sdb.refund } -func updateAccount(EIP161Enabled bool, isAura bool, stateWriter StateWriter, addr libcommon.Address, stateObject *stateObject, isDirty bool, logger StateLogger) error { +func updateAccount(EIP161Enabled bool, isAura bool, stateWriter StateWriter, addr libcommon.Address, stateObject *stateObject, isDirty bool, logger *tracing.Hooks) error { emptyRemoval := EIP161Enabled && stateObject.empty() && (!isAura || addr != SystemAddress) if stateObject.selfdestructed || (isDirty && emptyRemoval) { // If ether was sent to account post-selfdestruct it is burnt. - if logger != nil && !stateObject.Balance().IsZero() && stateObject.selfdestructed { - logger.OnBalanceChange(stateObject.address, stateObject.Balance(), uint256.NewInt(0), evmtypes.BalanceDecreaseSelfdestructBurn) + if logger != nil && logger.OnBalanceChange != nil && !stateObject.Balance().IsZero() && stateObject.selfdestructed { + logger.OnBalanceChange(stateObject.address, stateObject.Balance(), uint256.NewInt(0), tracing.BalanceDecreaseSelfdestructBurn) } if err := stateWriter.DeleteAccount(addr, &stateObject.original); err != nil { diff --git a/core/state/state_object.go b/core/state/state_object.go index 8398f023fe1..fb2a302f5a6 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -25,8 +25,8 @@ import ( "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types/accounts" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/turbo/trie" @@ -225,7 +225,7 @@ func (so *stateObject) SetState(key *libcommon.Hash, value uint256.Int) { key: *key, prevalue: prev, }) - if so.db.logger != nil { + if so.db.logger != nil && so.db.logger.OnStorageChange != nil { so.db.logger.OnStorageChange(so.address, key, prev, value) } so.setState(key, value) @@ -273,7 +273,7 @@ func (so *stateObject) printTrie() { // AddBalance adds amount to so's balance. // It is used to add funds to the destination account of a transfer. -func (so *stateObject) AddBalance(amount *uint256.Int, reason evmtypes.BalanceChangeReason) { +func (so *stateObject) AddBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) { // EIP161: We must check emptiness for the objects such that the account // clearing (0,0,0 objects) can take effect. if amount.IsZero() { @@ -289,19 +289,19 @@ func (so *stateObject) AddBalance(amount *uint256.Int, reason evmtypes.BalanceCh // SubBalance removes amount from so's balance. // It is used to remove funds from the origin account of a transfer. -func (so *stateObject) SubBalance(amount *uint256.Int, reason evmtypes.BalanceChangeReason) { +func (so *stateObject) SubBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) { if amount.IsZero() { return } so.SetBalance(new(uint256.Int).Sub(so.Balance(), amount), reason) } -func (so *stateObject) SetBalance(amount *uint256.Int, reason evmtypes.BalanceChangeReason) { +func (so *stateObject) SetBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) { so.db.journal.append(balanceChange{ account: &so.address, prev: so.data.Balance, }) - if so.db.logger != nil { + if so.db.logger != nil && so.db.logger.OnBalanceChange != nil { so.db.logger.OnBalanceChange(so.address, so.Balance(), amount, reason) } so.setBalance(amount) @@ -351,7 +351,7 @@ func (so *stateObject) SetCode(codeHash libcommon.Hash, code []byte) { prevhash: so.data.CodeHash, prevcode: prevcode, }) - if so.db.logger != nil { + if so.db.logger != nil && so.db.logger.OnCodeChange != nil { so.db.logger.OnCodeChange(so.address, so.data.CodeHash, prevcode, codeHash, code) } so.setCode(codeHash, code) @@ -368,7 +368,7 @@ func (so *stateObject) SetNonce(nonce uint64) { account: &so.address, prev: so.data.Nonce, }) - if so.db.logger != nil { + if so.db.logger != nil && so.db.logger.OnNonceChange != nil { so.db.logger.OnNonceChange(so.address, so.data.Nonce, nonce) } so.setNonce(nonce) diff --git a/core/state/state_test.go b/core/state/state_test.go index 961614c6717..4be4b62b90a 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -29,8 +29,8 @@ import ( "github.com/ledgerwatch/erigon-lib/kv/memdb" checker "gopkg.in/check.v1" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types/accounts" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/crypto" ) @@ -53,7 +53,7 @@ func (s *StateSuite) TestDump(c *checker.C) { obj2 := s.state.GetOrNewStateObject(toAddr([]byte{0x01, 0x02})) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) obj3 := s.state.GetOrNewStateObject(toAddr([]byte{0x02})) - obj3.SetBalance(uint256.NewInt(44), evmtypes.BalanceChangeUnspecified) + obj3.SetBalance(uint256.NewInt(44), tracing.BalanceChangeUnspecified) // write some of them to the trie err := s.w.UpdateAccountData(obj1.address, &obj1.data, new(accounts.Account)) @@ -227,7 +227,7 @@ func TestSnapshot2(t *testing.T) { // db, trie are already non-empty values so0 := state.getStateObject(stateobjaddr0) - so0.SetBalance(uint256.NewInt(42), evmtypes.BalanceChangeUnspecified) + so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified) so0.SetNonce(43) so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) so0.selfdestructed = false @@ -247,7 +247,7 @@ func TestSnapshot2(t *testing.T) { // and one with deleted == true so1 := state.getStateObject(stateobjaddr1) - so1.SetBalance(uint256.NewInt(52), evmtypes.BalanceChangeUnspecified) + so1.SetBalance(uint256.NewInt(52), tracing.BalanceChangeUnspecified) so1.SetNonce(53) so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'}) so1.selfdestructed = true @@ -338,7 +338,7 @@ func TestDump(t *testing.T) { obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) obj2.setIncarnation(1) obj3 := state.GetOrNewStateObject(toAddr([]byte{0x02})) - obj3.SetBalance(uint256.NewInt(44), evmtypes.BalanceChangeUnspecified) + obj3.SetBalance(uint256.NewInt(44), tracing.BalanceChangeUnspecified) // write some of them to the trie err := w.UpdateAccountData(obj1.address, &obj1.data, new(accounts.Account)) diff --git a/core/state_processor.go b/core/state_processor.go index 9d0edcdddc0..47e80ab994a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -22,6 +22,7 @@ import ( "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/evmtypes" @@ -39,12 +40,6 @@ func applyTransaction(config *chain.Config, engine consensus.EngineReader, gp *G receipt *types.Receipt err error ) - if evm.Config().Tracer != nil { - evm.Config().Tracer.CaptureTxStart(evm, tx) - defer func() { - evm.Config().Tracer.CaptureTxEnd(receipt, err) - }() - } rules := evm.ChainRules() msg, err := tx.AsMessage(*types.MakeSigner(config, header.Number.Uint64(), header.Time), header.BaseFee, rules) @@ -53,6 +48,25 @@ func applyTransaction(config *chain.Config, engine consensus.EngineReader, gp *G } msg.SetCheckNonce(!cfg.StatelessExec) + if evm.Config().Tracer != nil { + if evm.Config().Tracer != nil && evm.Config().Tracer.OnTxStart != nil { + evm.Config().Tracer.OnTxStart(&tracing.VMContext{ + ChainConfig: evm.ChainConfig(), + IntraBlockState: ibs, + BlockNumber: evm.Context.BlockNumber, + Time: evm.Context.Time, + Coinbase: evm.Context.Coinbase, + Random: evm.Context.PrevRanDao, + TxHash: evm.TxHash, + }, tx, msg.From()) + } + if evm.Config().Tracer.OnTxEnd != nil { + defer func() { + evm.Config().Tracer.OnTxEnd(receipt, err) + }() + } + } + if msg.FeeCap().IsZero() && engine != nil { // Only zero-gas transactions may be service ones syscall := func(contract libcommon.Address, data []byte) ([]byte, error) { diff --git a/core/state_transition.go b/core/state_transition.go index 4bf28d1184f..80717e94817 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -27,6 +27,7 @@ import ( cmath "github.com/ledgerwatch/erigon/common/math" "github.com/ledgerwatch/erigon/common/u256" "github.com/ledgerwatch/erigon/consensus/misc" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/crypto" @@ -249,8 +250,8 @@ func (st *StateTransition) buyGas(gasBailout bool) error { } } - if st.evm.Config().Tracer != nil { - st.evm.Config().Tracer.OnGasChange(0, st.msg.Gas(), vm.GasChangeTxInitialBalance) + if st.evm.Config().Tracer != nil && st.evm.Config().Tracer.OnGasChange != nil { + st.evm.Config().Tracer.OnGasChange(0, st.msg.Gas(), tracing.GasChangeTxInitialBalance) } st.gas += st.msg.Gas() @@ -258,8 +259,8 @@ func (st *StateTransition) buyGas(gasBailout bool) error { if subBalance { // CS TODO: cross check - st.state.SubBalance(st.msg.From(), gasVal, evmtypes.BalanceDecreaseGasBuy) - st.state.SubBalance(st.msg.From(), blobGasVal, evmtypes.BalanceDecreaseGasBuy) + st.state.SubBalance(st.msg.From(), gasVal, tracing.BalanceDecreaseGasBuy) + st.state.SubBalance(st.msg.From(), blobGasVal, tracing.BalanceDecreaseGasBuy) } return nil } @@ -384,8 +385,8 @@ func (st *StateTransition) TransitionDb(refunds bool, gasBailout bool) (*Executi return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas) } - if t := st.evm.Config().Tracer; t != nil { - t.OnGasChange(st.gas, st.gas-gas, vm.GasChangeTxIntrinsicGas) + if t := st.evm.Config().Tracer; t != nil && t.OnGasChange != nil { + t.OnGasChange(st.gas, st.gas-gas, tracing.GasChangeTxIntrinsicGas) } st.gas -= gas @@ -441,12 +442,12 @@ func (st *StateTransition) TransitionDb(refunds bool, gasBailout bool) (*Executi } amount := new(uint256.Int).SetUint64(st.gasUsed()) amount.Mul(amount, effectiveTip) // gasUsed * effectiveTip = how much goes to the block producer (miner, validator) - st.state.AddBalance(coinbase, amount, evmtypes.BalanceIncreaseRewardTransactionFee) + st.state.AddBalance(coinbase, amount, tracing.BalanceIncreaseRewardTransactionFee) if !msg.IsFree() && rules.IsLondon { burntContractAddress := st.evm.ChainConfig().GetBurntContract(st.evm.Context.BlockNumber) if burntContractAddress != nil { burnAmount := new(uint256.Int).Mul(new(uint256.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee) - st.state.AddBalance(*burntContractAddress, burnAmount, evmtypes.BalanceChangeUnspecified) + st.state.AddBalance(*burntContractAddress, burnAmount, tracing.BalanceChangeUnspecified) } } if st.isBor { @@ -482,17 +483,17 @@ func (st *StateTransition) refundGas(refundQuotient uint64) { refund = st.state.GetRefund() } - if st.evm.Config().Tracer != nil && refund > 0 { - st.evm.Config().Tracer.OnGasChange(st.gas, st.gas+refund, vm.GasChangeTxRefunds) + if st.evm.Config().Tracer != nil && st.evm.Config().Tracer.OnGasChange != nil && refund > 0 { + st.evm.Config().Tracer.OnGasChange(st.gas, st.gas+refund, tracing.GasChangeTxRefunds) } st.gas += refund // Return ETH for remaining gas, exchanged at the original rate. remaining := new(uint256.Int).Mul(new(uint256.Int).SetUint64(st.gas), st.gasPrice) - st.state.AddBalance(st.msg.From(), remaining, evmtypes.BalanceIncreaseGasReturn) + st.state.AddBalance(st.msg.From(), remaining, tracing.BalanceIncreaseGasReturn) - if st.evm.Config().Tracer != nil && st.gas > 0 { - st.evm.Config().Tracer.OnGasChange(st.gas, 0, vm.GasChangeTxLeftOverReturned) + if st.evm.Config().Tracer != nil && st.evm.Config().Tracer.OnGasChange != nil && st.gas > 0 { + st.evm.Config().Tracer.OnGasChange(st.gas, 0, tracing.GasChangeTxLeftOverReturned) } // Also return remaining gas to the block gas counter so it is diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go new file mode 100644 index 00000000000..684233544be --- /dev/null +++ b/core/tracing/hooks.go @@ -0,0 +1,281 @@ +// 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 tracing + +import ( + "math/big" + + libcommon "github.com/ledgerwatch/erigon-lib/common" + + "github.com/ledgerwatch/erigon-lib/chain" + "github.com/ledgerwatch/erigon/core/types" + + "github.com/holiman/uint256" +) + +// OpContext provides the context at which the opcode is being +// executed in, including the memory, stack and various contract-level information. +type OpContext interface { + MemoryData() []byte + StackData() []uint256.Int + Caller() libcommon.Address + Address() libcommon.Address + CallValue() *uint256.Int + CallInput() []byte + Code() []byte + CodeHash() libcommon.Hash +} + +// IntraBlockState gives tracers access to the whole state. +type IntraBlockState interface { + GetBalance(libcommon.Address) *uint256.Int + GetNonce(libcommon.Address) uint64 + GetCode(libcommon.Address) []byte + GetState(addr libcommon.Address, key *libcommon.Hash, value *uint256.Int) + Exist(libcommon.Address) bool + GetRefund() uint64 +} + +// VMContext provides the context for the EVM execution. +type VMContext struct { + Coinbase libcommon.Address + BlockNumber uint64 + Time uint64 + Random *libcommon.Hash + // Effective tx gas price + GasPrice *uint256.Int + ChainConfig *chain.Config + IntraBlockState IntraBlockState + + TxHash libcommon.Hash +} + +// BlockEvent is emitted upon tracing an incoming block. +// It contains the block as well as consensus related information. +type BlockEvent struct { + Block *types.Block + TD *big.Int + Finalized *types.Header + Safe *types.Header +} + +type ( + /* + - VM events - + */ + + // TxStartHook is called before the execution of a transaction starts. + // Call simulations don't come with a valid signature. `from` field + // to be used for address of the caller. + TxStartHook = func(vm *VMContext, tx types.Transaction, from libcommon.Address) + + // TxEndHook is called after the execution of a transaction ends. + TxEndHook = func(receipt *types.Receipt, err error) + + // EnterHook is invoked when the processing of a message starts. + EnterHook = func(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) + + // ExitHook is invoked when the processing of a message ends. + // `revert` is true when there was an error during the execution. + // Exceptionally, before the homestead hardfork a contract creation that + // ran out of gas when attempting to persist the code to database did not + // count as a call failure and did not cause a revert of the call. This will + // be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`. + ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool) + + // OpcodeHook is invoked just prior to the execution of an opcode. + OpcodeHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, rData []byte, depth int, err error) + + // FaultHook is invoked when an error occurs during the execution of an opcode. + FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error) + + // GasChangeHook is invoked when the gas changes. + GasChangeHook = func(old, new uint64, reason GasChangeReason) + + /* + - Chain events - + */ + + // BlockchainInitHook is called when the blockchain is initialized. + BlockchainInitHook = func(chainConfig *chain.Config) + + // BlockStartHook is called before executing `block`. + // `td` is the total difficulty prior to `block`. + BlockStartHook = func(event BlockEvent) + + // BlockEndHook is called after executing a block. + BlockEndHook = func(err error) + + // SkippedBlockHook indicates a block was skipped during processing + // due to it being known previously. This can happen e.g. when recovering + // from a crash. + SkippedBlockHook = func(event BlockEvent) + + // GenesisBlockHook is called when the genesis block is being processed. + GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc) + + /* + - State events - + */ + + // BalanceChangeHook is called when the balance of an account changes. + BalanceChangeHook = func(addr libcommon.Address, prev, new *uint256.Int, reason BalanceChangeReason) + + // NonceChangeHook is called when the nonce of an account changes. + NonceChangeHook = func(addr libcommon.Address, prev, new uint64) + + // CodeChangeHook is called when the code of an account changes. + CodeChangeHook = func(addr libcommon.Address, prevCodeHash libcommon.Hash, prevCode []byte, codeHash libcommon.Hash, code []byte) + + // StorageChangeHook is called when the storage of an account changes. + StorageChangeHook = func(addr libcommon.Address, slot *libcommon.Hash, prev, new uint256.Int) + + // LogHook is called when a log is emitted. + LogHook = func(log *types.Log) +) + +type Hooks struct { + // VM events + OnTxStart TxStartHook + OnTxEnd TxEndHook + OnEnter EnterHook + OnExit ExitHook + OnOpcode OpcodeHook + OnFault FaultHook + OnGasChange GasChangeHook + // Chain events + OnBlockchainInit BlockchainInitHook + OnBlockStart BlockStartHook + OnBlockEnd BlockEndHook + OnSkippedBlock SkippedBlockHook + OnGenesisBlock GenesisBlockHook + // State events + OnBalanceChange BalanceChangeHook + OnNonceChange NonceChangeHook + OnCodeChange CodeChangeHook + OnStorageChange StorageChangeHook + OnLog LogHook +} + +// BalanceChangeReason is used to indicate the reason for a balance change, useful +// for tracing and reporting. +type BalanceChangeReason byte + +const ( + BalanceChangeUnspecified BalanceChangeReason = 0 + + // Issuance + // BalanceIncreaseRewardMineUncle is a reward for mining an uncle block. + BalanceIncreaseRewardMineUncle BalanceChangeReason = 1 + // BalanceIncreaseRewardMineBlock is a reward for mining a block. + BalanceIncreaseRewardMineBlock BalanceChangeReason = 2 + // BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain. + BalanceIncreaseWithdrawal BalanceChangeReason = 3 + // BalanceIncreaseGenesisBalance is ether allocated at the genesis block. + BalanceIncreaseGenesisBalance BalanceChangeReason = 4 + + // Transaction fees + // BalanceIncreaseRewardTransactionFee is the transaction tip increasing block builder's balance. + BalanceIncreaseRewardTransactionFee BalanceChangeReason = 5 + // BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction. + // Part of this gas will be burnt as per EIP-1559 rules. + BalanceDecreaseGasBuy BalanceChangeReason = 6 + // BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution. + BalanceIncreaseGasReturn BalanceChangeReason = 7 + + // DAO fork + // BalanceIncreaseDaoContract is ether sent to the DAO refund contract. + BalanceIncreaseDaoContract BalanceChangeReason = 8 + // BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved to the refund contract. + BalanceDecreaseDaoAccount BalanceChangeReason = 9 + + // BalanceChangeTransfer is ether transferred via a call. + // it is a decrease for the sender and an increase for the recipient. + BalanceChangeTransfer BalanceChangeReason = 10 + // BalanceChangeTouchAccount is a transfer of zero value. It is only there to + // touch-create an account. + BalanceChangeTouchAccount BalanceChangeReason = 11 + + // BalanceIncreaseSelfdestruct is added to the recipient as indicated by a selfdestructing account. + BalanceIncreaseSelfdestruct BalanceChangeReason = 12 + // BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct. + BalanceDecreaseSelfdestruct BalanceChangeReason = 13 + // BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed + // account within the same tx (captured at end of tx). + // Note it doesn't account for a self-destruct which appoints itself as recipient. + BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14 +) + +// GasChangeReason is used to indicate the reason for a gas change, useful +// for tracing and reporting. +// +// There is essentially two types of gas changes, those that can be emitted once per transaction +// and those that can be emitted on a call basis, so possibly multiple times per transaction. +// +// They can be recognized easily by their name, those that start with `GasChangeTx` are emitted +// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis. +type GasChangeReason byte + +const ( + GasChangeUnspecified GasChangeReason = 0 + + // GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only + // one such gas change per transaction. + GasChangeTxInitialBalance GasChangeReason = 1 + // GasChangeTxIntrinsicGas is the amount of gas that will be charged for the intrinsic cost of the transaction, there is + // always exactly one of those per transaction. + GasChangeTxIntrinsicGas GasChangeReason = 2 + // GasChangeTxRefunds is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) + // this generates an increase in gas. There is at most one of such gas change per transaction. + GasChangeTxRefunds GasChangeReason = 3 + // GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned + // to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas + // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. + // There is at most one of such gas change per transaction. + GasChangeTxLeftOverReturned GasChangeReason = 4 + + // GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only + // one such gas change per call. + GasChangeCallInitialBalance GasChangeReason = 5 + // GasChangeCallLeftOverReturned is the amount of gas left over that will be returned to the caller, this change will always + // be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even + // will be emitted. + GasChangeCallLeftOverReturned GasChangeReason = 6 + // GasChangeCallLeftOverRefunded is the amount of gas that will be refunded to the call after the child call execution it + // executed completed. This value is always positive as we are giving gas back to the you, the left over gas of the child. + // If there was no gas left to be refunded, no such even will be emitted. + GasChangeCallLeftOverRefunded GasChangeReason = 7 + // GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE. + GasChangeCallContractCreation GasChangeReason = 8 + // GasChangeContractCreation is the amount of gas that will be burned for a CREATE2. + GasChangeCallContractCreation2 GasChangeReason = 9 + // GasChangeCallCodeStorage is the amount of gas that will be charged for code storage. + GasChangeCallCodeStorage GasChangeReason = 10 + // GasChangeCallOpCode is the amount of gas that will be charged for an opcode executed by the EVM, exact opcode that was + // performed can be check by `OnOpcode` handling. + GasChangeCallOpCode GasChangeReason = 11 + // GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution. + GasChangeCallPrecompiledContract GasChangeReason = 12 + // GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules. + GasChangeCallStorageColdAccess GasChangeReason = 13 + // GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert. + GasChangeCallFailedExecution GasChangeReason = 14 + + // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as + // it will be "manually" tracked by a direct emit of the gas change event. + GasChangeIgnored GasChangeReason = 0xFF +) diff --git a/core/vm/contract.go b/core/vm/contract.go index 9f9ed821657..2af9a15631f 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -19,6 +19,7 @@ package vm import ( "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/core/tracing" ) // ContractRef is a reference to the contract's backing object @@ -166,17 +167,28 @@ func (c *Contract) Caller() libcommon.Address { } // UseGas attempts the use gas and subtracts it and returns true on success -func (c *Contract) UseGas(gas uint64, logger EVMLogger, reason GasChangeReason) (ok bool) { +func (c *Contract) UseGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) { if c.Gas < gas { return false } - if logger != nil && reason != GasChangeIgnored { + if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { logger.OnGasChange(c.Gas, c.Gas-gas, reason) } c.Gas -= gas return true } +// RefundGas refunds gas to the contract +func (c *Contract) RefundGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) { + if gas == 0 { + return + } + if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { + logger.OnGasChange(c.Gas, c.Gas+gas, reason) + } + c.Gas += gas +} + // Address returns the contracts address func (c *Contract) Address() libcommon.Address { return c.self diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 60fd4832c9e..6f65f067288 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -31,6 +31,7 @@ import ( "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/math" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/crypto/bls12381" "github.com/ledgerwatch/erigon/crypto/bn256" @@ -192,14 +193,14 @@ func ActivePrecompiles(rules *chain.Rules) []libcommon.Address { // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger EVMLogger, +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks, ) (ret []byte, remainingGas uint64, err error) { gasCost := p.RequiredGas(input) if suppliedGas < gasCost { return nil, 0, ErrOutOfGas } - if logger != nil { - logger.OnGasChange(suppliedGas, suppliedGas-gasCost, GasChangeCallPrecompiledContract) + if logger != nil && logger.OnGasChange != nil { + logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract) } suppliedGas -= gasCost output, err := p.Run(input) diff --git a/core/vm/evm.go b/core/vm/evm.go index 0ba75ef8492..c4c3907ec85 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -26,6 +26,7 @@ import ( libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon/common/u256" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/params" @@ -182,9 +183,9 @@ func (evm *EVM) call(typ OpCode, caller ContractRef, addr libcommon.Address, inp // DELEGATECALL inherits value from parent call v = parent.value } - evm.captureBegin(depth == 0, typ, caller.Address(), addr, isPrecompile, input, gas, v, code) + evm.captureBegin(depth, typ, caller.Address(), addr, isPrecompile, input, gas, v, code) defer func(startGas uint64) { - evm.captureEnd(depth == 0, typ, startGas, leftOverGas, ret, err) + evm.captureEnd(depth, typ, startGas, leftOverGas, ret, err) }(gas) } @@ -220,7 +221,7 @@ func (evm *EVM) call(typ OpCode, caller ContractRef, addr libcommon.Address, inp // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, // but is the correct thing to do and matters on other networks, in tests, and potential // future scenarios - evm.intraBlockState.AddBalance(addr, u256.Num0, evmtypes.BalanceChangeTouchAccount) + evm.intraBlockState.AddBalance(addr, u256.Num0, tracing.BalanceChangeTouchAccount) } // It is allowed to call precompiles, even via delegatecall @@ -260,8 +261,8 @@ func (evm *EVM) call(typ OpCode, caller ContractRef, addr libcommon.Address, inp if err != nil || evm.config.RestoreState { evm.intraBlockState.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - if evm.Config().Tracer != nil { - evm.Config().Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution) + if evm.config.Tracer != nil && evm.config.Tracer.OnGasChange != nil { + evm.Config().Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) } gas = 0 } @@ -325,9 +326,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, depth := evm.interpreter.Depth() if evm.Config().Tracer != nil { - evm.captureBegin(depth == 0, typ, caller.Address(), address, false, codeAndHash.code, gas, value, nil) + evm.captureBegin(depth, typ, caller.Address(), address, false, codeAndHash.code, gas, value, nil) defer func(startGas uint64) { - evm.captureEnd(depth == 0, typ, startGas, leftOverGas, ret, err) + evm.captureEnd(depth, typ, startGas, leftOverGas, ret, err) }(gas) } @@ -358,8 +359,8 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, contractHash := evm.intraBlockState.GetCodeHash(address) if evm.intraBlockState.GetNonce(address) != 0 || (contractHash != (libcommon.Hash{}) && contractHash != emptyCodeHash) { err = ErrContractAddressCollision - if evm.Config().Tracer != nil { - evm.Config().Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution) + if evm.config.Tracer != nil && evm.config.Tracer.OnGasChange != nil { + evm.Config().Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) } return nil, libcommon.Address{}, 0, err } @@ -401,7 +402,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // by the error checking condition below. if err == nil { createDataGas := uint64(len(ret)) * params.CreateDataGas - if contract.UseGas(createDataGas, evm.Config().Tracer, GasChangeCallCodeStorage) { + if contract.UseGas(createDataGas, evm.Config().Tracer, tracing.GasChangeCallCodeStorage) { evm.intraBlockState.SetCode(address, ret) } else if evm.chainRules.IsHomestead { err = ErrCodeStoreOutOfGas @@ -414,7 +415,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.intraBlockState.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas, evm.Config().Tracer, GasChangeCallFailedExecution) + contract.UseGas(contract.Gas, evm.Config().Tracer, tracing.GasChangeCallFailedExecution) } } @@ -466,23 +467,22 @@ func (evm *EVM) IntraBlockState() evmtypes.IntraBlockState { return evm.intraBlockState } -func (evm *EVM) captureBegin(isRoot bool, typ OpCode, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, startGas uint64, value *uint256.Int, code []byte) { +func (evm *EVM) captureBegin(depth int, typ OpCode, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, startGas uint64, value *uint256.Int, code []byte) { tracer := evm.Config().Tracer - if isRoot { - tracer.CaptureStart(from, to, precompile, typ == CREATE || typ == CREATE2, input, startGas, value, code) - } else { - tracer.CaptureEnter(typ, from, to, precompile, typ == CREATE || typ == CREATE2, input, startGas, value, code) + if tracer.OnEnter != nil { + tracer.OnEnter(depth, byte(typ), from, to, precompile, input, startGas, value, code) + } + if tracer.OnGasChange != nil { + tracer.OnGasChange(0, startGas, tracing.GasChangeCallInitialBalance) } - - tracer.OnGasChange(0, startGas, GasChangeCallInitialBalance) } -func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas uint64, ret []byte, err error) { +func (evm *EVM) captureEnd(depth int, typ OpCode, startGas uint64, leftOverGas uint64, ret []byte, err error) { tracer := evm.Config().Tracer - if leftOverGas != 0 { - tracer.OnGasChange(leftOverGas, 0, GasChangeCallLeftOverReturned) + if leftOverGas != 0 && tracer.OnGasChange != nil { + tracer.OnGasChange(leftOverGas, 0, tracing.GasChangeCallLeftOverReturned) } var reverted bool @@ -493,9 +493,21 @@ func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas reverted = false } - if isRoot { - tracer.CaptureEnd(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) - } else { - tracer.CaptureExit(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) + if tracer.OnExit != nil { + tracer.OnExit(depth, ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) + } +} + +// GetVMContext provides context about the block being executed as well as state +// to the tracers. +func (evm *EVM) GetVMContext() *tracing.VMContext { + return &tracing.VMContext{ + Coinbase: evm.Context.Coinbase, + BlockNumber: evm.Context.BlockNumber, + Time: evm.Context.Time, + Random: evm.Context.PrevRanDao, + GasPrice: evm.TxContext.GasPrice, + ChainConfig: evm.ChainConfig(), + IntraBlockState: evm.IntraBlockState(), } } diff --git a/core/vm/evmtypes/evmtypes.go b/core/vm/evmtypes/evmtypes.go index 6f0e6fcc89e..a03be566ad9 100644 --- a/core/vm/evmtypes/evmtypes.go +++ b/core/vm/evmtypes/evmtypes.go @@ -9,6 +9,7 @@ import ( "github.com/ledgerwatch/erigon-lib/common" types2 "github.com/ledgerwatch/erigon-lib/types" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" ) @@ -55,60 +56,12 @@ type ( GetHashFunc func(uint64) common.Hash ) -// BalanceChangeReason is used to indicate the reason for a balance change, useful -// for tracing and reporting. -type BalanceChangeReason byte - -const ( - BalanceChangeUnspecified BalanceChangeReason = 0 - // Issuance - // BalanceIncreaseRewardMineUncle is a reward for mining an uncle block. - BalanceIncreaseRewardMineUncle BalanceChangeReason = 1 - // BalanceIncreaseRewardMineBlock is a reward for mining a block. - BalanceIncreaseRewardMineBlock BalanceChangeReason = 2 - // BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain. - BalanceIncreaseWithdrawal BalanceChangeReason = 3 - // BalanceIncreaseGenesisBalance is ether allocated at the genesis block. - BalanceIncreaseGenesisBalance BalanceChangeReason = 4 - - // Transaction fees - // BalanceIncreaseRewardTransactionFee is the transaction tip increasing block builder's balance. - BalanceIncreaseRewardTransactionFee BalanceChangeReason = 5 - // BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction. - // Part of this gas will be burnt as per EIP-1559 rules. - BalanceDecreaseGasBuy BalanceChangeReason = 6 - // BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution. - BalanceIncreaseGasReturn BalanceChangeReason = 7 - - // DAO fork - // BalanceIncreaseDaoContract is ether sent to the DAO refund contract. - BalanceIncreaseDaoContract BalanceChangeReason = 8 - // BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved to the refund contract. - BalanceDecreaseDaoAccount BalanceChangeReason = 9 - - // BalanceChangeTransfer is ether transfered via a call. - // it is a decrease for the sender and an increase for the recipient. - BalanceChangeTransfer BalanceChangeReason = 10 - // BalanceChangeTouchAccount is a transfer of zero value. It is only there to - // touch-create an account. - BalanceChangeTouchAccount BalanceChangeReason = 11 - - // BalanceIncreaseSelfdestruct is added to the recipient as indicated by a selfdestructing account. - BalanceIncreaseSelfdestruct BalanceChangeReason = 12 - // BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct. - BalanceDecreaseSelfdestruct BalanceChangeReason = 13 - // BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed - // account within the same tx (captured at end of tx). - // Note it doesn't account for a self-destruct which appoints itself as recipient. - BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14 -) - // IntraBlockState is an EVM database for full state querying. type IntraBlockState interface { CreateAccount(common.Address, bool) - SubBalance(common.Address, *uint256.Int, BalanceChangeReason) - AddBalance(common.Address, *uint256.Int, BalanceChangeReason) + SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) + AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) GetBalance(common.Address) *uint256.Int GetNonce(common.Address) uint64 diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9a22dff97db..c48414744bb 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -26,8 +26,8 @@ import ( "github.com/ledgerwatch/log/v3" "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/params" ) @@ -279,9 +279,6 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( panic(err) } - if interpreter.evm.Config().Tracer != nil { - interpreter.evm.Config().Tracer.CaptureKeccakPreimage(libcommon.BytesToHash(interpreter.hasherBuf[:]), data) - } size.SetBytes(interpreter.hasherBuf[:]) return nil, nil } @@ -655,7 +652,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // reuse size int for stackvalue stackvalue := size - scope.Contract.UseGas(gas, interpreter.evm.Config().Tracer, GasChangeCallContractCreation) + scope.Contract.UseGas(gas, interpreter.evm.Config().Tracer, tracing.GasChangeCallContractCreation) res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, &value) @@ -671,10 +668,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b stackvalue.SetBytes(addr.Bytes()) } - if interpreter.evm.Config().Tracer != nil && returnGas > 0 { - interpreter.evm.Config().Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) - } - scope.Contract.Gas += returnGas + scope.Contract.RefundGas(returnGas, interpreter.evm.config.Tracer, tracing.GasChangeCallLeftOverRefunded) if suberr == ErrExecutionReverted { interpreter.returnData = res // set REVERT data to return data buffer @@ -698,7 +692,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] // Apply EIP150 gas -= gas / 64 - scope.Contract.UseGas(gas, interpreter.evm.Config().Tracer, GasChangeCallContractCreation2) + scope.Contract.UseGas(gas, interpreter.evm.Config().Tracer, tracing.GasChangeCallContractCreation2) // reuse size int for stackvalue stackValue := size res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas, &endowment, &salt) @@ -711,11 +705,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] } scope.Stack.Push(&stackValue) - if interpreter.evm.Config().Tracer != nil && returnGas > 0 { - interpreter.evm.Config().Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) - } - - scope.Contract.Gas += returnGas + scope.Contract.RefundGas(returnGas, interpreter.evm.config.Tracer, tracing.GasChangeCallLeftOverRefunded) if suberr == ErrExecutionReverted { interpreter.returnData = res // set REVERT data to return data buffer @@ -757,11 +747,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - if interpreter.evm.Config().Tracer != nil && returnGas > 0 { - interpreter.evm.Config().Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) - } - - scope.Contract.Gas += returnGas + scope.Contract.RefundGas(returnGas, interpreter.evm.config.Tracer, tracing.GasChangeCallLeftOverRefunded) interpreter.returnData = ret return ret, nil @@ -795,11 +781,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - if interpreter.evm.Config().Tracer != nil && returnGas > 0 { - interpreter.evm.Config().Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) - } - - scope.Contract.Gas += returnGas + scope.Contract.RefundGas(returnGas, interpreter.evm.config.Tracer, tracing.GasChangeCallLeftOverRefunded) interpreter.returnData = ret return ret, nil @@ -829,11 +811,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - if interpreter.evm.Config().Tracer != nil && returnGas > 0 { - interpreter.evm.Config().Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) - } - - scope.Contract.Gas += returnGas + scope.Contract.RefundGas(returnGas, interpreter.evm.config.Tracer, tracing.GasChangeCallLeftOverRefunded) interpreter.returnData = ret return ret, nil @@ -863,11 +841,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - if interpreter.evm.Config().Tracer != nil && returnGas > 0 { - interpreter.evm.Config().Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) - } - - scope.Contract.Gas += returnGas + scope.Contract.RefundGas(returnGas, interpreter.evm.config.Tracer, tracing.GasChangeCallLeftOverRefunded) interpreter.returnData = ret return ret, nil @@ -902,11 +876,15 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext callerAddr := scope.Contract.Address() beneficiaryAddr := libcommon.Address(beneficiary.Bytes20()) balance := *interpreter.evm.IntraBlockState().GetBalance(callerAddr) - interpreter.evm.IntraBlockState().AddBalance(beneficiaryAddr, &balance, evmtypes.BalanceIncreaseSelfdestruct) + interpreter.evm.IntraBlockState().AddBalance(beneficiaryAddr, &balance, tracing.BalanceIncreaseSelfdestruct) interpreter.evm.IntraBlockState().Selfdestruct(callerAddr) if interpreter.evm.Config().Tracer != nil { - interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, callerAddr, beneficiaryAddr, false /* precompile */, false /* create */, []byte{}, 0, &balance, nil /* code */) - interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil, false) + if interpreter.evm.Config().Tracer.OnEnter != nil { + interpreter.evm.Config().Tracer.OnEnter(interpreter.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), false, []byte{}, 0, &balance, nil) + } + if interpreter.evm.Config().Tracer.OnExit != nil { + interpreter.evm.Config().Tracer.OnExit(interpreter.depth, []byte{}, 0, nil, false) + } } return nil, errStopToken } @@ -919,12 +897,16 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon callerAddr := scope.Contract.Address() beneficiaryAddr := libcommon.Address(beneficiary.Bytes20()) balance := *interpreter.evm.IntraBlockState().GetBalance(callerAddr) - interpreter.evm.IntraBlockState().SubBalance(callerAddr, &balance, evmtypes.BalanceDecreaseSelfdestruct) - interpreter.evm.IntraBlockState().AddBalance(beneficiaryAddr, &balance, evmtypes.BalanceIncreaseSelfdestruct) + interpreter.evm.IntraBlockState().SubBalance(callerAddr, &balance, tracing.BalanceDecreaseSelfdestruct) + interpreter.evm.IntraBlockState().AddBalance(beneficiaryAddr, &balance, tracing.BalanceIncreaseSelfdestruct) interpreter.evm.IntraBlockState().Selfdestruct6780(callerAddr) if interpreter.evm.Config().Tracer != nil { - interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, callerAddr, beneficiaryAddr, false /* precompile */, false /* create */, []byte{}, 0, &balance, nil /* code */) - interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil, false) + if interpreter.cfg.Tracer.OnEnter != nil { + interpreter.cfg.Tracer.OnEnter(interpreter.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), false, []byte{}, 0, &balance, nil) + } + if interpreter.cfg.Tracer.OnExit != nil { + interpreter.cfg.Tracer.OnExit(interpreter.depth, []byte{}, 0, nil, false) + } } return nil, errStopToken } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index be294bf5af0..ac409915f1e 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -20,26 +20,29 @@ import ( "hash" "sync" + "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/chain" + "github.com/ledgerwatch/erigon-lib/common" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/math" "github.com/ledgerwatch/log/v3" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/vm/stack" ) // Config are the configuration options for the Interpreter type Config struct { - Debug bool // Enables debugging - Tracer EVMLogger // Opcode logger - NoRecursion bool // Disables call, callcode, delegate call and create - NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) - SkipAnalysis bool // Whether we can skip jumpdest analysis based on the checked history - TraceJumpDest bool // Print transaction hashes where jumpdest analysis was useful - NoReceipts bool // Do not calculate receipts - ReadOnly bool // Do no perform any block finalisation - StatelessExec bool // true is certain conditions (like state trie root hash matching) need to be relaxed for stateless EVM execution - RestoreState bool // Revert all changes made to the state (useful for constant system calls) + Debug bool // Enables debugging + Tracer *tracing.Hooks + NoRecursion bool // Disables call, callcode, delegate call and create + NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) + SkipAnalysis bool // Whether we can skip jumpdest analysis based on the checked history + TraceJumpDest bool // Print transaction hashes where jumpdest analysis was useful + NoReceipts bool // Do not calculate receipts + ReadOnly bool // Do no perform any block finalisation + StatelessExec bool // true is certain conditions (like state trie root hash matching) need to be relaxed for stateless EVM execution + RestoreState bool // Revert all changes made to the state (useful for constant system calls) ExtraEips []int // Additional EIPS that are to be enabled } @@ -80,6 +83,53 @@ type ScopeContext struct { Contract *Contract } +// MemoryData returns the underlying memory slice. Callers must not modify the contents +// of the returned data. +func (ctx *ScopeContext) MemoryData() []byte { + if ctx.Memory == nil { + return nil + } + return ctx.Memory.Data() +} + +// MemoryData returns the stack data. Callers must not modify the contents +// of the returned data. +func (ctx *ScopeContext) StackData() []uint256.Int { + if ctx.Stack == nil { + return nil + } + return ctx.Stack.Data +} + +// Caller returns the current caller. +func (ctx *ScopeContext) Caller() common.Address { + return ctx.Contract.Caller() +} + +// Address returns the address where this scope of execution is taking place. +func (ctx *ScopeContext) Address() common.Address { + return ctx.Contract.Address() +} + +// CallValue returns the value supplied with this call. +func (ctx *ScopeContext) CallValue() *uint256.Int { + return ctx.Contract.Value() +} + +// CallInput returns the input/calldata with this call. Callers must not modify +// the contents of the returned data. +func (ctx *ScopeContext) CallInput() []byte { + return ctx.Contract.Input +} + +func (ctx *ScopeContext) Code() []byte { + return ctx.Contract.Code +} + +func (ctx *ScopeContext) CodeHash() libcommon.Hash { + return ctx.Contract.CodeHash +} + // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports // Read to get a variable amount of data from the hash state. Read is faster than Sum // because it doesn't copy the internal state, but also modifies the internal state. @@ -226,10 +276,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( defer func() { // first: capture data/memory/state/depth/etc... then clenup them if in.cfg.Tracer != nil && err != nil { - if !logged { - in.cfg.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.depth, VMErrorFromErr(err)) //nolint:errcheck - } else { - in.cfg.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.depth, VMErrorFromErr(err)) + if !logged && in.evm.config.Tracer.OnOpcode != nil { + in.evm.config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, in.returnData, in.depth, VMErrorFromErr(err)) + } + if logged && in.evm.config.Tracer.OnFault != nil { + in.evm.config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, in.depth, VMErrorFromErr(err)) } } // this function must execute _after_: the `CaptureState` needs the stacks before @@ -266,7 +317,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } else if sLen > operation.maxStack { return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } - if !contract.UseGas(cost, in.cfg.Tracer, GasChangeIgnored) { + if !contract.UseGas(cost, in.cfg.Tracer, tracing.GasChangeIgnored) { return nil, ErrOutOfGas } if operation.dynamicGas != nil { @@ -292,21 +343,27 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( var dynamicCost uint64 dynamicCost, err = operation.dynamicGas(in.evm, contract, locStack, mem, memorySize) cost += dynamicCost // for tracing - if err != nil || !contract.UseGas(dynamicCost, in.cfg.Tracer, GasChangeIgnored) { + if err != nil || !contract.UseGas(dynamicCost, in.cfg.Tracer, tracing.GasChangeIgnored) { return nil, ErrOutOfGas } // Do tracing before memory expansion if in.cfg.Tracer != nil { - in.cfg.Tracer.CaptureState(_pc, op, gasCopy, cost, callContext, in.returnData, in.depth, err) //nolint:errcheck - logged = true + if in.evm.config.Tracer.OnOpcode != nil { + in.evm.config.Tracer.OnOpcode(_pc, byte(op), gasCopy, cost, callContext, in.returnData, in.depth, VMErrorFromErr(err)) + logged = true + } } if memorySize > 0 { mem.Resize(memorySize) } } else if in.cfg.Tracer != nil { - in.cfg.Tracer.OnGasChange(gasCopy, gasCopy-cost, GasChangeCallOpCode) - in.cfg.Tracer.CaptureState(_pc, op, gasCopy, cost, callContext, in.returnData, in.depth, VMErrorFromErr(err)) //nolint:errcheck - logged = true + if in.evm.config.Tracer.OnGasChange != nil { + in.evm.config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) + } + if in.evm.config.Tracer.OnOpcode != nil { + in.evm.config.Tracer.OnOpcode(_pc, byte(op), gasCopy, cost, callContext, in.returnData, in.depth, VMErrorFromErr(err)) + logged = true + } } // execute the operation res, err = operation.execute(pc, in, callContext) diff --git a/core/vm/logger.go b/core/vm/logger.go deleted file mode 100644 index b27825b5ac5..00000000000 --- a/core/vm/logger.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2015 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 vm - -import ( - "github.com/holiman/uint256" - libcommon "github.com/ledgerwatch/erigon-lib/common" - - "github.com/ledgerwatch/erigon/core/types" -) - -// EVMLogger is used to collect execution traces from an EVM transaction -// execution. CaptureState is called for each step of the VM with the -// current VM state. -// Note that reference types are actual VM data structures; make copies -// if you need to retain them beyond the current call. -type EVMLogger interface { - // Transaction level - CaptureTxStart(evm *EVM, tx types.Transaction) - CaptureTxEnd(receipt *types.Receipt, err error) - // Top call frame - CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) - // CaptureEnd is invoked when the processing of the top call ends. - // See docs for `CaptureExit` for info on the `reverted` parameter. - CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) - // Rest of the frames - CaptureEnter(typ OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) - // CaptureExit is invoked when the processing of a message ends. - // `revert` is true when there was an error during the execution. - // Exceptionally, before the homestead hardfork a contract creation that - // ran out of gas when attempting to persist the code to database did not - // count as a call failure and did not cause a revert of the call. This will - // be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`. - CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) - // Opcode level - CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) - CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) - CaptureKeccakPreimage(hash libcommon.Hash, data []byte) - // Misc - OnGasChange(old, new uint64, reason GasChangeReason) -} - -// GasChangeReason is used to indicate the reason for a gas change, useful -// for tracing and reporting. -// -// There is essentially two types of gas changes, those that can be emitted once per transaction -// and those that can be emitted on a call basis, so possibly multiple times per transaction. -// -// They can be recognized easily by their name, those that start with `GasChangeTx` are emitted -// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis. -type GasChangeReason byte - -const ( - GasChangeUnspecified GasChangeReason = iota - - // GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only - // one such gas change per transaction. - GasChangeTxInitialBalance - // GasChangeTxIntrinsicGas is the amount of gas that will be charged for the intrinsic cost of the transaction, there is - // always exactly one of those per transaction. - GasChangeTxIntrinsicGas - // GasChangeTxRefunds is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) - // this generates an increase in gas. There is at most one of such gas change per transaction. - GasChangeTxRefunds - // GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned - // to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas - // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. - // There is at most one of such gas change per transaction. - GasChangeTxLeftOverReturned - - // GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only - // one such gas change per call. - GasChangeCallInitialBalance - // GasChangeCallLeftOverReturned is the amount of gas left over that will be returned to the caller, this change will always - // be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even - // will be emitted. - GasChangeCallLeftOverReturned - // GasChangeCallLeftOverRefunded is the amount of gas that will be refunded to the call after the child call execution it - // executed completed. This value is always positive as we are giving gas back to the you, the left over gas of the child. - // If there was no gas left to be refunded, no such even will be emitted. - GasChangeCallLeftOverRefunded - // GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE. - GasChangeCallContractCreation - // GasChangeContractCreation is the amount of gas that will be burned for a CREATE2. - GasChangeCallContractCreation2 - // GasChangeCallCodeStorage is the amount of gas that will be charged for code storage. - GasChangeCallCodeStorage - // GasChangeCallOpCode is the amount of gas that will be charged for an opcode executed by the EVM, exact opcode that was - // performed can be check by `CaptureState` handling. - GasChangeCallOpCode - // GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution. - GasChangeCallPrecompiledContract - // GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules. - GasChangeCallStorageColdAccess - // GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert. - GasChangeCallFailedExecution - - // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as - // it will be "manually" tracked by a direct emit of the gas change event. - GasChangeIgnored GasChangeReason = 0xFF -) - -// FlushableTracer is a Tracer extension whose accumulated traces has to be -// flushed once the tracing is completed. -type FlushableTracer interface { - EVMLogger - Flush(tx types.Transaction) -} diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index c80016783a2..678891adda9 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -23,6 +23,7 @@ import ( libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/math" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/vm/stack" "github.com/ledgerwatch/erigon/params" ) @@ -163,7 +164,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { if addrMod { // Charge the remaining difference here already, to correctly calculate available // gas for call - if !contract.UseGas(coldCost, evm.Config().Tracer, GasChangeCallStorageColdAccess) { + if !contract.UseGas(coldCost, evm.Config().Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 892af8b9c26..ee221c5b237 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -136,7 +136,7 @@ func Execute(code, input []byte, cfg *Config, bn uint64) ([]byte, *state.IntraBl rules = vmenv.ChainRules() ) if cfg.EVMConfig.Tracer != nil { - cfg.EVMConfig.Tracer.CaptureTxStart(vmenv, types.NewTransaction(0, address, cfg.Value, cfg.GasLimit, cfg.GasPrice, input)) + cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTransaction(0, address, cfg.Value, cfg.GasLimit, cfg.GasPrice, input), cfg.Origin) } cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) cfg.State.CreateAccount(address, true) @@ -183,7 +183,7 @@ func Create(input []byte, cfg *Config, blockNr uint64) ([]byte, libcommon.Addres rules = vmenv.ChainRules() ) if cfg.EVMConfig.Tracer != nil { - cfg.EVMConfig.Tracer.CaptureTxStart(vmenv, types.NewContractCreation(0, cfg.Value, cfg.GasLimit, cfg.GasPrice, input)) + cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewContractCreation(0, cfg.Value, cfg.GasLimit, cfg.GasPrice, input), cfg.Origin) } cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) @@ -212,7 +212,7 @@ func Call(address libcommon.Address, input []byte, cfg *Config) ([]byte, uint64, statedb := cfg.State rules := vmenv.ChainRules() if cfg.EVMConfig.Tracer != nil { - cfg.EVMConfig.Tracer.CaptureTxStart(vmenv, types.NewTransaction(0, address, cfg.Value, cfg.GasLimit, cfg.GasPrice, input)) + cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTransaction(0, address, cfg.Value, cfg.GasLimit, cfg.GasPrice, input), cfg.Origin) } statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 1e326eea237..f4d8ffb801f 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -525,7 +525,7 @@ func TestEip2929Cases(t *testing.T) { Execute(code, nil, &Config{ EVMConfig: vm.Config{ Debug: true, - Tracer: logger.NewMarkdownLogger(nil, os.Stdout), + Tracer: logger.NewMarkdownLogger(nil, os.Stdout).Hooks(), ExtraEips: []int{2929}, }, }, 0) diff --git a/eth/backend.go b/eth/backend.go index 55726c14bab..d8027683707 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -220,7 +220,7 @@ const blockBufferSize = 128 // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger log.Logger, tracer tracers.Tracer) (*Ethereum, error) { +func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger log.Logger, tracer *tracers.Tracer) (*Ethereum, error) { config.Snapshot.Enabled = config.Sync.UseSnapshots if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(libcommon.Big0) <= 0 { logger.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice) @@ -287,7 +287,7 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger genesisSpec = nil } var genesisErr error - chainConfig, genesis, genesisErr = core.WriteGenesisBlock(tx, genesisSpec, config.OverrideCancunTime, tmpdir, logger, tracer) + chainConfig, genesis, genesisErr = core.WriteGenesisBlock(tx, genesisSpec, config.OverrideCancunTime, tmpdir, logger, tracer.Hooks) if _, ok := genesisErr.(*chain.ConfigCompatError); genesisErr != nil && !ok { return genesisErr } diff --git a/eth/calltracer/calltracer.go b/eth/calltracer/calltracer.go index 4104f3c454b..ad98d6a02f1 100644 --- a/eth/calltracer/calltracer.go +++ b/eth/calltracer/calltracer.go @@ -2,22 +2,23 @@ package calltracer import ( "encoding/binary" - "math/big" "sort" "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/length" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" + "github.com/ledgerwatch/erigon/eth/tracers" ) type CallTracer struct { + t *tracers.Tracer + froms map[libcommon.Address]struct{} tos map[libcommon.Address]bool // address -> isCreated } @@ -29,9 +30,13 @@ func NewCallTracer() *CallTracer { } } -func (ct *CallTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) {} - -func (ct *CallTracer) CaptureTxEnd(receipt *types.Receipt, err error) {} +func (ct *CallTracer) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnEnter: ct.OnEnter, + }, + } +} // CaptureStart and CaptureEnter also capture SELFDESTRUCT opcode invocations func (ct *CallTracer) captureStartOrEnter(from, to libcommon.Address, create bool, code []byte) { @@ -49,49 +54,10 @@ func (ct *CallTracer) captureStartOrEnter(from, to libcommon.Address, create boo } } -func (ct *CallTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (ct *CallTracer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + create := vm.OpCode(typ) == vm.CREATE ct.captureStartOrEnter(from, to, create, code) } -func (ct *CallTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - ct.captureStartOrEnter(from, to, create, code) -} -func (ct *CallTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { -} -func (ct *CallTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { -} -func (ct *CallTracer) CaptureEnd(output []byte, usedGas uint64, err error, reverted bool) { -} - -func (ct *CallTracer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { -} - -func (ct *CallTracer) OnBlockEnd(err error) {} - -func (ct *CallTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) {} - -func (ct *CallTracer) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (ct *CallTracer) OnBeaconBlockRootEnd() {} - -func (ct *CallTracer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (ct *CallTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (ct *CallTracer) OnBalanceChange(addr libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (ct *CallTracer) OnNonceChange(addr libcommon.Address, prev, new uint64) {} - -func (ct *CallTracer) OnCodeChange(addr libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (ct *CallTracer) OnStorageChange(addr libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { -} - -func (ct *CallTracer) OnLog(log *types.Log) {} - -func (ct *CallTracer) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { -} func (ct *CallTracer) WriteToDb(tx kv.StatelessWriteTx, block *types.Block, vmConfig vm.Config) error { ct.tos[block.Coinbase()] = false diff --git a/eth/stagedsync/stage_execute.go b/eth/stagedsync/stage_execute.go index c63182c5c92..12b43eb8b0f 100644 --- a/eth/stagedsync/stage_execute.go +++ b/eth/stagedsync/stage_execute.go @@ -36,6 +36,7 @@ import ( "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/core/vm" @@ -158,14 +159,14 @@ func executeBlock( return h } - getTracer := func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { - return tracelogger.NewStructLogger(&tracelogger.LogConfig{}), nil + getTracer := func(txIndex int, txHash common.Hash) (*tracing.Hooks, error) { + return tracelogger.NewStructLogger(&tracelogger.LogConfig{}).Hooks(), nil } callTracer := calltracer.NewCallTracer() vmConfig.Debug = true if vmConfig.Tracer == nil { - vmConfig.Tracer = callTracer + vmConfig.Tracer = callTracer.Tracer().Hooks } var receipts types.Receipts diff --git a/eth/tracers/api.go b/eth/tracers/config/api.go similarity index 97% rename from eth/tracers/api.go rename to eth/tracers/config/api.go index 9890821192a..9e3e0b2a654 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/config/api.go @@ -1,4 +1,4 @@ -package tracers +package config import ( "encoding/json" diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 35dfa8d9c19..792f3d835cc 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -161,18 +161,18 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - statedb.SetLogger(tracer) - evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) + statedb.SetLogger(tracer.Hooks) + evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer.Hooks}) msg, err := tx.AsMessage(*signer, test.Genesis.BaseFee, rules) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - tracer.CaptureTxStart(evm, tx) + tracer.OnTxStart(evm.GetVMContext(), tx, msg.From()) vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.GetGas()).AddBlobGas(tx.GetBlobGas()), true /* refunds */, false /* gasBailout */) if err != nil { t.Fatalf("failed to execute transaction: %v", err) } - tracer.CaptureTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, err) + tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, err) // Retrieve the trace result and compare against the expected. res, err := tracer.GetResult() if err != nil { @@ -272,7 +272,7 @@ func benchTracer(b *testing.B, tracerName string, test *callTracerTest) { if err != nil { b.Fatalf("failed to create call tracer: %v", err) } - evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) + evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer.Hooks}) snap := statedb.Snapshot() st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.GetGas()).AddBlobGas(tx.GetBlobGas())) if _, err = st.TransitionDb(true /* refunds */, false /* gasBailout */); err != nil { @@ -346,18 +346,18 @@ func TestZeroValueToNotExitCall(t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - statedb.SetLogger(tracer) - evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) - tracer.CaptureTxStart(evm, tx) + statedb.SetLogger(tracer.Hooks) + evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer.Hooks}) msg, err := tx.AsMessage(*signer, nil, rules) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } + tracer.OnTxStart(evm.GetVMContext(), tx, msg.From()) vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.GetGas()).AddBlobGas(tx.GetBlobGas()), true /* refunds */, false /* gasBailout */) if err != nil { t.Fatalf("failed to execute transaction: %v", err) } - tracer.CaptureTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, err) + tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, err) // Retrieve the trace result and compare against the etalon res, err := tracer.GetResult() if err != nil { diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index f052192c171..510ace78106 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -126,18 +126,18 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - statedb.SetLogger(tracer) - evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) + statedb.SetLogger(tracer.Hooks) + evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer.Hooks}) msg, err := tx.AsMessage(*signer, nil, rules) // BaseFee is set to nil and not to contet.BaseFee, to match the output to go-ethereum tests if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - tracer.CaptureTxStart(evm, tx) + tracer.OnTxStart(evm.GetVMContext(), tx, msg.From()) vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.GetGas()), true, false) if err != nil { t.Fatalf("failed to execute transaction: %v", err) } - tracer.CaptureTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) + tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) // Retrieve the trace result and compare against the expected res, err := tracer.GetResult() if err != nil { diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index 8ab2df831af..800f01e028b 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -28,10 +28,9 @@ import ( "github.com/ledgerwatch/erigon-lib/common/hexutility" "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" - "github.com/ledgerwatch/erigon/core/vm/stack" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/eth/tracers" jsassets "github.com/ledgerwatch/erigon/eth/tracers/js/internal/tracers" @@ -98,7 +97,7 @@ type jsTracer struct { tracers.NoopTracer vm *goja.Runtime - env *vm.EVM + env *tracing.VMContext toBig toBigFn // Converts a hex string into a JS bigint toBuf toBufFn // Converts a []byte into a JS buffer fromBuf fromBufFn // Converts an array, hex string or Uint8Array to a []byte @@ -134,7 +133,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 *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { if c, ok := assetTracers[code]; ok { code = c } @@ -210,26 +209,37 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer t.frameValue = t.frame.setupObject() t.frameResultValue = t.frameResult.setupObject() t.logValue = t.log.setupObject() - return t, nil -} - -// CaptureTxStart implements the Tracer interface and is invoked at the beginning of + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil +} + +// OnTxStart implements the Tracer interface and is invoked at the beginning of // transaction processing. -func (t *jsTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { +func (t *jsTracer) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { t.env = env // Need statedb access for db object - db := &dbObj{ibs: env.IntraBlockState(), vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf} + db := &dbObj{ibs: env.IntraBlockState, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf} t.dbValue = db.setupObject() // Update list of precompiles based on current block - rules := env.ChainRules() + rules := env.ChainConfig.Rules(env.BlockNumber, env.Time) t.activePrecompiles = vm.ActivePrecompiles(rules) - t.ctx["block"] = t.vm.ToValue(t.env.Context.BlockNumber) - t.ctx["gasPrice"] = t.vm.ToValue(t.env.TxContext.GasPrice.ToBig()) + t.ctx["block"] = t.vm.ToValue(t.env.BlockNumber) + t.ctx["gasPrice"] = t.vm.ToValue(t.env.GasPrice.ToBig()) } -// CaptureTxEnd implements the Tracer interface and is invoked at the end of +// OnTxEnd implements the Tracer interface and is invoked at the end of // transaction processing. -func (t *jsTracer) CaptureTxEnd(receipt *types.Receipt, err error) { +func (t *jsTracer) OnTxEnd(receipt *types.Receipt, err error) { if err != nil { // Don't override vm error if _, ok := t.ctx["error"]; !ok { @@ -240,8 +250,11 @@ func (t *jsTracer) CaptureTxEnd(receipt *types.Receipt, err error) { t.ctx["gasUsed"] = t.vm.ToValue(receipt.GasUsed) } -// CaptureStart implements the Tracer interface to initialize the tracing operation. -func (t *jsTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +// onStart implements the Tracer interface to initialize the tracing operation. +func (t *jsTracer) onStart(from libcommon.Address, to libcommon.Address, create bool, input []byte, gas uint64, value *uint256.Int) { + if t.err != nil { + return + } if create { t.ctx["type"] = t.vm.ToValue("CREATE") } else { @@ -259,8 +272,8 @@ func (t *jsTracer) CaptureStart(from libcommon.Address, to libcommon.Address, pr t.ctx["value"] = valueBig } -// CaptureState implements the Tracer interface to trace a single step of VM execution. -func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +// OnOpcode implements the Tracer interface to trace a single step of VM execution. +func (t *jsTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { if !t.traceStep { return } @@ -269,14 +282,14 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope } log := t.log - log.op.op = op - log.memory.memory = scope.Memory - log.stack.stack = scope.Stack - log.contract.contract = scope.Contract + log.op.op = vm.OpCode(op) + log.memory.memory = scope.MemoryData() + log.stack.stack = scope.StackData() + log.contract.scope = scope log.pc = pc log.gas = gas log.cost = cost - log.refund = t.env.IntraBlockState().GetRefund() + log.refund = t.env.IntraBlockState.GetRefund() log.depth = depth log.err = err if _, err := t.step(t.obj, t.logValue, t.dbValue); err != nil { @@ -284,28 +297,28 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope } } -// CaptureFault implements the Tracer interface to trace an execution fault -func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +// OnFault implements the Tracer interface to trace an execution fault +func (t *jsTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { if t.err != nil { return } - // Other log fields have been already set as part of the last CaptureState. + // Other log fields have been already set as part of the last OnFault. t.log.err = err if _, err := t.fault(t.obj, t.logValue, t.dbValue); err != nil { t.onError("fault", err) } } -// CaptureEnd is called after the call finishes to finalize the tracing. -func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { +// onEnd is called after the call finishes to finalize the tracing. +func (t *jsTracer) onEnd(output []byte, gasUsed uint64, err error, reverted bool) { t.ctx["output"] = t.vm.ToValue(output) if err != nil { t.ctx["error"] = t.vm.ToValue(err.Error()) } } -// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *jsTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *jsTracer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { if !t.traceFrame { return } @@ -313,7 +326,12 @@ func (t *jsTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcom return } - t.frame.typ = typ.String() + if depth == 0 { + t.onStart(from, to, vm.OpCode(typ) == vm.CREATE, input, gas, value) + return + } + + t.frame.typ = vm.OpCode(typ).String() t.frame.from = from t.frame.to = to t.frame.input = libcommon.CopyBytes(input) @@ -328,13 +346,22 @@ func (t *jsTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcom } } -// CaptureExit is called when EVM exits a scope, even if the scope didn't +// OnExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { +func (t *jsTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { if !t.traceFrame { return } + if t.err != nil { + return + } + + if depth == 0 { + t.onEnd(output, gasUsed, err, reverted) + return + } + t.frameResult.gasUsed = uint(gasUsed) t.frameResult.output = libcommon.CopyBytes(output) t.frameResult.err = err @@ -368,9 +395,6 @@ func (t *jsTracer) Stop(err error) { // execution. func (t *jsTracer) onError(context string, err error) { t.err = wrapError(context, err) - // `env` is set on CaptureStart which comes before any JS execution. - // So it should be non-nil. - t.env.Cancel() } func wrapError(context string, err error) error { @@ -549,7 +573,7 @@ func (o *opObj) setupObject() *goja.Object { } type memoryObj struct { - memory *vm.Memory + memory []byte vm *goja.Runtime toBig toBigFn toBuf toBufFn @@ -577,14 +601,10 @@ 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) } - mlen := mo.memory.Len() - if end-int64(mlen) > memoryPadLimit { - return nil, fmt.Errorf("tracer reached limit for padding memory slice: end %d, memorySize %d", end, mlen) + slice, err := tracers.GetMemoryCopyPadded(mo.memory, begin, end-begin) + if err != nil { + return nil, err } - slice := make([]byte, end-begin) - end = min(end, int64(mo.memory.Len())) - ptr := mo.memory.GetPtr(begin, end-begin) - copy(slice, ptr) return slice, nil } @@ -604,14 +624,14 @@ func (mo *memoryObj) GetUint(addr int64) goja.Value { // getUint returns the 32 bytes at the specified address interpreted as a uint. func (mo *memoryObj) getUint(addr int64) (*big.Int, error) { - if mo.memory.Len() < int(addr)+32 || addr < 0 { - return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), addr, 32) + if len(mo.memory) < int(addr)+32 || addr < 0 { + return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", len(mo.memory), addr, 32) } - return new(big.Int).SetBytes(mo.memory.GetPtr(addr, 32)), nil + return new(big.Int).SetBytes(tracers.MemoryPtr(mo.memory, addr, 32)), nil } func (mo *memoryObj) Length() int { - return mo.memory.Len() + return len(mo.memory) } func (m *memoryObj) setupObject() *goja.Object { @@ -623,7 +643,7 @@ func (m *memoryObj) setupObject() *goja.Object { } type stackObj struct { - stack *stack.Stack + stack []uint256.Int vm *goja.Runtime toBig toBigFn } @@ -644,14 +664,14 @@ func (s *stackObj) Peek(idx int) goja.Value { // peek returns the nth-from-the-top element of the stack. func (s *stackObj) peek(idx int) (*big.Int, error) { - if len(s.stack.Data) <= idx || idx < 0 { - return nil, fmt.Errorf("tracer accessed out of bound stack: size %d, index %d", len(s.stack.Data), idx) + if len(s.stack) <= idx || idx < 0 { + return nil, fmt.Errorf("tracer accessed out of bound stack: size %d, index %d", len(s.stack), idx) } - return s.stack.Back(idx).ToBig(), nil + return tracers.StackBack(s.stack, idx).ToBig(), nil } func (s *stackObj) Length() int { - return len(s.stack.Data) + return len(s.stack) } func (s *stackObj) setupObject() *goja.Object { @@ -662,7 +682,7 @@ func (s *stackObj) setupObject() *goja.Object { } type dbObj struct { - ibs evmtypes.IntraBlockState + ibs tracing.IntraBlockState vm *goja.Runtime toBig toBigFn toBuf toBufFn @@ -755,14 +775,14 @@ func (do *dbObj) setupObject() *goja.Object { } type contractObj struct { - contract *vm.Contract - vm *goja.Runtime - toBig toBigFn - toBuf toBufFn + scope tracing.OpContext + vm *goja.Runtime + toBig toBigFn + toBuf toBufFn } func (co *contractObj) GetCaller() goja.Value { - caller := co.contract.Caller().Bytes() + caller := co.scope.Caller().Bytes() res, err := co.toBuf(co.vm, caller) if err != nil { co.vm.Interrupt(err) @@ -772,7 +792,7 @@ func (co *contractObj) GetCaller() goja.Value { } func (co *contractObj) GetAddress() goja.Value { - addr := co.contract.Address().Bytes() + addr := co.scope.Address().Bytes() res, err := co.toBuf(co.vm, addr) if err != nil { co.vm.Interrupt(err) @@ -782,7 +802,7 @@ func (co *contractObj) GetAddress() goja.Value { } func (co *contractObj) GetValue() goja.Value { - value := co.contract.Value() + value := co.scope.CallValue() res, err := co.toBig(co.vm, value.String()) if err != nil { co.vm.Interrupt(err) @@ -792,7 +812,7 @@ func (co *contractObj) GetValue() goja.Value { } func (co *contractObj) GetInput() goja.Value { - input := libcommon.CopyBytes(co.contract.Input) + input := libcommon.CopyBytes(co.scope.CallInput()) res, err := co.toBuf(co.vm, input) if err != nil { co.vm.Interrupt(err) diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 5485a815f8d..40e77ff9a08 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -65,9 +65,9 @@ func testCtx() *vmContext { return &vmContext{blockCtx: evmtypes.BlockContext{BlockNumber: 1}, txCtx: evmtypes.TxContext{GasPrice: uint256.NewInt(100000)}} } -func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *chain.Config, contractCode []byte) (json.RawMessage, error) { +func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *chain.Config, contractCode []byte) (json.RawMessage, error) { var ( - env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer}) + env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer.Hooks}) gasLimit uint64 = 31000 startGas uint64 = 10000 value = uint256.NewInt(0) @@ -78,12 +78,12 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *chain.Config, c contract.Code = contractCode } - tracer.CaptureTxStart(env, types.NewTransaction(0, libcommon.Address{}, nil, gasLimit, nil, nil)) - tracer.CaptureStart(contract.Caller(), contract.Address(), false /* precompile */, false /* create */, []byte{}, startGas, value, []byte{} /* code */) + tracer.OnTxStart(env.GetVMContext(), types.NewTransaction(0, libcommon.Address{}, nil, gasLimit, nil, nil), contract.Caller()) + tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), false, []byte{}, startGas, value, contractCode) ret, err := env.Interpreter().Run(contract, []byte{}, false) - tracer.CaptureEnd(ret, startGas-contract.Gas, err, true) + tracer.OnExit(0, ret, startGas-contract.Gas, err, true) // Rest gas assumes no refund - tracer.CaptureTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) + tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) if err != nil { return nil, err } @@ -185,16 +185,16 @@ func TestHaltBetweenSteps(t *testing.T) { if err != nil { t.Fatal(err) } - env := vm.NewEVM(evmtypes.BlockContext{BlockNumber: 1}, evmtypes.TxContext{GasPrice: uint256.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + env := vm.NewEVM(evmtypes.BlockContext{BlockNumber: 1}, evmtypes.TxContext{GasPrice: uint256.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer.Hooks}) scope := &vm.ScopeContext{ Contract: vm.NewContract(&account{}, libcommon.Address{}, uint256.NewInt(0), 0, false /* skipAnalysis */), } - tracer.CaptureTxStart(env, types.NewTransaction(0, libcommon.Address{}, new(uint256.Int), 0, new(uint256.Int), nil)) - tracer.CaptureStart(libcommon.Address{}, libcommon.Address{}, false /* precompile */, false /* create */, []byte{}, 0, uint256.NewInt(0), []byte{} /* code */) - tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil) + tracer.OnTxStart(env.GetVMContext(), types.NewTransaction(0, libcommon.Address{}, new(uint256.Int), 0, new(uint256.Int), nil), libcommon.Address{}) + tracer.OnEnter(0, byte(vm.CALL), libcommon.Address{}, libcommon.Address{}, false, []byte{}, 0, uint256.NewInt(0), []byte{}) + tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil) timeout := errors.New("stahp") tracer.Stop(timeout) - tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil) + tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil) if _, err := tracer.GetResult(); !strings.Contains(err.Error(), timeout.Error()) { t.Errorf("Expected timeout error, got %v", err) @@ -210,10 +210,10 @@ func TestNoStepExec(t *testing.T) { if err != nil { t.Fatal(err) } - env := vm.NewEVM(evmtypes.BlockContext{BlockNumber: 1}, evmtypes.TxContext{GasPrice: uint256.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) - tracer.CaptureTxStart(env, types.NewTransaction(0, libcommon.Address{}, new(uint256.Int), 0, new(uint256.Int), nil)) - tracer.CaptureStart(libcommon.Address{}, libcommon.Address{}, false /* precompile */, false /* create */, []byte{}, 1000, uint256.NewInt(0), []byte{} /* code */) - tracer.CaptureEnd(nil, 0, nil, false) + env := vm.NewEVM(evmtypes.BlockContext{BlockNumber: 1}, evmtypes.TxContext{GasPrice: uint256.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer.Hooks}) + tracer.OnTxStart(env.GetVMContext(), types.NewTransaction(0, libcommon.Address{}, new(uint256.Int), 0, new(uint256.Int), nil), libcommon.Address{}) + tracer.OnEnter(0, byte(vm.CALL), libcommon.Address{}, libcommon.Address{}, false, []byte{}, 1000, uint256.NewInt(0), []byte{}) + tracer.OnExit(0, nil, 0, nil, false) ret, err := tracer.GetResult() if err != nil { t.Fatal(err) @@ -282,8 +282,8 @@ func TestEnterExit(t *testing.T) { scope := &vm.ScopeContext{ Contract: vm.NewContract(&account{}, libcommon.Address{}, uint256.NewInt(0), 0, false /* skipAnalysis */), } - tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), false, false, []byte{}, 1000, new(uint256.Int), []byte{}) - tracer.CaptureExit([]byte{}, 400, nil, false) + tracer.OnEnter(1, byte(vm.CALL), scope.Contract.Caller(), scope.Contract.Address(), false, []byte{}, 1000, new(uint256.Int), []byte{}) + tracer.OnExit(1, []byte{}, 400, nil, false) have, err := tracer.GetResult() if err != nil { diff --git a/eth/tracers/live/printer.go b/eth/tracers/live/printer.go index 8a914e7bec9..2a850bc3669 100644 --- a/eth/tracers/live/printer.go +++ b/eth/tracers/live/printer.go @@ -9,9 +9,8 @@ import ( "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutility" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/eth/tracers" ) @@ -21,55 +20,58 @@ func init() { type Printer struct{} -func newPrinter(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { - return &Printer{}, nil +func newPrinter(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { + t := &Printer{} + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + OnGasChange: t.OnGasChange, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil } -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (p *Printer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - fmt.Printf("CaptureStart: from=%v, to=%v, precompile=%v, create=%v, input=%s, gas=%v, value=%v, code=%v\n", from, to, precompile, create, hexutility.Bytes(input), gas, value, hexutility.Bytes(code)) +// OnExit is called after the call finishes to finalize the tracing. +func (p *Printer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + fmt.Printf("OnExit: output=%s, gasUsed=%v, err=%v\n", hexutility.Bytes(output), gasUsed, err) } -// CaptureEnd is called after the call finishes to finalize the tracing. -func (p *Printer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { - fmt.Printf("CaptureEnd: output=%s, gasUsed=%v, err=%v\n", hexutility.Bytes(output), gasUsed, err) +// OnOpcode implements the EVMLogger interface to trace a single step of VM execution. +func (p *Printer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + fmt.Printf("OnOpcode: pc=%v, op=%v, gas=%v, cost=%v, scope=%v, rData=%v, depth=%v, err=%v\n", pc, op, gas, cost, scope, rData, depth, err) } -// CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (p *Printer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - //fmt.Printf("CaptureState: pc=%v, op=%v, gas=%v, cost=%v, scope=%v, rData=%v, depth=%v, err=%v\n", pc, op, gas, cost, scope, rData, depth, err) +// OnFault implements the EVMLogger interface to trace an execution fault. +func (p *Printer) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) { + fmt.Printf("OnFault: pc=%v, op=%v, gas=%v, cost=%v, depth=%v, err=%v\n", pc, op, gas, cost, depth, err) } -// CaptureFault implements the EVMLogger interface to trace an execution fault. -func (p *Printer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { - fmt.Printf("CaptureFault: pc=%v, op=%v, gas=%v, cost=%v, depth=%v, err=%v\n", pc, op, gas, cost, depth, err) +func (p *Printer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + fmt.Printf("CaptureEnter: depth=%v, typ=%v from=%v, to=%v, input=%s, gas=%v, value=%v\n", depth, typ, from, to, hexutility.Bytes(input), gas, value) } -// CaptureKeccakPreimage is called during the KECCAK256 opcode. -func (p *Printer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (p *Printer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - fmt.Printf("CaptureEnter: typ=%v, from=%v, to=%v, precompile=%v, create=%v, input=%s, gas=%v, value=%v, code=%v\n", typ, from, to, precompile, create, hexutility.Bytes(input), gas, value, hexutility.Bytes(code)) -} - -// CaptureExit is called when EVM exits a scope, even if the scope didn't -// execute any code. -func (p *Printer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { - fmt.Printf("CaptureExit: output=%s, gasUsed=%v, err=%v\n", hexutility.Bytes(output), gasUsed, err) -} - -func (p *Printer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { +func (p *Printer) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { buf, err := json.Marshal(tx) if err != nil { fmt.Printf("err: %v\n", err) return } - fmt.Printf("CaptureTxStart: tx=%s\n", buf) + fmt.Printf("OnTxStart: tx=%s\n", buf) } -func (p *Printer) CaptureTxEnd(receipt *types.Receipt, err error) { +func (p *Printer) OnTxEnd(receipt *types.Receipt, err error) { if err != nil { fmt.Printf("CaptureTxEnd err: %v\n", err) return @@ -98,15 +100,7 @@ func (p *Printer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { fmt.Printf("OnGenesisBlock: b=%v, allocLength=%d\n", b.NumberU64(), len(alloc)) } -func (p *Printer) OnBeaconBlockRootStart(root libcommon.Hash) { - fmt.Printf("OnBeaconBlockRootStart: b=%v", root) -} - -func (p *Printer) OnBeaconBlockRootEnd() { - fmt.Printf("OnBeaconBlockRootEnd:") -} - -func (p *Printer) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { +func (p *Printer) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason tracing.BalanceChangeReason) { fmt.Printf("OnBalanceChange: a=%v, prev=%v, new=%v\n", a, prev, new) } @@ -131,7 +125,7 @@ func (p *Printer) OnLog(l *types.Log) { fmt.Printf("OnLog: l=%s\n", buf) } -func (p *Printer) OnGasChange(old, new uint64, reason vm.GasChangeReason) { +func (p *Printer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { fmt.Printf("OnGasChange: old=%v, new=%v, diff=%v\n", old, new, new-old) } diff --git a/eth/tracers/live/tracer.go b/eth/tracers/live/tracer.go index f51277341ec..1b9979e31e6 100644 --- a/eth/tracers/live/tracer.go +++ b/eth/tracers/live/tracer.go @@ -13,7 +13,7 @@ func init() { } // ctorFn is the constructor signature of a native tracer. -type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error) +type ctorFn = func(*tracers.Context, json.RawMessage) (*tracers.Tracer, error) // ctors is a map of package-local tracer constructors. var ctors map[string]ctorFn @@ -27,7 +27,7 @@ func register(name string, ctor ctorFn) { } // lookup returns a tracer, if one can be matched to the given name. -func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { +func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { if ctors == nil { ctors = make(map[string]ctorFn) } diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index bad04e1a17b..21b4f910d1e 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -18,18 +18,14 @@ package logger import ( "encoding/json" - "math/big" "sort" - "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" types2 "github.com/ledgerwatch/erigon-lib/types" - "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/evmtypes" - "github.com/ledgerwatch/erigon/crypto" ) // accessList is an accumulator for the set of accounts and storage slots an EVM @@ -174,113 +170,33 @@ func NewAccessListTracer(acl types2.AccessList, exclude map[libcommon.Address]st } } -func (a *AccessListTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) {} - -func (a *AccessListTracer) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (a *AccessListTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { -} - -func (a *AccessListTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (a *AccessListTracer) Hooks() *tracing.Hooks { + return &tracing.Hooks{ + OnOpcode: a.OnOpcode, + } } -// 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 - contract := scope.Contract - caller := contract.Address() - - stackData := stack.Data +// OnOpcode captures all opcodes that touch storage or addresses and adds them to the accesslist. +func (a *AccessListTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + stackData := scope.StackData() stackLen := len(stackData) + op := vm.OpCode(opcode) if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 { - addr := contract.Address() slot := libcommon.Hash(stackData[stackLen-1].Bytes32()) - if _, ok := a.excl[addr]; !ok { - a.list.addSlot(addr, slot) - if _, ok := a.createdContracts[addr]; !ok { - a.usedBeforeCreation[addr] = struct{}{} - } - } + a.list.addSlot(scope.Address(), slot) } if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 { addr := libcommon.Address(stackData[stackLen-1].Bytes20()) if _, ok := a.excl[addr]; !ok { a.list.addAddress(addr) - if _, ok := a.createdContracts[addr]; !ok { - a.usedBeforeCreation[addr] = struct{}{} - } } } if (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE) && stackLen >= 5 { addr := libcommon.Address(stackData[stackLen-2].Bytes20()) if _, ok := a.excl[addr]; !ok { a.list.addAddress(addr) - if _, ok := a.createdContracts[addr]; !ok { - a.usedBeforeCreation[addr] = struct{}{} - } } } - if op == vm.CREATE { - // contract address for CREATE can only be generated with state - if a.state != nil { - nonce := a.state.GetNonce(caller) - addr := crypto.CreateAddress(caller, nonce) - if _, ok := a.excl[addr]; !ok { - a.createdContracts[addr] = struct{}{} - } - } - } - if op == vm.CREATE2 && stackLen >= 4 { - offset := stackData[stackLen-2] - size := stackData[stackLen-3] - init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) - inithash := crypto.Keccak256(init) - salt := stackData[stackLen-4] - addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) - if _, ok := a.excl[addr]; !ok { - a.createdContracts[addr] = struct{}{} - } - } - -} - -func (a *AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { -} - -func (a *AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { -} - -func (a *AccessListTracer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (a *AccessListTracer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { -} - -func (a *AccessListTracer) OnBlockEnd(err error) { -} - -func (a *AccessListTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { -} - -func (a *AccessListTracer) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (a *AccessListTracer) OnBeaconBlockRootEnd() {} - -func (a *AccessListTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (a *AccessListTracer) OnBalanceChange(addr libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (a *AccessListTracer) OnNonceChange(addr libcommon.Address, prev, new uint64) {} - -func (a *AccessListTracer) OnCodeChange(addr libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (a *AccessListTracer) OnStorageChange(addr libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { -} - -func (a *AccessListTracer) OnLog(log *types.Log) {} - -func (a *AccessListTracer) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { } // GetResult returns an empty json object. diff --git a/eth/tracers/logger/json_stream.go b/eth/tracers/logger/json_stream.go index 0376972750b..0408e8fdcda 100644 --- a/eth/tracers/logger/json_stream.go +++ b/eth/tracers/logger/json_stream.go @@ -4,18 +4,17 @@ import ( "context" "encoding/hex" "encoding/json" - "math/big" "sort" "github.com/holiman/uint256" jsoniter "github.com/json-iterator/go" - "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" + "github.com/ledgerwatch/erigon/eth/tracers" ) // JsonStreamLogger is an EVM state logger and implements Tracer. @@ -35,7 +34,7 @@ type JsonStreamLogger struct { logs []StructLog output []byte //nolint err error //nolint - env *vm.EVM + env *tracing.VMContext } // NewStructLogger returns a new logger @@ -52,25 +51,33 @@ func NewJsonStreamLogger(cfg *LogConfig, ctx context.Context, stream *jsoniter.S return logger } -func (l *JsonStreamLogger) CaptureTxStart(env *vm.EVM, tx types.Transaction) { - l.env = env +func (l *JsonStreamLogger) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: l.OnTxStart, + OnOpcode: l.OnOpcode, + }, + } } -func (l *JsonStreamLogger) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (l *JsonStreamLogger) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (l *JsonStreamLogger) Hooks() *tracing.Hooks { + return &tracing.Hooks{ + OnTxStart: l.OnTxStart, + OnOpcode: l.OnOpcode, + } } -func (l *JsonStreamLogger) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (l *JsonStreamLogger) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { + l.env = env } -// CaptureState logs a new structured log message and pushes it out to the environment +// OnOpcode logs a new structured log message and pushes it out to the environment // -// CaptureState also tracks SLOAD/SSTORE ops to track storage change. -func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - contract := scope.Contract - memory := scope.Memory - stack := scope.Stack +// OnOpcode also tracks SLOAD/SSTORE ops to track storage change. +func (l *JsonStreamLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + contractAddr := scope.Address() + memory := scope.MemoryData() + stack := scope.StackData() select { case <-l.ctx.Done(): @@ -90,26 +97,26 @@ func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6 if !l.cfg.DisableStorage { // initialise new changed values storage container for this contract // if not present. - if l.storage[contract.Address()] == nil { - l.storage[contract.Address()] = make(Storage) + if l.storage[contractAddr] == nil { + l.storage[contractAddr] = make(Storage) } // capture SLOAD opcodes and record the read entry in the local storage - if op == vm.SLOAD && stack.Len() >= 1 { + if vm.OpCode(op) == vm.SLOAD && len(stack) >= 1 { var ( - address = libcommon.Hash(stack.Data[stack.Len()-1].Bytes32()) + address = libcommon.Hash(stack[len(stack)-1].Bytes32()) value uint256.Int ) - l.env.IntraBlockState().GetState(contract.Address(), &address, &value) - l.storage[contract.Address()][address] = value.Bytes32() + l.env.IntraBlockState.GetState(contractAddr, &address, &value) + l.storage[contractAddr][address] = value.Bytes32() outputStorage = true } // capture SSTORE opcodes and record the written entry in the local storage. - if op == vm.SSTORE && stack.Len() >= 2 { + if vm.OpCode(op) == vm.SSTORE && len(stack) >= 2 { var ( - value = libcommon.Hash(stack.Data[stack.Len()-2].Bytes32()) - address = libcommon.Hash(stack.Data[stack.Len()-1].Bytes32()) + value = libcommon.Hash(stack[len(stack)-2].Bytes32()) + address = libcommon.Hash(stack[len(stack)-1].Bytes32()) ) - l.storage[contract.Address()][address] = value + l.storage[contractAddr][address] = value outputStorage = true } } @@ -119,7 +126,7 @@ func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6 l.stream.WriteUint64(pc) l.stream.WriteMore() l.stream.WriteObjectField("op") - l.stream.WriteString(op.String()) + l.stream.WriteString(vm.OpCode(op).String()) l.stream.WriteMore() l.stream.WriteObjectField("gas") l.stream.WriteUint64(gas) @@ -140,7 +147,7 @@ func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6 l.stream.WriteMore() l.stream.WriteObjectField("stack") l.stream.WriteArrayStart() - for i, stackValue := range stack.Data { + for i, stackValue := range stack { if i > 0 { l.stream.WriteMore() } @@ -149,7 +156,7 @@ func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6 l.stream.WriteArrayEnd() } if !l.cfg.DisableMemory { - memData := memory.Data() + memData := memory l.stream.WriteMore() l.stream.WriteObjectField("memory") l.stream.WriteArrayStart() @@ -170,7 +177,7 @@ func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6 if l.locations != nil { l.locations = l.locations[:0] } - s := l.storage[contract.Address()] + s := l.storage[contractAddr] for loc := range s { l.locations = append(l.locations, loc) } @@ -191,47 +198,6 @@ func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6 _ = l.stream.Flush() } -// CaptureFault implements the Tracer interface to trace an execution fault -// while running an opcode. -func (l *JsonStreamLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { -} - -func (l *JsonStreamLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { -} - -func (l *JsonStreamLogger) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { -} - -func (l *JsonStreamLogger) OnBlockEnd(err error) { -} - -func (l *JsonStreamLogger) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { -} - -func (l *JsonStreamLogger) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (l *JsonStreamLogger) OnBeaconBlockRootEnd() {} - -func (l *JsonStreamLogger) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (l *JsonStreamLogger) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (l *JsonStreamLogger) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (l *JsonStreamLogger) OnNonceChange(a libcommon.Address, prev, new uint64) {} - -func (l *JsonStreamLogger) OnCodeChange(a libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (l *JsonStreamLogger) OnStorageChange(a libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { -} - -func (l *JsonStreamLogger) OnLog(log *types.Log) {} - -func (l *JsonStreamLogger) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { -} - // GetResult returns an empty json object. func (l *JsonStreamLogger) GetResult() (json.RawMessage, error) { return json.RawMessage(`{}`), nil diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 6fbfcbe2570..2bed66b6581 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -17,9 +17,10 @@ import ( "github.com/ledgerwatch/erigon-lib/common/hexutility" "github.com/ledgerwatch/erigon/common/math" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" + "github.com/ledgerwatch/erigon/eth/tracers" ) var ErrTraceLimitReached = errors.New("the number of logs reached the specified limit") @@ -117,7 +118,9 @@ type StructLogger struct { logs []StructLog output []byte err error - env *vm.EVM + env *tracing.VMContext + + usedGas uint64 } // NewStructLogger returns a new logger @@ -131,26 +134,30 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { return logger } -func (l *StructLogger) CaptureTxStart(env *vm.EVM, tx types.Transaction) { - l.env = env -} - -func (l *StructLogger) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (l *StructLogger) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (l *StructLogger) Hooks() *tracing.Hooks { + return &tracing.Hooks{ + OnTxStart: l.OnTxStart, + OnTxEnd: l.OnTxEnd, + OnExit: l.OnExit, + OnOpcode: l.OnOpcode, + } } -// CaptureEnter implements the Tracer interface to initialize the tracing operation for an internal call. -func (l *StructLogger) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (l *StructLogger) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: l.Hooks(), + GetResult: l.GetResult, + Stop: l.Stop, + } } -// CaptureState logs a new structured log message and pushes it out to the environment +// OnOpcode logs a new structured log message and pushes it out to the environment // -// CaptureState also tracks SLOAD/SSTORE ops to track storage change. -func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - memory := scope.Memory - stack := scope.Stack - contract := scope.Contract +// OnOpcode also tracks SLOAD/SSTORE ops to track storage change. +func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + op := vm.OpCode(opcode) + memory := scope.MemoryData() + stack := scope.StackData() // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { @@ -160,43 +167,46 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s // Copy a snapshot of the current memory state to a new buffer var mem []byte if !l.cfg.DisableMemory { - mem = make([]byte, len(memory.Data())) - copy(mem, memory.Data()) + mem = make([]byte, len(memory)) + copy(mem, memory) } // Copy a snapshot of the current stack state to a new buffer var stck []*big.Int if !l.cfg.DisableStack { - stck = make([]*big.Int, len(stack.Data)) - for i, item := range stack.Data { + stck = make([]*big.Int, len(stack)) + for i, item := range stack { stck[i] = new(big.Int).Set(item.ToBig()) } } + + contractAddr := scope.Address() + stackLen := len(stack) // Copy a snapshot of the current storage to a new container var storage Storage if !l.cfg.DisableStorage { // initialise new changed values storage container for this contract // if not present. - if l.storage[contract.Address()] == nil { - l.storage[contract.Address()] = make(Storage) + if l.storage[contractAddr] == nil { + l.storage[contractAddr] = make(Storage) } // capture SLOAD opcodes and record the read entry in the local storage - if op == vm.SLOAD && stack.Len() >= 1 { + if op == vm.SLOAD && stackLen >= 1 { var ( - address = libcommon.Hash(stack.Data[stack.Len()-1].Bytes32()) + address = libcommon.Hash(stack[stackLen-1].Bytes32()) value uint256.Int ) - l.env.IntraBlockState().GetState(contract.Address(), &address, &value) - l.storage[contract.Address()][address] = value.Bytes32() + l.env.IntraBlockState.GetState(contractAddr, &address, &value) + l.storage[contractAddr][address] = value.Bytes32() } // capture SSTORE opcodes and record the written entry in the local storage. - if op == vm.SSTORE && stack.Len() >= 2 { + if op == vm.SSTORE && stackLen >= 2 { var ( - value = libcommon.Hash(stack.Data[stack.Len()-2].Bytes32()) - address = libcommon.Hash(stack.Data[stack.Len()-1].Bytes32()) + value = libcommon.Hash(stack[stackLen-2].Bytes32()) + address = libcommon.Hash(stack[stackLen-1].Bytes32()) ) - l.storage[contract.Address()][address] = value + l.storage[contractAddr][address] = value } - storage = l.storage[contract.Address()].Copy() + storage = l.storage[contractAddr].Copy() } var rdata []byte if !l.cfg.DisableReturnData { @@ -204,17 +214,16 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s copy(rdata, rData) } // create a new snapshot of the EVM. - log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.IntraBlockState().GetRefund(), err} + log := StructLog{pc, op, gas, cost, mem, len(memory), stck, rdata, storage, depth, l.env.IntraBlockState.GetRefund(), err} l.logs = append(l.logs, log) } -// CaptureFault implements the Tracer 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) { -} +// OnExit is called after the call finishes to finalize the tracing. +func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth != 0 { + return + } -// CaptureEnd is called after the call finishes to finalize the tracing. -func (l *StructLogger) CaptureEnd(output []byte, usedGas uint64, err error, reverted bool) { l.output = output l.err = err if l.cfg.Debug { @@ -225,38 +234,19 @@ func (l *StructLogger) CaptureEnd(output []byte, usedGas uint64, err error, reve } } -func (l *StructLogger) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { -} - -func (l *StructLogger) OnBlockEnd(err error) { -} - -func (l *StructLogger) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { -} - -func (l *StructLogger) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (l *StructLogger) OnBeaconBlockRootEnd() {} - -func (l *StructLogger) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (l *StructLogger) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (l *StructLogger) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (l *StructLogger) OnNonceChange(a libcommon.Address, prev, new uint64) {} - -func (l *StructLogger) OnCodeChange(a libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (l *StructLogger) OnStorageChange(a libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { +func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { + l.env = env } -func (l *StructLogger) OnLog(log *types.Log) {} - -// CaptureExit is called after the internal call finishes to finalize the tracing. -func (l *StructLogger) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { +func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) { + if err != nil { + // Don't override vm error + if l.err == nil { + l.err = err + } + return + } + l.usedGas = receipt.GasUsed } // GetResult returns an empty json object. @@ -379,7 +369,7 @@ func WriteLogs(writer io.Writer, logs []*types.Log) { type mdLogger struct { out io.Writer cfg *LogConfig - env *vm.EVM + env *tracing.VMContext } // NewMarkdownLogger creates a logger which outputs information in a format adapted @@ -392,11 +382,19 @@ func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger { return l } -func (t *mdLogger) CaptureTxStart(env *vm.EVM, tx types.Transaction) { - t.env = env +func (t *mdLogger) Hooks() *tracing.Hooks { + return &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + } } -func (t *mdLogger) CaptureTxEnd(receipt *types.Receipt, err error) {} +func (t *mdLogger) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { + t.env = env +} func (t *mdLogger) captureStartOrEnter(from, to libcommon.Address, create bool, input []byte, gas uint64, value *uint256.Int) { if !create { @@ -415,82 +413,46 @@ func (t *mdLogger) captureStartOrEnter(from, to libcommon.Address, create bool, `) } -func (t *mdLogger) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { //nolint:interfacer +func (t *mdLogger) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + if depth != 0 { + return + } + create := vm.OpCode(typ) == vm.CREATE t.captureStartOrEnter(from, to, create, input, gas, value) } -func (t *mdLogger) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { //nolint:interfacer - t.captureStartOrEnter(from, to, create, input, gas, value) +func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth == 0 { + fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n", + output, gasUsed, err) + } } -func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - stack := scope.Stack - +// OnOpcode also tracks SLOAD/SSTORE ops to track storage change. +func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + stack := scope.StackData() fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) if !t.cfg.DisableStack { // format stack var a []string - for _, elem := range stack.Data { + for _, elem := range stack { a = append(a, elem.String()) } b := fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) } - fmt.Fprintf(t.out, "%10v |", t.env.IntraBlockState().GetRefund()) + fmt.Fprintf(t.out, "%10v |", t.env.IntraBlockState.GetRefund()) fmt.Fprintln(t.out, "") if err != nil { fmt.Fprintf(t.out, "Error: %v\n", err) } } -func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) } -func (t *mdLogger) captureEndOrExit(output []byte, usedGas uint64, err error) { - fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", - output, usedGas, err) -} - -func (t *mdLogger) CaptureEnd(output []byte, usedGas uint64, err error, reverted bool) { - t.captureEndOrExit(output, usedGas, err) -} - -func (t *mdLogger) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { -} - -func (t *mdLogger) OnBlockEnd(err error) { -} - -func (t *mdLogger) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { -} - -func (t *mdLogger) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (t *mdLogger) OnBeaconBlockRootEnd() {} - -func (t *mdLogger) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (t *mdLogger) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (t *mdLogger) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (t *mdLogger) OnNonceChange(a libcommon.Address, prev, new uint64) {} - -func (t *mdLogger) OnCodeChange(a libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (t *mdLogger) OnStorageChange(a libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { -} - -func (t *mdLogger) OnLog(log *types.Log) {} - -func (t *mdLogger) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { - t.captureEndOrExit(output, usedGas, err) -} - // GetResult returns an empty json object. func (t *mdLogger) GetResult() (json.RawMessage, error) { return json.RawMessage(`{}`), nil diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 7065172b126..5d6d36e19e5 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -21,68 +21,66 @@ import ( "io" "math/big" - "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/math" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" + "github.com/ledgerwatch/erigon/eth/tracers" ) type JSONLogger struct { encoder *json.Encoder cfg *LogConfig - env *vm.EVM + env *tracing.VMContext } // NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects // into the provided stream. -func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger { +func NewJSONLogger(cfg *LogConfig, writer io.Writer) *tracers.Tracer { l := &JSONLogger{json.NewEncoder(writer), cfg, nil} if l.cfg == nil { l.cfg = &LogConfig{} } - return l + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: l.OnTxStart, + OnExit: l.OnExit, + OnOpcode: l.OnOpcode, + OnFault: l.OnFault, + }, + } } -func (l *JSONLogger) CaptureTxStart(env *vm.EVM, tx types.Transaction) { +func (l *JSONLogger) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { l.env = env } -func (l *JSONLogger) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (l *JSONLogger) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { -} - -func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { -} - -// CaptureState outputs state information on the logger. -func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - memory := scope.Memory - stack := scope.Stack +// OnOpcode outputs state information on the logger. +func (l *JSONLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + memory := scope.MemoryData() + stack := scope.StackData() log := StructLog{ Pc: pc, - Op: op, + Op: vm.OpCode(op), Gas: gas, GasCost: cost, - MemorySize: memory.Len(), + MemorySize: len(memory), Storage: nil, Depth: depth, - RefundCounter: l.env.IntraBlockState().GetRefund(), + RefundCounter: l.env.IntraBlockState.GetRefund(), Err: err, } if !l.cfg.DisableMemory { - log.Memory = memory.Data() + log.Memory = memory } if !l.cfg.DisableStack { //TODO(@holiman) improve this - logstack := make([]*big.Int, len(stack.Data)) - for i, item := range stack.Data { + logstack := make([]*big.Int, len(stack)) + for i, item := range stack { logstack[i] = item.ToBig() } log.Stack = logstack @@ -91,11 +89,15 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco } // CaptureFault outputs state information on the logger. -func (l *JSONLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +func (l *JSONLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, depth int, err error) { + l.OnOpcode(pc, op, gas, cost, scope, nil, depth, err) } // CaptureEnd is triggered at end of execution. -func (l *JSONLogger) CaptureEnd(output []byte, usedGas uint64, err error, reverted bool) { +func (l *JSONLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth > 0 { + return + } type endLog struct { Output string `json:"output"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` @@ -105,40 +107,7 @@ func (l *JSONLogger) CaptureEnd(output []byte, usedGas uint64, err error, revert if err != nil { errMsg = err.Error() } - _ = l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(usedGas), errMsg}) -} - -func (l *JSONLogger) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { -} - -func (l *JSONLogger) OnBlockEnd(err error) { -} - -func (l *JSONLogger) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { -} - -func (l *JSONLogger) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (l *JSONLogger) OnBeaconBlockRootEnd() {} - -func (l *JSONLogger) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (l *JSONLogger) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (l *JSONLogger) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (l *JSONLogger) OnNonceChange(a libcommon.Address, prev, new uint64) {} - -func (l *JSONLogger) OnCodeChange(a libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (l *JSONLogger) OnStorageChange(a libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { -} - -func (l *JSONLogger) OnLog(log *types.Log) {} - -func (l *JSONLogger) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { + _ = l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg}) } // GetResult returns an empty json object. diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index c382cfdb8b3..77a32ac5876 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -56,8 +56,8 @@ func (*dummyStatedb) GetRefund() uint64 { return 1337 } func TestStoreCapture(t *testing.T) { var ( - env = vm.NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{}) logger = NewStructLogger(nil) + env = vm.NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()}) mem = vm.NewMemory() stack = stack.New() contract = vm.NewContract(&dummyContractRef{}, libcommon.Address{}, new(uint256.Int), 0, false /* skipAnalysis */) @@ -65,9 +65,8 @@ func TestStoreCapture(t *testing.T) { stack.Push(uint256.NewInt(1)) stack.Push(uint256.NewInt(0)) var index libcommon.Hash - logger.CaptureTxStart(env, nil) - logger.CaptureStart(libcommon.Address{}, libcommon.Address{}, false, false, nil, 0, nil, nil) - logger.CaptureState(0, vm.SSTORE, 0, 0, &vm.ScopeContext{ + logger.OnTxStart(env.GetVMContext(), nil, libcommon.Address{}) + logger.OnOpcode(0, byte(vm.SSTORE), 0, 0, &vm.ScopeContext{ Memory: mem, Stack: stack, Contract: contract, diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 157989c5d3c..54c5240f1bd 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -25,6 +25,7 @@ import ( libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/eth/tracers" @@ -53,16 +54,23 @@ type fourByteTracer struct { ids map[string]int // ids aggregates the 4byte ids found interrupt uint32 // Atomic flag to signal execution interruption reason error // Textual reason for the interruption - activePrecompiles []libcommon.Address // Updated on CaptureStart based on given rules + activePrecompiles []libcommon.Address // Updated on tx start based on given rules } // 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 *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) { t := &fourByteTracer{ ids: make(map[string]int), } - return t, nil + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnEnter: t.OnEnter, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil } // isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go @@ -81,22 +89,13 @@ func (t *fourByteTracer) store(id []byte, size int) { t.ids[key] += 1 } -func (t *fourByteTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { - rules := env.ChainRules() +func (t *fourByteTracer) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { + rules := env.ChainConfig.Rules(env.BlockNumber, env.Time) t.activePrecompiles = vm.ActivePrecompiles(rules) } -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *fourByteTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - // Save the outer calldata also - if len(input) >= 4 { - t.store(input[0:4], len(input)-4) - } -} - // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from libcommon.Address, to libcommon.Address, precompile, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - // Skip if tracing was interrupted +func (t *fourByteTracer) OnEnter(depth int, opcode byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { // Skip if tracing was interrupted if atomic.LoadUint32(&t.interrupt) > 0 { return } @@ -104,6 +103,7 @@ func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from libcommon.Address, to l return } // primarily we want to avoid CREATE/CREATE2/SELFDESTRUCT + op := vm.OpCode(opcode) if op != vm.DELEGATECALL && op != vm.STATICCALL && op != vm.CALL && op != vm.CALLCODE { return diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 6d7318e3e45..bf0dc0d5821 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -23,12 +23,12 @@ import ( "sync/atomic" "github.com/holiman/uint256" - libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutil" "github.com/ledgerwatch/erigon-lib/common/hexutility" "github.com/ledgerwatch/erigon/accounts/abi" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/eth/tracers" @@ -124,7 +124,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 *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { var config callTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { @@ -133,39 +133,24 @@ func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, e } // First callframe contains tx context info // and is populated on start and end. - return &callTracer{callstack: make([]callFrame, 1), config: config}, nil -} - -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *callTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - t.callstack[0] = callFrame{ - Type: vm.CALL, - From: from, - To: to, - Input: libcommon.CopyBytes(input), - Gas: t.gasLimit, // gas has intrinsicGas already subtracted - } - if value != nil { - t.callstack[0].Value = value.ToBig() - } - if create { - t.callstack[0].Type = vm.CREATE - } -} - -// CaptureEnd is called after the call finishes to finalize the tracing. -func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { - t.callstack[0].processOutput(output, err, reverted) -} - -// CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + t := &callTracer{callstack: make([]callFrame, 1), config: config} + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *callTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - t.depth++ - if t.config.OnlyTopCall { +func (t *callTracer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + t.depth = depth + if t.config.OnlyTopCall && depth > 0 { return } // Skip if tracing was interrupted @@ -174,7 +159,7 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libc } toCopy := to call := callFrame{ - Type: typ, + Type: vm.OpCode(typ), From: from, To: toCopy, Input: libcommon.CopyBytes(input), @@ -186,11 +171,19 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libc // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { - t.depth-- + +func (t *callTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth == 0 { + t.captureEnd(output, gasUsed, err, reverted) + return + } + + t.depth = depth - 1 + if t.config.OnlyTopCall { return } + size := len(t.callstack) if size <= 1 { return @@ -205,11 +198,18 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error, rever t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) } -func (t *callTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { +func (t *callTracer) captureEnd(output []byte, gasUsed uint64, err error, reverted bool) { + if len(t.callstack) != 1 { + return + } + t.callstack[0].processOutput(output, err, reverted) +} + +func (t *callTracer) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { t.gasLimit = tx.GetGas() } -func (t *callTracer) CaptureTxEnd(receipt *types.Receipt, err error) { +func (t *callTracer) OnTxEnd(receipt *types.Receipt, err error) { // Error happened during tx validation. if err != nil { return diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index 50a27d7f531..84ad42bdfe3 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -18,15 +18,12 @@ package native import ( "encoding/json" - "math/big" "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/eth/tracers" ) @@ -38,18 +35,18 @@ func init() { // runs multiple tracers in one go. type muxTracer struct { names []string - tracers []tracers.Tracer + tracers []*tracers.Tracer } // newMuxTracer returns a new mux tracer. -func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { +func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.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([]*tracers.Tracer, 0, len(config)) names := make([]string, 0, len(config)) for k, v := range config { t, err := tracers.New(k, ctx, v) @@ -60,81 +57,80 @@ func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, er names = append(names, k) } - return &muxTracer{names: names, tracers: objects}, nil -} - -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *muxTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - for _, t := range t.tracers { - t.CaptureStart(from, to, precompile, create, input, gas, value, code) - } -} - -// CaptureEnd is called after the call finishes to finalize the tracing. -func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { - for _, t := range t.tracers { - t.CaptureEnd(output, gasUsed, err, reverted) - } -} - -// CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *muxTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - for _, t := range t.tracers { - t.CaptureState(pc, op, gas, cost, scope, rData, depth, err) - } -} - -// CaptureFault implements the EVMLogger interface to trace an execution fault. -func (t *muxTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { - for _, t := range t.tracers { - t.CaptureFault(pc, op, gas, cost, scope, depth, err) - } -} - -// CaptureKeccakPreimage is called during the KECCAK256 opcode. -func (t *muxTracer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) { - for _, t := range t.tracers { - t.CaptureKeccakPreimage(hash, data) + t := &muxTracer{names: names, tracers: objects} + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + OnGasChange: t.OnGasChange, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil +} + +func (t *muxTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + for _, t := range t.tracers { + if t.OnOpcode != nil { + t.OnOpcode(pc, op, gas, cost, scope, rData, depth, err) + } } } -// CaptureGasConsumed is called when gas is consumed. -func (t *muxTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) { +func (t *muxTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { for _, t := range t.tracers { - t.OnGasChange(old, new, reason) + if t.OnFault != nil { + t.OnFault(pc, op, gas, cost, scope, depth, err) + } } } -// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *muxTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (t *muxTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { for _, t := range t.tracers { - t.CaptureEnter(typ, from, to, precompile, create, input, gas, value, code) + if t.OnGasChange != nil { + t.OnGasChange(old, new, reason) + } } } -// CaptureExit is called when EVM exits a scope, even if the scope didn't -// execute any code. -func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { +func (t *muxTracer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { for _, t := range t.tracers { - t.CaptureExit(output, gasUsed, err, reverted) + if t.OnEnter != nil { + t.OnEnter(depth, typ, from, to, precompile, input, gas, value, code) + } } } -func (t *muxTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { +func (t *muxTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { for _, t := range t.tracers { - t.CaptureTxStart(env, tx) + if t.OnExit != nil { + t.OnExit(depth, output, gasUsed, err, reverted) + } } } -func (t *muxTracer) CaptureTxEnd(receipt *types.Receipt, err error) { +func (t *muxTracer) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { for _, t := range t.tracers { - t.CaptureTxEnd(receipt, err) + if t.OnTxStart != nil { + t.OnTxStart(env, tx, from) + } } } -func (t *muxTracer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { +func (t *muxTracer) OnTxEnd(receipt *types.Receipt, err error) { for _, t := range t.tracers { - t.OnBlockStart(b, td, finalized, safe, chainConfig) + if t.OnTxEnd != nil { + t.OnTxEnd(receipt, err) + } } } @@ -150,39 +146,35 @@ func (t *muxTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { } } -func (t *muxTracer) OnBeaconBlockRootStart(root libcommon.Hash) { - for _, t := range t.tracers { - t.OnBeaconBlockRootStart(root) - } -} - -func (t *muxTracer) OnBeaconBlockRootEnd() { +func (t *muxTracer) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason tracing.BalanceChangeReason) { for _, t := range t.tracers { - t.OnBeaconBlockRootEnd() - } -} - -func (t *muxTracer) OnBalanceChange(addr libcommon.Address, prev *uint256.Int, new *uint256.Int, reason evmtypes.BalanceChangeReason) { - for _, t := range t.tracers { - t.OnBalanceChange(addr, prev, new, reason) + if t.OnBalanceChange != nil { + t.OnBalanceChange(a, prev, new, reason) + } } } func (t *muxTracer) OnNonceChange(a libcommon.Address, prev, new uint64) { for _, t := range t.tracers { - t.OnNonceChange(a, prev, new) + if t.OnNonceChange != nil { + t.OnNonceChange(a, prev, new) + } } } func (t *muxTracer) OnCodeChange(a libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { for _, t := range t.tracers { - t.OnCodeChange(a, prevCodeHash, prev, codeHash, code) + if t.OnCodeChange != nil { + t.OnCodeChange(a, prevCodeHash, prev, codeHash, code) + } } } func (t *muxTracer) OnStorageChange(addr libcommon.Address, slot *libcommon.Hash, prev uint256.Int, new uint256.Int) { for _, t := range t.tracers { - t.OnStorageChange(addr, slot, prev, new) + if t.OnStorageChange != nil { + t.OnStorageChange(addr, slot, prev, new) + } } } @@ -205,7 +197,9 @@ func (t *muxTracer) GetResult() (json.RawMessage, error) { func (t *muxTracer) OnLog(log *types.Log) { for _, t := range t.tracers { - t.OnLog(log) + if t.OnLog != nil { + t.OnLog(log) + } } } diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 8b980b0bf28..f109fc4182b 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -30,6 +30,7 @@ import ( libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutility" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/crypto" @@ -61,8 +62,7 @@ type accountMarshaling struct { } type prestateTracer struct { - tracers.NoopTracer - env *vm.EVM + env *tracing.VMContext pre state post state create bool @@ -79,47 +79,37 @@ 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 *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { var config prestateTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { return nil, err } } - return &prestateTracer{ + t := &prestateTracer{ pre: state{}, post: state{}, config: config, created: make(map[libcommon.Address]bool), deleted: make(map[libcommon.Address]bool), - }, nil -} - -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *prestateTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { -} - -// CaptureEnd is called after the call finishes to finalize the tracing. -func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { - if t.config.DiffMode { - return - } - - if t.create { - // Keep existing account prior to contract creation at that address - if s := t.pre[t.to]; s != nil && !s.exists() { - // Exclude newly created contract. - delete(t.pre, t.to) - } } + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnOpcode: t.OnOpcode, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil } -// CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - stack := scope.Stack - stackData := stack.Data +// OnOpcode implements the EVMLogger interface to trace a single step of VM execution. +func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + op := vm.OpCode(opcode) + stackData := scope.StackData() stackLen := len(stackData) - caller := scope.Contract.Address() + caller := scope.Address() switch { case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): slot := libcommon.Hash(stackData[stackLen-1].Bytes32()) @@ -134,14 +124,18 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, addr := libcommon.Address(stackData[stackLen-2].Bytes20()) t.lookupAccount(addr) case op == vm.CREATE: - nonce := t.env.IntraBlockState().GetNonce(caller) + nonce := t.env.IntraBlockState.GetNonce(caller) addr := crypto.CreateAddress(caller, nonce) t.lookupAccount(addr) t.created[addr] = true case stackLen >= 4 && op == vm.CREATE2: offset := stackData[stackLen-2] size := stackData[stackLen-3] - init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + init, err := tracers.GetMemoryCopyPadded(scope.MemoryData(), int64(offset.Uint64()), int64(size.Uint64())) + if err != nil { + t.Stop(fmt.Errorf("failed to copy CREATE2 in prestate tracer input err: %s", err)) + return + } inithash := crypto.Keccak256(init) salt := stackData[stackLen-4] addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) @@ -150,10 +144,10 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, } } -func (t *prestateTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { +func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { t.env = env - signer := types.MakeSigner(env.ChainConfig(), env.Context.BlockNumber, env.Context.Time) + signer := types.MakeSigner(env.ChainConfig, env.BlockNumber, env.Time) from, err := tx.Sender(*signer) if err != nil { t.Stop(fmt.Errorf("could not recover sender address: %v", err)) @@ -161,7 +155,7 @@ func (t *prestateTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { } if tx.GetTo() == nil { t.create = true - t.to = crypto.CreateAddress(from, env.IntraBlockState().GetNonce(from)) + t.to = crypto.CreateAddress(from, env.IntraBlockState.GetNonce(from)) } else { t.to = *tx.GetTo() t.create = false @@ -169,14 +163,14 @@ func (t *prestateTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) { t.lookupAccount(from) t.lookupAccount(t.to) - t.lookupAccount(env.Context.Coinbase) + t.lookupAccount(env.Coinbase) if t.create && t.config.DiffMode { t.created[t.to] = true } } -func (t *prestateTracer) CaptureTxEnd(receipt *types.Receipt, err error) { +func (t *prestateTracer) OnTxEnd(receipt *types.Receipt, err error) { if !t.config.DiffMode { return } @@ -192,9 +186,9 @@ func (t *prestateTracer) CaptureTxEnd(receipt *types.Receipt, err error) { } modified := false postAccount := &account{Storage: make(map[libcommon.Hash]libcommon.Hash)} - newBalance := t.env.IntraBlockState().GetBalance(addr).ToBig() - newNonce := t.env.IntraBlockState().GetNonce(addr) - newCode := t.env.IntraBlockState().GetCode(addr) + newBalance := t.env.IntraBlockState.GetBalance(addr).ToBig() + newNonce := t.env.IntraBlockState.GetNonce(addr) + newCode := t.env.IntraBlockState.GetCode(addr) if newBalance.Cmp(t.pre[addr].Balance) != 0 { modified = true @@ -216,7 +210,7 @@ func (t *prestateTracer) CaptureTxEnd(receipt *types.Receipt, err error) { } var newVal uint256.Int - t.env.IntraBlockState().GetState(addr, &key, &newVal) + t.env.IntraBlockState.GetState(addr, &key, &newVal) if new(uint256.Int).SetBytes(val[:]).Eq(&newVal) { // Omit unchanged slots delete(t.pre[addr].Storage, key) @@ -277,9 +271,9 @@ func (t *prestateTracer) lookupAccount(addr libcommon.Address) { } t.pre[addr] = &account{ - Balance: t.env.IntraBlockState().GetBalance(addr).ToBig(), - Nonce: t.env.IntraBlockState().GetNonce(addr), - Code: t.env.IntraBlockState().GetCode(addr), + Balance: t.env.IntraBlockState.GetBalance(addr).ToBig(), + Nonce: t.env.IntraBlockState.GetNonce(addr), + Code: t.env.IntraBlockState.GetCode(addr), Storage: make(map[libcommon.Hash]libcommon.Hash), } } @@ -292,6 +286,6 @@ func (t *prestateTracer) lookupStorage(addr libcommon.Address, key libcommon.Has return } var val uint256.Int - t.env.IntraBlockState().GetState(addr, &key, &val) + t.env.IntraBlockState.GetState(addr, &key, &val) t.pre[addr].Storage[key] = val.Bytes32() } diff --git a/eth/tracers/native/tracer.go b/eth/tracers/native/tracer.go index bdcc31394ec..29bf07eebf4 100644 --- a/eth/tracers/native/tracer.go +++ b/eth/tracers/native/tracer.go @@ -43,7 +43,7 @@ func init() { } // ctorFn is the constructor signature of a native tracer. -type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error) +type ctorFn = func(*tracers.Context, json.RawMessage) (*tracers.Tracer, error) /* ctors is a map of package-local tracer constructors. @@ -68,7 +68,7 @@ func register(name string, ctor ctorFn) { } // lookup returns a tracer, if one can be matched to the given name. -func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { +func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { if ctors == nil { ctors = make(map[string]ctorFn) } diff --git a/eth/tracers/noop.go b/eth/tracers/noop.go index 68d29737bc9..84fa3ba81cc 100644 --- a/eth/tracers/noop.go +++ b/eth/tracers/noop.go @@ -18,15 +18,13 @@ package tracers import ( "encoding/json" - "math/big" "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/chain" + "github.com/ledgerwatch/erigon-lib/common" libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" ) func init() { @@ -38,59 +36,48 @@ func init() { type NoopTracer struct{} // newNoopTracer returns a new noop tracer. -func newNoopTracer(ctx *Context, _ json.RawMessage) (Tracer, error) { - return &NoopTracer{}, nil +func newNoopTracer(ctx *Context, _ json.RawMessage) (*Tracer, error) { + t := &NoopTracer{} + return &Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + OnGasChange: t.OnGasChange, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil } -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *NoopTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (t *NoopTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { } -// CaptureEnd is called after the call finishes to finalize the tracing. -func (t *NoopTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { +func (t *NoopTracer) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) { } -// CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *NoopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { -} - -// CaptureFault implements the EVMLogger interface to trace an execution fault. -func (t *NoopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { -} - -// CaptureKeccakPreimage is called during the KECCAK256 opcode. -func (t *NoopTracer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -// OnGasChange is called when gas is either consumed or refunded. -func (t *NoopTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *NoopTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { -} - -// CaptureExit is called when EVM exits a scope, even if the scope didn't -// execute any code. -func (t *NoopTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { -} +func (t *NoopTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {} -func (*NoopTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) {} - -func (*NoopTracer) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (*NoopTracer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { +func (t *NoopTracer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { } -func (*NoopTracer) OnBlockEnd(err error) { +func (t *NoopTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { } -func (*NoopTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { +func (*NoopTracer) OnTxStart(env *tracing.VMContext, tx types.Transaction, from common.Address) { } -func (*NoopTracer) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (*NoopTracer) OnBeaconBlockRootEnd() {} +func (*NoopTracer) OnTxEnd(receipt *types.Receipt, err error) {} -func (*NoopTracer) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { +func (*NoopTracer) OnBalanceChange(a common.Address, prev, new *uint256.Int, reason tracing.BalanceChangeReason) { } func (*NoopTracer) OnNonceChange(a libcommon.Address, prev, new uint64) {} diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 89a39636979..53d885e2f4e 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -21,8 +21,9 @@ import ( "encoding/json" "errors" + "github.com/ledgerwatch/erigon/core/tracing" + libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/core" ) // Context contains some contextual infos for a transaction execution that is not @@ -35,14 +36,14 @@ type Context struct { // Tracer interface extends vm.EVMLogger and additionally // allows collecting the tracing result. -type Tracer interface { - core.BlockchainLogger - GetResult() (json.RawMessage, error) +type Tracer struct { + *tracing.Hooks + GetResult func() (json.RawMessage, error) // Stop terminates execution of the tracer at the first opportune moment. - Stop(err error) + Stop func(err error) } -type lookupFunc func(string, *Context, json.RawMessage) (Tracer, error) +type lookupFunc func(string, *Context, json.RawMessage) (*Tracer, error) var ( lookups []lookupFunc @@ -62,7 +63,7 @@ func RegisterLookup(wildcard bool, lookup lookupFunc) { // New returns a new instance of a tracer, by iterating through the // registered lookups. -func New(code string, ctx *Context, cfg json.RawMessage) (Tracer, error) { +func New(code string, ctx *Context, cfg json.RawMessage) (*Tracer, error) { for _, lookup := range lookups { if tracer, err := lookup(code, ctx, cfg); err == nil { return tracer, nil diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index cc3e29146cf..6cdc704c936 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -20,10 +20,11 @@ import ( "crypto/ecdsa" "crypto/rand" "encoding/json" - "github.com/ledgerwatch/erigon-lib/common/hexutil" "math/big" "testing" + "github.com/ledgerwatch/erigon-lib/common/hexutil" + libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/types" @@ -109,20 +110,20 @@ func TestPrestateTracerCreate2(t *testing.T) { if err != nil { t.Fatalf("failed to prestate tracer: %v", err) } - evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) + evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer.Hooks}) msg, err := txn.AsMessage(*signer, nil, rules) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - tracer.CaptureTxStart(evm, txn) + tracer.OnTxStart(evm.GetVMContext(), txn, msg.From()) st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(txn.GetGas()).AddBlobGas(txn.GetBlobGas())) exeRes, err := st.TransitionDb(false, false) if err != nil { t.Fatalf("failed to execute transaction: %v", err) } - tracer.CaptureTxEnd(&types.Receipt{GasUsed: exeRes.UsedGas}, nil) + tracer.OnTxEnd(&types.Receipt{GasUsed: exeRes.UsedGas}, nil) // Retrieve the trace result and compare against the etalon res, err := tracer.GetResult() if err != nil { diff --git a/eth/tracers/util.go b/eth/tracers/util.go new file mode 100644 index 00000000000..b115df34925 --- /dev/null +++ b/eth/tracers/util.go @@ -0,0 +1,80 @@ +// 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 tracers + +import ( + "errors" + "fmt" + + "github.com/holiman/uint256" +) + +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 []byte, offset, size int64) ([]byte, error) { + if offset < 0 || size < 0 { + return nil, errors.New("offset or size must not be negative") + } + length := int64(len(m)) + if offset+size < length { // slice fully inside memory + return memoryCopy(m, offset, size), nil + } + paddingNeeded := offset + size - length + if paddingNeeded > memoryPadLimit { + return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded) + } + cpy := make([]byte, size) + if overlap := length - offset; overlap > 0 { + copy(cpy, MemoryPtr(m, offset, overlap)) + } + return cpy, nil +} + +func memoryCopy(m []byte, offset, size int64) (cpy []byte) { + if size == 0 { + return nil + } + + if len(m) > int(offset) { + cpy = make([]byte, size) + copy(cpy, m[offset:offset+size]) + + return + } + + return +} + +func MemoryPtr(m []byte, offset, size int64) []byte { + if size == 0 { + return nil + } + + if len(m) > int(offset) { + return m[offset : offset+size] + } + + return nil +} + +// Back returns the n'th item in stack +func StackBack(st []uint256.Int, n int) *uint256.Int { + return &st[len(st)-n-1] +} diff --git a/polygon/bor/bor.go b/polygon/bor/bor.go index 43e7be821a9..30e7660ff51 100644 --- a/polygon/bor/bor.go +++ b/polygon/bor/bor.go @@ -31,6 +31,7 @@ import ( "github.com/ledgerwatch/erigon/consensus/misc" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/crypto" @@ -1093,7 +1094,7 @@ func (c *Bor) GenerateSeal(chain consensus.ChainHeaderReader, currnt, parent *ty } func (c *Bor) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header, - state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, eLogger consensus.EngineLogger) { + state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, eLogger *tracing.Hooks) { } // Authorize injects a private key into the consensus engine to mint new blocks diff --git a/polygon/tracer/bor_state_sync_txn_tracer.go b/polygon/tracer/bor_state_sync_txn_tracer.go index a70ff5ae4ae..bdaf72c8632 100644 --- a/polygon/tracer/bor_state_sync_txn_tracer.go +++ b/polygon/tracer/bor_state_sync_txn_tracer.go @@ -2,29 +2,43 @@ package tracer import ( "encoding/json" - "math/big" "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/eth/tracers" ) func NewBorStateSyncTxnTracer( - tracer vm.EVMLogger, + tracer *tracers.Tracer, stateSyncEventsCount int, stateReceiverContractAddress libcommon.Address, -) tracers.Tracer { - return &borStateSyncTxnTracer{ - EVMLogger: tracer, +) *tracers.Tracer { + l := &borStateSyncTxnTracer{ + Tracer: tracer, stateSyncEventsCount: stateSyncEventsCount, stateReceiverContractAddress: stateReceiverContractAddress, } + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: l.OnTxStart, + OnTxEnd: l.OnTxEnd, + OnEnter: l.OnEnter, + OnExit: l.OnExit, + OnOpcode: l.OnOpcode, + OnFault: l.OnFault, + OnGasChange: l.OnGasChange, + OnBalanceChange: l.OnBalanceChange, + OnNonceChange: l.OnNonceChange, + OnCodeChange: l.OnCodeChange, + OnStorageChange: l.OnStorageChange, + OnLog: l.OnLog, + }, + GetResult: l.GetResult, + Stop: l.Stop, + } } // borStateSyncTxnTracer is a special tracer which is used only for tracing bor state sync transactions. Bor state sync @@ -37,44 +51,25 @@ func NewBorStateSyncTxnTracer( // state sync events at end of each sprint these are synthetically executed as if they were sub-calls of the // state sync events bor transaction. type borStateSyncTxnTracer struct { - vm.EVMLogger + Tracer *tracers.Tracer captureStartCalledOnce bool stateSyncEventsCount int stateReceiverContractAddress libcommon.Address } -func (bsstt *borStateSyncTxnTracer) CaptureTxStart(evm *vm.EVM, tx types.Transaction) { - bsstt.EVMLogger.CaptureTxStart(evm, tx) -} - -func (bsstt *borStateSyncTxnTracer) CaptureTxEnd(receipt *types.Receipt, err error) { - bsstt.EVMLogger.CaptureTxEnd(receipt, err) +func (bsstt *borStateSyncTxnTracer) OnTxStart(env *tracing.VMContext, tx types.Transaction, from libcommon.Address) { + if bsstt.Tracer.OnTxStart != nil { + bsstt.Tracer.OnTxStart(env, tx, from) + } } -func (bsstt *borStateSyncTxnTracer) CaptureStart( - from libcommon.Address, - to libcommon.Address, - precompile bool, - create bool, - input []byte, - gas uint64, - value *uint256.Int, - code []byte, -) { - if !bsstt.captureStartCalledOnce { - // first event execution started - // perform a CaptureStart for the synthetic state sync transaction - from := state.SystemAddress - to := bsstt.stateReceiverContractAddress - bsstt.EVMLogger.CaptureStart(from, to, false, false, nil, 0, uint256.NewInt(0), nil) - bsstt.captureStartCalledOnce = true +func (bsstt *borStateSyncTxnTracer) OnTxEnd(receipt *types.Receipt, err error) { + if bsstt.Tracer.OnTxEnd != nil { + bsstt.Tracer.OnTxEnd(receipt, err) } - - // trick the tracer to think it is a CaptureEnter - bsstt.EVMLogger.CaptureEnter(vm.CALL, from, to, precompile, create, input, gas, value, code) } -func (bsstt *borStateSyncTxnTracer) CaptureEnd(output []byte, usedGas uint64, err error, reverted bool) { +func (bsstt *borStateSyncTxnTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { if bsstt.stateSyncEventsCount == 0 { // guard against unexpected use panic("unexpected extra call to borStateSyncTxnTracer.CaptureEnd") @@ -83,153 +78,96 @@ func (bsstt *borStateSyncTxnTracer) CaptureEnd(output []byte, usedGas uint64, er // finished executing 1 event bsstt.stateSyncEventsCount-- - // trick tracer to think it is a CaptureExit - bsstt.EVMLogger.CaptureExit(output, usedGas, err, reverted) - - if bsstt.stateSyncEventsCount == 0 { - // reached last event - // perform a CaptureEnd for the synthetic state sync transaction - bsstt.EVMLogger.CaptureEnd(nil, 0, nil, reverted) - } -} - -func (bsstt *borStateSyncTxnTracer) CaptureState( - pc uint64, - op vm.OpCode, - gas uint64, - cost uint64, - scope *vm.ScopeContext, - rData []byte, - depth int, - err error, -) { - // trick tracer to think it is 1 level deeper - bsstt.EVMLogger.CaptureState(pc, op, gas, cost, scope, rData, depth+1, err) -} - -func (bsstt *borStateSyncTxnTracer) CaptureFault( - pc uint64, - op vm.OpCode, - gas uint64, - cost uint64, - scope *vm.ScopeContext, - depth int, - err error, -) { - // trick tracer to think it is 1 level deeper - bsstt.EVMLogger.CaptureFault(pc, op, gas, cost, scope, depth+1, err) + if bsstt.Tracer.OnExit != nil { + // trick tracer to think it is a CaptureExit + bsstt.Tracer.OnExit(depth, output, gasUsed, err, reverted) + } } -func (bsstt *borStateSyncTxnTracer) GetResult() (json.RawMessage, error) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - return tracer.GetResult() - } else { - panic("unexpected usage - borStateSyncTxnTracer.GetResult called on a wrapped tracer which does not support it") +func (bsstt *borStateSyncTxnTracer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + if bsstt.Tracer.OnEnter != nil { + bsstt.Tracer.OnEnter(depth, typ, from, to, precompile, input, gas, value, code) } } -func (bsstt *borStateSyncTxnTracer) Stop(err error) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.Stop(err) - } else { - panic("unexpected usage - borStateSyncTxnTracer.Stop called on a wrapped tracer which does not support it") +func (bsstt *borStateSyncTxnTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + if bsstt.Tracer.OnOpcode != nil { + // trick tracer to think it is 1 level deeper + bsstt.Tracer.OnOpcode(pc, op, gas, cost, scope, rData, depth+1, err) } } -// CaptureKeccakPreimage is called during the KECCAK256 opcode. -func (bsstt *borStateSyncTxnTracer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.CaptureKeccakPreimage(hash, data) - } else { - panic("unexpected usage - borStateSyncTxnTracer.CaptureKeccakPreimage called on a wrapped tracer which does not support it") +func (bsstt *borStateSyncTxnTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { + if bsstt.Tracer.OnFault != nil { + // trick tracer to think it is 1 level deeper + bsstt.Tracer.OnFault(pc, op, gas, cost, scope, depth+1, err) } } -// OnGasChange is called when gas is either consumed or refunded. -func (bsstt *borStateSyncTxnTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnGasChange(old, new, reason) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnGasChange called on a wrapped tracer which does not support it") +func (bsstt *borStateSyncTxnTracer) GetResult() (json.RawMessage, error) { + if bsstt.Tracer.GetResult != nil { + return bsstt.Tracer.GetResult() } + return nil, nil } -func (bsstt *borStateSyncTxnTracer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnBlockStart(b, td, finalized, safe, chainConfig) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnBlockStart called on a wrapped tracer which does not support it") +func (bsstt *borStateSyncTxnTracer) Stop(err error) { + if bsstt.Tracer.Stop != nil { + bsstt.Tracer.Stop(err) } } -func (bsstt *borStateSyncTxnTracer) OnBlockEnd(err error) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnBlockEnd(err) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnBlockEnd called on a wrapped tracer which does not support it") +// OnGasChange is called when gas is either consumed or refunded. +func (bsstt *borStateSyncTxnTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { + if bsstt.Tracer.OnGasChange != nil { + bsstt.Tracer.OnGasChange(old, new, reason) } } -func (bsstt *borStateSyncTxnTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnGenesisBlock(b, alloc) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnGenesisBlock called on a wrapped tracer which does not support it") +func (bsstt *borStateSyncTxnTracer) OnBlockStart(event tracing.BlockEvent) { + if bsstt.Tracer.OnBlockStart != nil { + bsstt.Tracer.OnBlockStart(event) } } -func (bsstt *borStateSyncTxnTracer) OnBeaconBlockRootStart(root libcommon.Hash) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnBeaconBlockRootStart(root) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnBeaconBlockRootStart called on a wrapped tracer which does not support it") +func (bsstt *borStateSyncTxnTracer) OnBlockEnd(err error) { + if bsstt.Tracer.OnBlockEnd != nil { + bsstt.Tracer.OnBlockEnd(err) } } -func (bsstt *borStateSyncTxnTracer) OnBeaconBlockRootEnd() { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnBeaconBlockRootEnd() - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnBeaconBlockRootEnd called on a wrapped tracer which does not support it") +func (bsstt *borStateSyncTxnTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { + if bsstt.Tracer.OnGenesisBlock != nil { + bsstt.Tracer.OnGenesisBlock(b, alloc) } } -func (bsstt *borStateSyncTxnTracer) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnBalanceChange(a, prev, new, reason) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnBalanceChange called on a wrapped tracer which does not support it") +func (bsstt *borStateSyncTxnTracer) OnBalanceChange(a libcommon.Address, prev, new *uint256.Int, reason tracing.BalanceChangeReason) { + if bsstt.Tracer.OnBalanceChange != nil { + bsstt.Tracer.OnBalanceChange(a, prev, new, reason) } } func (bsstt *borStateSyncTxnTracer) OnNonceChange(a libcommon.Address, prev, new uint64) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnNonceChange(a, prev, new) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnNonceChange called on a wrapped tracer which does not support it") + if bsstt.Tracer.OnNonceChange != nil { + bsstt.Tracer.OnNonceChange(a, prev, new) } } func (bsstt *borStateSyncTxnTracer) OnCodeChange(a libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnCodeChange(a, prevCodeHash, prev, codeHash, code) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnCodeChange called on a wrapped tracer which does not support it") + if bsstt.Tracer.OnCodeChange != nil { + bsstt.Tracer.OnCodeChange(a, prevCodeHash, prev, codeHash, code) } } func (bsstt *borStateSyncTxnTracer) OnStorageChange(a libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnStorageChange(a, k, prev, new) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnStorageChange called on a wrapped tracer which does not support it") + if bsstt.Tracer.OnStorageChange != nil { + bsstt.Tracer.OnStorageChange(a, k, prev, new) } } func (bsstt *borStateSyncTxnTracer) OnLog(log *types.Log) { - if tracer, ok := bsstt.EVMLogger.(tracers.Tracer); ok { - tracer.OnLog(log) - } else { - panic("unexpected usage - borStateSyncTxnTracer.OnLog called on a wrapped tracer which does not support it") + if bsstt.Tracer.OnLog != nil { + bsstt.Tracer.OnLog(log) } } diff --git a/polygon/tracer/trace_bor_state_sync_txn.go b/polygon/tracer/trace_bor_state_sync_txn.go index eb9d8855a36..6d60cfda369 100644 --- a/polygon/tracer/trace_bor_state_sync_txn.go +++ b/polygon/tracer/trace_bor_state_sync_txn.go @@ -17,6 +17,7 @@ import ( "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/eth/tracers" + tracerConfig "github.com/ledgerwatch/erigon/eth/tracers/config" "github.com/ledgerwatch/erigon/polygon/bor/borcfg" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/turbo/services" @@ -27,7 +28,7 @@ func TraceBorStateSyncTxnDebugAPI( ctx context.Context, dbTx kv.Tx, chainConfig *chain.Config, - traceConfig *tracers.TraceConfig, + traceConfig *tracerConfig.TraceConfig, ibs *state.IntraBlockState, blockReader services.FullBlockReader, blockHash libcommon.Hash, @@ -74,6 +75,7 @@ func TraceBorStateSyncTxnTraceAPI( blockHash libcommon.Hash, blockNum uint64, blockTime uint64, + tracer *tracers.Tracer, ) (*core.ExecutionResult, error) { stateSyncEvents, err := blockReader.EventsByBlock(ctx, dbTx, blockHash, blockNum) if err != nil { @@ -82,7 +84,7 @@ func TraceBorStateSyncTxnTraceAPI( stateReceiverContract := libcommon.HexToAddress(chainConfig.Bor.(*borcfg.BorConfig).StateReceiverContract) if vmConfig.Tracer != nil { - vmConfig.Tracer = NewBorStateSyncTxnTracer(vmConfig.Tracer, len(stateSyncEvents), stateReceiverContract) + vmConfig.Tracer = NewBorStateSyncTxnTracer(tracer, len(stateSyncEvents), stateReceiverContract).Hooks } txCtx := initStateSyncTxContext(blockNum, blockHash) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 1480bf508d1..af84a1a58dc 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -39,9 +39,9 @@ import ( "github.com/ledgerwatch/erigon/common/math" "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/eth/ethconfig" "github.com/ledgerwatch/erigon/rlp" @@ -318,7 +318,7 @@ func MakePreState(rules *chain.Rules, tx kv.RwTx, accounts types.GenesisAlloc, b if a.Balance != nil { balance, _ = uint256.FromBig(a.Balance) } - statedb.SetBalance(addr, balance, evmtypes.BalanceChangeUnspecified) + statedb.SetBalance(addr, balance, tracing.BalanceChangeUnspecified) for k, v := range a.Storage { key := k val := uint256.NewInt(0).SetBytes(v.Bytes()) diff --git a/turbo/adapter/ethapi/api.go b/turbo/adapter/ethapi/api.go index a640a8f767d..5ced3af752f 100644 --- a/turbo/adapter/ethapi/api.go +++ b/turbo/adapter/ethapi/api.go @@ -19,9 +19,10 @@ package ethapi import ( "errors" "fmt" - "github.com/ledgerwatch/erigon-lib/common/hexutil" "math/big" + "github.com/ledgerwatch/erigon-lib/common/hexutil" + "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutility" diff --git a/turbo/adapter/ethapi/state_overrides.go b/turbo/adapter/ethapi/state_overrides.go index 23226188489..19c9467058e 100644 --- a/turbo/adapter/ethapi/state_overrides.go +++ b/turbo/adapter/ethapi/state_overrides.go @@ -8,7 +8,7 @@ import ( libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon/core/state" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" + "github.com/ledgerwatch/erigon/core/tracing" ) type StateOverrides map[libcommon.Address]Account @@ -30,7 +30,7 @@ func (overrides *StateOverrides) Override(state *state.IntraBlockState) error { if overflow { return fmt.Errorf("account.Balance higher than 2^256-1") } - state.SetBalance(addr, balance, evmtypes.BalanceChangeUnspecified) + state.SetBalance(addr, balance, tracing.BalanceChangeUnspecified) } if account.State != nil && account.StateDiff != nil { return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) diff --git a/turbo/app/init_cmd.go b/turbo/app/init_cmd.go index ce33eeb2c65..5c1ec2552a5 100644 --- a/turbo/app/init_cmd.go +++ b/turbo/app/init_cmd.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/eth/tracers" "github.com/ledgerwatch/erigon/turbo/debug" @@ -37,7 +38,7 @@ It expects the genesis file as argument.`, // the zero'd block (i.e. genesis) or will fail hard if it can't succeed. func initGenesis(cliCtx *cli.Context) error { var logger log.Logger - var tracer tracers.Tracer + var tracer *tracers.Tracer var err error if logger, tracer, _, err = debug.Setup(cliCtx, true /* rootLogger */); err != nil { return err @@ -67,7 +68,11 @@ func initGenesis(cliCtx *cli.Context) error { if err != nil { utils.Fatalf("Failed to open database: %v", err) } - _, hash, err := core.CommitGenesisBlock(chaindb, genesis, "", logger, tracer) + var tracingHooks *tracing.Hooks + if tracer != nil { + tracingHooks = tracer.Hooks + } + _, hash, err := core.CommitGenesisBlock(chaindb, genesis, "", logger, tracingHooks) if err != nil { utils.Fatalf("Failed to write genesis block: %v", err) } diff --git a/turbo/app/snapshots_cmd.go b/turbo/app/snapshots_cmd.go index dc3c56c5c18..d0c1aa73653 100644 --- a/turbo/app/snapshots_cmd.go +++ b/turbo/app/snapshots_cmd.go @@ -651,7 +651,7 @@ func doRetireCommand(cliCtx *cli.Context) error { func doUploaderCommand(cliCtx *cli.Context) error { var logger log.Logger - var tracer tracers.Tracer + var tracer *tracers.Tracer var err error var metricsMux *http.ServeMux diff --git a/turbo/debug/flags.go b/turbo/debug/flags.go index 5ab263eec63..619bc120200 100644 --- a/turbo/debug/flags.go +++ b/turbo/debug/flags.go @@ -179,7 +179,7 @@ func SetupCobra(cmd *cobra.Command, filePrefix string) log.Logger { // Setup initializes profiling and logging based on the CLI flags. // It should be called as early as possible in the program. -func Setup(ctx *cli.Context, rootLogger bool) (log.Logger, tracers.Tracer, *http.ServeMux, error) { +func Setup(ctx *cli.Context, rootLogger bool) (log.Logger, *tracers.Tracer, *http.ServeMux, error) { // ensure we've read in config file details before setting up metrics etc. if err := SetFlagsFromConfigFile(ctx); err != nil { log.Warn("failed setting config flags from yaml/toml file", "err", err) diff --git a/turbo/jsonrpc/debug_api.go b/turbo/jsonrpc/debug_api.go index 81aab4fc903..7565fadde96 100644 --- a/turbo/jsonrpc/debug_api.go +++ b/turbo/jsonrpc/debug_api.go @@ -18,7 +18,7 @@ import ( "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/eth/stagedsync/stages" - "github.com/ledgerwatch/erigon/eth/tracers" + tracerConfig "github.com/ledgerwatch/erigon/eth/tracers/config" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/turbo/adapter/ethapi" @@ -32,13 +32,13 @@ const AccountRangeMaxResults = 256 // PrivateDebugAPI Exposed RPC endpoints for debugging use type PrivateDebugAPI interface { StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex uint64, contractAddress common.Address, keyStart hexutility.Bytes, maxResult int) (StorageRangeResult, error) - TraceTransaction(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error - TraceBlockByHash(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error - TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *tracers.TraceConfig, stream *jsoniter.Stream) error + TraceTransaction(ctx context.Context, hash common.Hash, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error + TraceBlockByHash(ctx context.Context, hash common.Hash, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error + TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error AccountRange(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, start []byte, maxResults int, nocode, nostorage bool) (state.IteratorDump, error) GetModifiedAccountsByNumber(ctx context.Context, startNum rpc.BlockNumber, endNum *rpc.BlockNumber) ([]common.Address, error) GetModifiedAccountsByHash(_ context.Context, startHash common.Hash, endHash *common.Hash) ([]common.Address, error) - TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *tracers.TraceConfig, stream *jsoniter.Stream) error + TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error AccountAt(ctx context.Context, blockHash common.Hash, txIndex uint64, account common.Address) (*AccountResult, error) GetRawHeader(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutility.Bytes, error) GetRawBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutility.Bytes, error) diff --git a/turbo/jsonrpc/debug_api_test.go b/turbo/jsonrpc/debug_api_test.go index a67440281bf..08e0763b73d 100644 --- a/turbo/jsonrpc/debug_api_test.go +++ b/turbo/jsonrpc/debug_api_test.go @@ -15,7 +15,7 @@ import ( "github.com/ledgerwatch/erigon-lib/kv/order" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/rpcdaemontest" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/eth/tracers" + tracerConfig "github.com/ledgerwatch/erigon/eth/tracers/config" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/rpc/rpccfg" "github.com/ledgerwatch/erigon/turbo/adapter/ethapi" @@ -65,7 +65,7 @@ func TestTraceBlockByNumber(t *testing.T) { if err != nil { t.Errorf("traceBlock %s: %v", tt.txHash, err) } - err = api.TraceBlockByNumber(m.Ctx, rpc.BlockNumber(tx.BlockNumber.ToInt().Uint64()), &tracers.TraceConfig{}, stream) + err = api.TraceBlockByNumber(m.Ctx, rpc.BlockNumber(tx.BlockNumber.ToInt().Uint64()), &tracerConfig.TraceConfig{}, stream) if err != nil { t.Errorf("traceBlock %s: %v", tt.txHash, err) } @@ -82,7 +82,7 @@ func TestTraceBlockByNumber(t *testing.T) { } var buf bytes.Buffer stream := jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096) - err := api.TraceBlockByNumber(m.Ctx, rpc.LatestBlockNumber, &tracers.TraceConfig{}, stream) + err := api.TraceBlockByNumber(m.Ctx, rpc.LatestBlockNumber, &tracerConfig.TraceConfig{}, stream) if err != nil { t.Errorf("traceBlock %v: %v", rpc.LatestBlockNumber, err) } @@ -110,7 +110,7 @@ func TestTraceBlockByHash(t *testing.T) { if err != nil { t.Errorf("traceBlock %s: %v", tt.txHash, err) } - err = api.TraceBlockByHash(m.Ctx, *tx.BlockHash, &tracers.TraceConfig{}, stream) + err = api.TraceBlockByHash(m.Ctx, *tx.BlockHash, &tracerConfig.TraceConfig{}, stream) if err != nil { t.Errorf("traceBlock %s: %v", tt.txHash, err) } @@ -133,7 +133,7 @@ func TestTraceTransaction(t *testing.T) { for _, tt := range debugTraceTransactionTests { var buf bytes.Buffer stream := jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096) - err := api.TraceTransaction(m.Ctx, common.HexToHash(tt.txHash), &tracers.TraceConfig{}, stream) + err := api.TraceTransaction(m.Ctx, common.HexToHash(tt.txHash), &tracerConfig.TraceConfig{}, stream) if err != nil { t.Errorf("traceTransaction %s: %v", tt.txHash, err) } @@ -163,7 +163,7 @@ func TestTraceTransactionNoRefund(t *testing.T) { var buf bytes.Buffer stream := jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096) var norefunds = true - err := api.TraceTransaction(m.Ctx, common.HexToHash(tt.txHash), &tracers.TraceConfig{NoRefunds: &norefunds}, stream) + err := api.TraceTransaction(m.Ctx, common.HexToHash(tt.txHash), &tracerConfig.TraceConfig{NoRefunds: &norefunds}, stream) if err != nil { t.Errorf("traceTransaction %s: %v", tt.txHash, err) } diff --git a/turbo/jsonrpc/eth_call.go b/turbo/jsonrpc/eth_call.go index 84871efc594..8f87a781aa4 100644 --- a/turbo/jsonrpc/eth_call.go +++ b/turbo/jsonrpc/eth_call.go @@ -548,7 +548,7 @@ func (api *APIImpl) CreateAccessList(ctx context.Context, args ethapi2.CallArgs, // Apply the transaction with the access list tracer tracer := logger.NewAccessListTracer(accessList, excl, state) - config := vm.Config{Tracer: tracer, Debug: true, NoBaseFee: true} + config := vm.Config{Tracer: tracer.Hooks(), Debug: true, NoBaseFee: true} blockCtx := transactions.NewEVMBlockContext(engine, header, bNrOrHash.RequireCanonical, tx, api._blockReader) txCtx := core.NewEVMTxContext(msg) diff --git a/turbo/jsonrpc/eth_receipts.go b/turbo/jsonrpc/eth_receipts.go index e0f14b790f0..140290ca302 100644 --- a/turbo/jsonrpc/eth_receipts.go +++ b/turbo/jsonrpc/eth_receipts.go @@ -523,7 +523,7 @@ func txnExecutor(tx kv.TemporalTx, chainConfig *chain.Config, engine consensus.E ibs: state.New(stateReader), } if tracer != nil { - ie.vmConfig = &vm.Config{Debug: true, Tracer: tracer} + ie.vmConfig = &vm.Config{Debug: true, Tracer: tracer.Tracer().Hooks} } return ie } diff --git a/turbo/jsonrpc/gen_traces_test.go b/turbo/jsonrpc/gen_traces_test.go index a5f4c7bdc7a..32ff7fcf673 100644 --- a/turbo/jsonrpc/gen_traces_test.go +++ b/turbo/jsonrpc/gen_traces_test.go @@ -13,7 +13,7 @@ import ( "github.com/ledgerwatch/erigon-lib/kv/kvcache" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/cli/httpcfg" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/rpcdaemontest" - "github.com/ledgerwatch/erigon/eth/tracers" + tracerConfig "github.com/ledgerwatch/erigon/eth/tracers/config" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/rpc/rpccfg" @@ -35,7 +35,7 @@ func TestGeneratedDebugApi(t *testing.T) { var buf bytes.Buffer stream := jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096) callTracer := "callTracer" - err := api.TraceBlockByNumber(context.Background(), rpc.BlockNumber(1), &tracers.TraceConfig{Tracer: &callTracer}, stream) + err := api.TraceBlockByNumber(context.Background(), rpc.BlockNumber(1), &tracerConfig.TraceConfig{Tracer: &callTracer}, stream) if err != nil { t.Errorf("debug_traceBlock %d: %v", 0, err) } diff --git a/turbo/jsonrpc/otterscan_api.go b/turbo/jsonrpc/otterscan_api.go index 4e294b521ed..54bd5e84c6d 100644 --- a/turbo/jsonrpc/otterscan_api.go +++ b/turbo/jsonrpc/otterscan_api.go @@ -116,7 +116,7 @@ func (api *OtterscanAPIImpl) getTransactionByHash(ctx context.Context, tx kv.Tx, return txn, block, blockHash, blockNum, txnIndex, nil } -func (api *OtterscanAPIImpl) runTracer(ctx context.Context, tx kv.Tx, hash common.Hash, tracer tracers.Tracer) (*core.ExecutionResult, error) { +func (api *OtterscanAPIImpl) runTracer(ctx context.Context, tx kv.Tx, hash common.Hash, tracer *tracers.Tracer) (*core.ExecutionResult, error) { txn, block, _, _, txIndex, err := api.getTransactionByHash(ctx, tx, hash) if err != nil { return nil, err @@ -136,26 +136,26 @@ func (api *OtterscanAPIImpl) runTracer(ctx context.Context, tx kv.Tx, hash commo return nil, err } - ibs.SetLogger(tracer) + ibs.SetLogger(tracer.Hooks) var vmConfig vm.Config if tracer == nil { vmConfig = vm.Config{} } else { - vmConfig = vm.Config{Debug: true, Tracer: tracer} + vmConfig = vm.Config{Debug: true, Tracer: tracer.Hooks} } vmenv := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vmConfig) - tracer.CaptureTxStart(vmenv, txn) + tracer.OnTxStart(vmenv.GetVMContext(), txn, msg.From()) result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()), true, false /* gasBailout */) if err != nil { if tracer != nil { - tracer.CaptureTxEnd(nil, err) + tracer.OnTxEnd(nil, err) } return nil, fmt.Errorf("tracing failed: %v", err) } if tracer != nil { - tracer.CaptureTxEnd(&types.Receipt{GasUsed: result.UsedGas}, nil) + tracer.OnTxEnd(&types.Receipt{GasUsed: result.UsedGas}, nil) } return result, nil } @@ -168,7 +168,7 @@ func (api *OtterscanAPIImpl) GetInternalOperations(ctx context.Context, hash com defer tx.Rollback() tracer := NewOperationsTracer(ctx) - if _, err := api.runTracer(ctx, tx, hash, tracer); err != nil { + if _, err := api.runTracer(ctx, tx, hash, tracer.Tracer()); err != nil { return nil, err } diff --git a/turbo/jsonrpc/otterscan_default_tracer.go b/turbo/jsonrpc/otterscan_default_tracer.go index d1c0842c4c1..a03f7f73bd6 100644 --- a/turbo/jsonrpc/otterscan_default_tracer.go +++ b/turbo/jsonrpc/otterscan_default_tracer.go @@ -1,79 +1,7 @@ package jsonrpc -import ( - "encoding/json" - "math/big" - - "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/chain" - "github.com/ledgerwatch/erigon-lib/common" - libcommon "github.com/ledgerwatch/erigon-lib/common" - - "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" -) - // Helper implementation of vm.Tracer; since the interface is big and most // custom tracers implement just a few of the methods, this is a base struct // to avoid lots of empty boilerplate code type DefaultTracer struct { } - -func (a *DefaultTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) {} - -func (a *DefaultTracer) CaptureTxEnd(receipt *types.Receipt, err error) {} - -func (a *DefaultTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { -} - -func (t *DefaultTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { -} - -func (t *DefaultTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { -} - -func (t *DefaultTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { -} - -func (t *DefaultTracer) CaptureEnd(output []byte, usedGas uint64, err error, reverted bool) { -} - -func (t *DefaultTracer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { -} - -func (t *DefaultTracer) OnBlockEnd(err error) { -} - -func (t *DefaultTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { -} - -func (t *DefaultTracer) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (t *DefaultTracer) OnBeaconBlockRootEnd() {} - -func (t *DefaultTracer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (t *DefaultTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (t *DefaultTracer) OnBalanceChange(addr libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (t *DefaultTracer) OnNonceChange(addr libcommon.Address, prev, new uint64) {} - -func (t *DefaultTracer) OnCodeChange(addr libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (t *DefaultTracer) OnStorageChange(addr libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { -} - -func (t *DefaultTracer) OnLog(log *types.Log) {} - -func (t *DefaultTracer) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { -} - -func (t *DefaultTracer) GetResult() (json.RawMessage, error) { - return json.RawMessage{}, nil -} - -func (t *DefaultTracer) Stop(err error) {} diff --git a/turbo/jsonrpc/otterscan_generic_tracer.go b/turbo/jsonrpc/otterscan_generic_tracer.go index 676e6ee9b2a..acb5c1ccb84 100644 --- a/turbo/jsonrpc/otterscan_generic_tracer.go +++ b/turbo/jsonrpc/otterscan_generic_tracer.go @@ -18,7 +18,7 @@ import ( ) type GenericTracer interface { - tracers.Tracer + Tracer() *tracers.Tracer SetTransaction(tx types.Transaction) Found() bool } @@ -64,7 +64,7 @@ func (api *OtterscanAPIImpl) genericTracer(dbtx kv.Tx, ctx context.Context, bloc cachedWriter := state.NewCachedWriter(noop, stateCache) ibs := state.New(cachedReader) - ibs.SetLogger(tracer) + ibs.SetLogger(tracer.Tracer().Hooks) getHeader := func(hash common.Hash, number uint64) *types.Header { h, e := api._blockReader.Header(ctx, dbtx, hash, number) @@ -99,20 +99,20 @@ func (api *OtterscanAPIImpl) genericTracer(dbtx kv.Tx, ctx context.Context, bloc BlockContext := core.NewEVMBlockContext(header, core.GetHashFn(header, getHeader), engine, nil) TxContext := core.NewEVMTxContext(msg) - vmenv := vm.NewEVM(BlockContext, TxContext, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer}) + vmenv := vm.NewEVM(BlockContext, TxContext, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer.Tracer().Hooks}) if tracer != nil { - tracer.CaptureTxStart(vmenv, tx) + tracer.Tracer().OnTxStart(vmenv.GetVMContext(), tx, msg.From()) } res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.GetGas()).AddBlobGas(tx.GetBlobGas()), true /* refunds */, false /* gasBailout */) if err != nil { if tracer != nil { - tracer.CaptureTxEnd(nil, err) + tracer.Tracer().OnTxEnd(nil, err) } return err } if tracer != nil { - tracer.CaptureTxEnd(&types.Receipt{GasUsed: res.UsedGas}, nil) + tracer.Tracer().OnTxEnd(&types.Receipt{GasUsed: res.UsedGas}, nil) } _ = ibs.FinalizeTx(rules, cachedWriter) diff --git a/turbo/jsonrpc/otterscan_search_trace.go b/turbo/jsonrpc/otterscan_search_trace.go index fcf1b0f8a89..cd69c243a96 100644 --- a/turbo/jsonrpc/otterscan_search_trace.go +++ b/turbo/jsonrpc/otterscan_search_trace.go @@ -89,18 +89,18 @@ func (api *OtterscanAPIImpl) traceBlock(dbtx kv.Tx, ctx context.Context, blockNu msg, _ := tx.AsMessage(*signer, header.BaseFee, rules) tracer := NewTouchTracer(searchAddr) - ibs.SetLogger(tracer) + ibs.SetLogger(tracer.Tracer().Hooks) BlockContext := core.NewEVMBlockContext(header, core.GetHashFn(header, getHeader), engine, nil) TxContext := core.NewEVMTxContext(msg) - vmenv := vm.NewEVM(BlockContext, TxContext, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer}) - tracer.CaptureTxStart(vmenv, tx) + vmenv := vm.NewEVM(BlockContext, TxContext, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer.Tracer().Hooks}) + tracer.Tracer().OnTxStart(vmenv.GetVMContext(), tx, msg.From()) res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.GetGas()).AddBlobGas(tx.GetBlobGas()), true /* refunds */, false /* gasBailout */) if err != nil { - tracer.CaptureTxEnd(nil, err) + tracer.Tracer().OnTxEnd(nil, err) return false, nil, err } - tracer.CaptureTxEnd(&types.Receipt{GasUsed: res.UsedGas}, nil) + tracer.Tracer().OnTxEnd(&types.Receipt{GasUsed: res.UsedGas}, nil) _ = ibs.FinalizeTx(rules, cachedWriter) if tracer.Found { diff --git a/turbo/jsonrpc/otterscan_trace_contract_creator.go b/turbo/jsonrpc/otterscan_trace_contract_creator.go index cc9ca4057d1..f57c0d23bcb 100644 --- a/turbo/jsonrpc/otterscan_trace_contract_creator.go +++ b/turbo/jsonrpc/otterscan_trace_contract_creator.go @@ -6,8 +6,10 @@ import ( "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/eth/tracers" ) type CreateTracer struct { @@ -27,6 +29,14 @@ func NewCreateTracer(ctx context.Context, target common.Address) *CreateTracer { } } +func (t *CreateTracer) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnEnter: t.OnEnter, + }, + } +} + func (t *CreateTracer) SetTransaction(tx types.Transaction) { t.Tx = tx } @@ -50,10 +60,6 @@ func (t *CreateTracer) captureStartOrEnter(from, to common.Address, create bool) t.Creator = from } -func (t *CreateTracer) CaptureStart(from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - t.captureStartOrEnter(from, to, create) -} - -func (t *CreateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - t.captureStartOrEnter(from, to, create) +func (t *CreateTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + t.captureStartOrEnter(from, to, vm.OpCode(typ) == vm.CREATE) } diff --git a/turbo/jsonrpc/otterscan_trace_operations.go b/turbo/jsonrpc/otterscan_trace_operations.go index 2d032b903d0..3dd95cf3129 100644 --- a/turbo/jsonrpc/otterscan_trace_operations.go +++ b/turbo/jsonrpc/otterscan_trace_operations.go @@ -2,12 +2,16 @@ package jsonrpc import ( "context" + "encoding/json" + "github.com/ledgerwatch/erigon-lib/common/hexutil" "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/eth/tracers" ) type OperationType int @@ -27,7 +31,6 @@ type InternalOperation struct { } type OperationsTracer struct { - DefaultTracer ctx context.Context Results []*InternalOperation } @@ -39,18 +42,34 @@ func NewOperationsTracer(ctx context.Context) *OperationsTracer { } } -func (t *OperationsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - if typ == vm.CALL && value.Uint64() != 0 { +func (t *OperationsTracer) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnEnter: t.OnEnter, + }, + GetResult: t.GetResult, + Stop: t.Stop, + } +} + +func (t *OperationsTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + if vm.OpCode(typ) == vm.CALL && value.Uint64() != 0 { t.Results = append(t.Results, &InternalOperation{OP_TRANSFER, from, to, (*hexutil.Big)(value.ToBig())}) return } - if typ == vm.CREATE { + if vm.OpCode(typ) == vm.CREATE { t.Results = append(t.Results, &InternalOperation{OP_CREATE, from, to, (*hexutil.Big)(value.ToBig())}) } - if typ == vm.CREATE2 { + if vm.OpCode(typ) == vm.CREATE2 { t.Results = append(t.Results, &InternalOperation{OP_CREATE2, from, to, (*hexutil.Big)(value.ToBig())}) } - if typ == vm.SELFDESTRUCT { + if vm.OpCode(typ) == vm.SELFDESTRUCT { t.Results = append(t.Results, &InternalOperation{OP_SELF_DESTRUCT, from, to, (*hexutil.Big)(value.ToBig())}) } } + +func (t *OperationsTracer) GetResult() (json.RawMessage, error) { + return json.RawMessage{}, nil +} + +func (t *OperationsTracer) Stop(err error) {} diff --git a/turbo/jsonrpc/otterscan_trace_touch.go b/turbo/jsonrpc/otterscan_trace_touch.go index 03b05629a17..32a09415156 100644 --- a/turbo/jsonrpc/otterscan_trace_touch.go +++ b/turbo/jsonrpc/otterscan_trace_touch.go @@ -6,7 +6,8 @@ import ( "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/core/tracing" + "github.com/ledgerwatch/erigon/eth/tracers" ) type TouchTracer struct { @@ -21,16 +22,20 @@ func NewTouchTracer(searchAddr common.Address) *TouchTracer { } } +func (t *TouchTracer) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnEnter: t.OnEnter, + }, + } +} + func (t *TouchTracer) captureStartOrEnter(from, to common.Address) { if !t.Found && (bytes.Equal(t.searchAddr.Bytes(), from.Bytes()) || bytes.Equal(t.searchAddr.Bytes(), to.Bytes())) { t.Found = true } } -func (t *TouchTracer) CaptureStart(from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - t.captureStartOrEnter(from, to) -} - -func (t *TouchTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (t *TouchTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { t.captureStartOrEnter(from, to) } diff --git a/turbo/jsonrpc/otterscan_trace_transaction.go b/turbo/jsonrpc/otterscan_trace_transaction.go index cc8c95fc0ba..1909143f441 100644 --- a/turbo/jsonrpc/otterscan_trace_transaction.go +++ b/turbo/jsonrpc/otterscan_trace_transaction.go @@ -11,7 +11,9 @@ import ( "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutility" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/eth/tracers" ) func (api *OtterscanAPIImpl) TraceTransaction(ctx context.Context, hash common.Hash) ([]*TraceEntry, error) { @@ -22,7 +24,7 @@ func (api *OtterscanAPIImpl) TraceTransaction(ctx context.Context, hash common.H defer tx.Rollback() tracer := NewTransactionTracer(ctx) - if _, err := api.runTracer(ctx, tx, hash, tracer); err != nil { + if _, err := api.runTracer(ctx, tx, hash, tracer.Tracer()); err != nil { return nil, err } @@ -52,6 +54,15 @@ func NewTransactionTracer(ctx context.Context) *TransactionTracer { } } +func (t *TransactionTracer) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnEnter: t.OnEnter, + OnExit: t.OnExit, + }, + } +} + func (t *TransactionTracer) captureStartOrEnter(typ vm.OpCode, from, to common.Address, precompile bool, input []byte, value *uint256.Int) { if precompile { return @@ -99,11 +110,11 @@ func (t *TransactionTracer) CaptureStart(from common.Address, to common.Address, t.captureStartOrEnter(vm.CALL, from, to, precompile, input, value) } -func (t *TransactionTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - t.depth++ - t.captureStartOrEnter(typ, from, to, precompile, input, value) +func (t *TransactionTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + t.depth = depth + t.captureStartOrEnter(vm.OpCode(typ), from, to, precompile, input, value) } -func (t *TransactionTracer) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { - t.depth-- +func (t *TransactionTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + t.depth = depth } diff --git a/turbo/jsonrpc/trace_adhoc.go b/turbo/jsonrpc/trace_adhoc.go index 2ff3c2c45a5..b7fa5a5253d 100644 --- a/turbo/jsonrpc/trace_adhoc.go +++ b/turbo/jsonrpc/trace_adhoc.go @@ -6,13 +6,11 @@ import ( "encoding/json" "fmt" "math" - "math/big" "strings" "github.com/holiman/uint256" "github.com/ledgerwatch/log/v3" - "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutil" "github.com/ledgerwatch/erigon-lib/common/hexutility" @@ -21,10 +19,10 @@ import ( math2 "github.com/ledgerwatch/erigon/common/math" "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/eth/tracers" ptracer "github.com/ledgerwatch/erigon/polygon/tracer" "github.com/ledgerwatch/erigon/rpc" @@ -301,9 +299,17 @@ type OeTracer struct { idx []string // Prefix for the "idx" inside operations, for easier navigation } -func (ot *OeTracer) CaptureTxStart(env *vm.EVM, tx types.Transaction) {} - -func (ot *OeTracer) CaptureTxEnd(receipt *types.Receipt, err error) {} +func (ot *OeTracer) Tracer() *tracers.Tracer { + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnEnter: ot.OnEnter, + OnExit: ot.OnExit, + OnOpcode: ot.OnOpcode, + }, + GetResult: ot.GetResult, + Stop: ot.Stop, + } +} func (ot *OeTracer) captureStartOrEnter(deep bool, typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { //fmt.Printf("captureStartOrEnter deep %t, typ %s, from %x, to %x, create %t, input %x, gas %d, value %d, precompile %t\n", deep, typ.String(), from, to, create, input, gas, value, precompile) @@ -411,12 +417,8 @@ func (ot *OeTracer) captureStartOrEnter(deep bool, typ vm.OpCode, from libcommon ot.traceStack = append(ot.traceStack, trace) } -func (ot *OeTracer) CaptureStart(from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - ot.captureStartOrEnter(false /* deep */, vm.CALL, from, to, precompile, create, input, gas, value, code) -} - -func (ot *OeTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - ot.captureStartOrEnter(true /* deep */, typ, from, to, precompile, create, input, gas, value, code) +func (ot *OeTracer) OnEnter(depth int, typ byte, from libcommon.Address, to libcommon.Address, precompile bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + ot.captureStartOrEnter(depth != 0 /* deep */, vm.OpCode(typ), from, to, precompile, vm.OpCode(typ) == vm.CREATE, input, gas, value, code) } func (ot *OeTracer) captureEndOrExit(deep bool, output []byte, usedGas uint64, err error) { @@ -488,17 +490,13 @@ func (ot *OeTracer) captureEndOrExit(deep bool, output []byte, usedGas uint64, e } } -func (ot *OeTracer) CaptureEnd(output []byte, usedGas uint64, err error, reverted bool) { - ot.captureEndOrExit(false /* deep */, output, usedGas, err) -} - -func (ot *OeTracer) CaptureExit(output []byte, usedGas uint64, err error, reverted bool) { - ot.captureEndOrExit(true /* deep */, output, usedGas, err) +func (ot *OeTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + ot.captureEndOrExit(depth != 0 /* deep */, output, gasUsed, err) } -func (ot *OeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, opDepth int, err error) { - memory := scope.Memory - st := scope.Stack +func (ot *OeTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + memory := scope.MemoryData() + st := scope.StackData() if ot.r.VmTrace != nil { var vmTrace *VmTrace @@ -527,8 +525,8 @@ func (ot *OeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scop showStack = 1 } for i := showStack - 1; i >= 0; i-- { - if st.Len() > i { - ot.lastVmOp.Ex.Push = append(ot.lastVmOp.Ex.Push, st.Back(i).String()) + if len(st) > i { + ot.lastVmOp.Ex.Push = append(ot.lastVmOp.Ex.Push, tracers.StackBack(st, i).String()) } } // Set the "mem" of the last operation @@ -538,7 +536,8 @@ func (ot *OeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scop setMem = true } if setMem && ot.lastMemLen > 0 { - cpy := memory.GetCopy(int64(ot.lastMemOff), int64(ot.lastMemLen)) + // TODO: error handling + cpy, _ := tracers.GetMemoryCopyPadded(memory, int64(ot.lastMemOff), int64(ot.lastMemLen)) if len(cpy) == 0 { cpy = make([]byte, ot.lastMemLen) } @@ -547,13 +546,13 @@ func (ot *OeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scop } if ot.lastOffStack != nil { ot.lastOffStack.Ex.Used = int(gas) - if st.Len() > 0 { - ot.lastOffStack.Ex.Push = []string{st.Back(0).String()} + if len(st) > 0 { + ot.lastOffStack.Ex.Push = []string{tracers.StackBack(st, 0).String()} } else { ot.lastOffStack.Ex.Push = []string{} } if ot.lastMemLen > 0 && memory != nil { - cpy := memory.GetCopy(int64(ot.lastMemOff), int64(ot.lastMemLen)) + cpy, _ := tracers.GetMemoryCopyPadded(memory, int64(ot.lastMemOff), int64(ot.lastMemLen)) if len(cpy) == 0 { cpy = make([]byte, ot.lastMemLen) } @@ -561,7 +560,7 @@ func (ot *OeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scop } ot.lastOffStack = nil } - if ot.lastOp == vm.STOP && op == vm.STOP && len(ot.vmOpStack) == 0 { + if ot.lastOp == vm.STOP && vm.OpCode(op) == vm.STOP && len(ot.vmOpStack) == 0 { // Looks like OE is "optimising away" the second STOP return } @@ -575,52 +574,52 @@ func (ot *OeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scop } ot.lastVmOp.Idx = fmt.Sprintf("%s%d", sb.String(), len(vmTrace.Ops)-1) } - ot.lastOp = op + ot.lastOp = vm.OpCode(op) ot.lastVmOp.Cost = int(cost) ot.lastVmOp.Pc = int(pc) ot.lastVmOp.Ex.Push = []string{} ot.lastVmOp.Ex.Used = int(gas) - int(cost) if !ot.compat { - ot.lastVmOp.Op = op.String() + ot.lastVmOp.Op = vm.OpCode(op).String() } - switch op { + switch vm.OpCode(op) { case vm.MSTORE, vm.MLOAD: - if st.Len() > 0 { - ot.lastMemOff = st.Back(0).Uint64() + if len(st) > 0 { + ot.lastMemOff = tracers.StackBack(st, 0).Uint64() ot.lastMemLen = 32 } case vm.MSTORE8: - if st.Len() > 0 { - ot.lastMemOff = st.Back(0).Uint64() + if len(st) > 0 { + ot.lastMemOff = tracers.StackBack(st, 0).Uint64() ot.lastMemLen = 1 } case vm.RETURNDATACOPY, vm.CALLDATACOPY, vm.CODECOPY: - if st.Len() > 2 { - ot.lastMemOff = st.Back(0).Uint64() - ot.lastMemLen = st.Back(2).Uint64() + if len(st) > 2 { + ot.lastMemOff = tracers.StackBack(st, 0).Uint64() + ot.lastMemLen = tracers.StackBack(st, 2).Uint64() } case vm.EXTCODECOPY: - if st.Len() > 3 { - ot.lastMemOff = st.Back(1).Uint64() - ot.lastMemLen = st.Back(3).Uint64() + if len(st) > 3 { + ot.lastMemOff = tracers.StackBack(st, 1).Uint64() + ot.lastMemLen = tracers.StackBack(st, 3).Uint64() } case vm.STATICCALL, vm.DELEGATECALL: - if st.Len() > 5 { - ot.memOffStack = append(ot.memOffStack, st.Back(4).Uint64()) - ot.memLenStack = append(ot.memLenStack, st.Back(5).Uint64()) + if len(st) > 5 { + ot.memOffStack = append(ot.memOffStack, tracers.StackBack(st, 4).Uint64()) + ot.memLenStack = append(ot.memLenStack, tracers.StackBack(st, 5).Uint64()) } case vm.CALL, vm.CALLCODE: - if st.Len() > 6 { - ot.memOffStack = append(ot.memOffStack, st.Back(5).Uint64()) - ot.memLenStack = append(ot.memLenStack, st.Back(6).Uint64()) + if len(st) > 6 { + ot.memOffStack = append(ot.memOffStack, tracers.StackBack(st, 5).Uint64()) + ot.memLenStack = append(ot.memLenStack, tracers.StackBack(st, 6).Uint64()) } case vm.CREATE, vm.CREATE2, vm.SELFDESTRUCT: // Effectively disable memory output ot.memOffStack = append(ot.memOffStack, 0) ot.memLenStack = append(ot.memLenStack, 0) case vm.SSTORE: - if st.Len() > 1 { - ot.lastVmOp.Ex.Store = &VmTraceStore{Key: st.Back(0).String(), Val: st.Back(1).String()} + if len(st) > 1 { + ot.lastVmOp.Ex.Store = &VmTraceStore{Key: tracers.StackBack(st, 0).String(), Val: tracers.StackBack(st, 1).String()} } } if ot.lastVmOp.Ex.Used < 0 { @@ -629,39 +628,6 @@ func (ot *OeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scop } } -func (ot *OeTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, opDepth int, err error) { -} - -func (ot *OeTracer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, chainConfig *chain.Config) { -} - -func (ot *OeTracer) OnBlockEnd(err error) { -} - -func (ot *OeTracer) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { -} - -func (ot *OeTracer) OnBeaconBlockRootStart(root libcommon.Hash) {} - -func (ot *OeTracer) OnBeaconBlockRootEnd() {} - -func (ot *OeTracer) CaptureKeccakPreimage(hash libcommon.Hash, data []byte) {} - -func (ot *OeTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} - -func (ot *OeTracer) OnBalanceChange(addr libcommon.Address, prev, new *uint256.Int, reason evmtypes.BalanceChangeReason) { -} - -func (ot *OeTracer) OnNonceChange(addr libcommon.Address, prev, new uint64) {} - -func (aot *OeTracer) OnCodeChange(addr libcommon.Address, prevCodeHash libcommon.Hash, prev []byte, codeHash libcommon.Hash, code []byte) { -} - -func (ot *OeTracer) OnStorageChange(addr libcommon.Address, k *libcommon.Hash, prev, new uint256.Int) { -} - -func (ot *OeTracer) OnLog(log *types.Log) {} - func (ot *OeTracer) GetResult() (json.RawMessage, error) { return json.RawMessage{}, nil } @@ -1078,7 +1044,7 @@ func (api *TraceAPIImpl) Call(ctx context.Context, args TraceCallParam, traceTyp blockCtx.GasLimit = math.MaxUint64 blockCtx.MaxGasLimit = true - evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Debug: traceTypeTrace, Tracer: &ot}) + evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Debug: traceTypeTrace, Tracer: ot.Tracer().Hooks}) // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1090,15 +1056,15 @@ func (api *TraceAPIImpl) Call(ctx context.Context, args TraceCallParam, traceTyp gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) var execResult *core.ExecutionResult ibs.SetTxContext(libcommon.Hash{}, libcommon.Hash{}, 0) - ibs.SetLogger(&ot) + ibs.SetLogger(ot.Tracer().Hooks) - ot.CaptureTxStart(evm, txn) + ot.Tracer().OnTxStart(evm.GetVMContext(), txn, msg.From()) execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */) if err != nil { - ot.CaptureTxEnd(nil, err) + ot.Tracer().OnTxEnd(nil, err) return nil, err } - ot.CaptureTxEnd(&types.Receipt{GasUsed: execResult.UsedGas}, nil) + ot.Tracer().OnTxEnd(&types.Receipt{GasUsed: execResult.UsedGas}, nil) traceResult.Output = libcommon.CopyBytes(execResult.ReturnData) if traceTypeStateDiff { sdMap := make(map[libcommon.Address]*StateDiffAccount) @@ -1290,7 +1256,7 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, txns []type traceResult := &TraceCallResult{Trace: []*ParityTrace{}, TransactionHash: args.txHash} vmConfig := vm.Config{} - var tracer tracers.Tracer + var tracer *tracers.Tracer if (traceTypeTrace && (txIndexNeeded == -1 || txIndex == txIndexNeeded)) || traceTypeVmTrace { var ot OeTracer ot.compat = api.compatibility @@ -1303,8 +1269,8 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, txns []type traceResult.VmTrace = &VmTrace{Ops: []*VmTraceOp{}} } vmConfig.Debug = true - vmConfig.Tracer = &ot - tracer = &ot + vmConfig.Tracer = ot.Tracer().Hooks + tracer = ot.Tracer() } blockCtx := transactions.NewEVMBlockContext(engine, header, parentNrOrHash.RequireCanonical, dbtx, api._blockReader) @@ -1350,6 +1316,7 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, txns []type header.Hash(), header.Number.Uint64(), header.Time, + tracer, ) } else { if args.txHash != nil { @@ -1358,24 +1325,24 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, txns []type ibs.SetTxContext(libcommon.Hash{}, header.Hash(), txIndex) } - ibs.SetLogger(tracer) + ibs.SetLogger(tracer.Hooks) txCtx := core.NewEVMTxContext(msg) evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vmConfig) gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) if tracer != nil { - tracer.CaptureTxStart(evm, txns[txIndex]) + tracer.OnTxStart(evm.GetVMContext(), txns[txIndex], msg.From()) } execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, gasBailout /* gasBailout */) } if err != nil { if tracer != nil { - tracer.CaptureTxEnd(nil, err) + tracer.OnTxEnd(nil, err) } return nil, nil, fmt.Errorf("first run for txIndex %d error: %w", txIndex, err) } if tracer != nil { - tracer.CaptureTxEnd(&types.Receipt{GasUsed: execResult.UsedGas}, nil) + tracer.OnTxEnd(&types.Receipt{GasUsed: execResult.UsedGas}, nil) } chainRules := chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Time) diff --git a/turbo/jsonrpc/trace_filtering.go b/turbo/jsonrpc/trace_filtering.go index edc1f39b941..b1c25decd11 100644 --- a/turbo/jsonrpc/trace_filtering.go +++ b/turbo/jsonrpc/trace_filtering.go @@ -772,7 +772,7 @@ func (api *TraceAPIImpl) filterV3(ctx context.Context, dbtx kv.TemporalTx, fromB ot.idx = []string{fmt.Sprintf("%d-", txIndex)} ot.traceAddr = []int{} vmConfig.Debug = true - vmConfig.Tracer = &ot + vmConfig.Tracer = ot.Tracer().Hooks ibs := state.New(cachedReader) blockCtx := transactions.NewEVMBlockContext(engine, lastHeader, true /* requireCanonical */, dbtx, api._blockReader) @@ -781,13 +781,13 @@ func (api *TraceAPIImpl) filterV3(ctx context.Context, dbtx kv.TemporalTx, fromB gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) ibs.SetTxContext(txHash, lastBlockHash, txIndex) - ibs.SetLogger(&ot) + ibs.SetLogger(ot.Tracer().Hooks) - ot.CaptureTxStart(evm, txn) + ot.Tracer().OnTxStart(evm.GetVMContext(), txn, msg.From()) var execResult *core.ExecutionResult execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */) if err != nil { - ot.CaptureTxEnd(nil, err) + ot.Tracer().OnTxEnd(nil, err) if first { first = false } else { @@ -798,7 +798,7 @@ func (api *TraceAPIImpl) filterV3(ctx context.Context, dbtx kv.TemporalTx, fromB stream.WriteObjectEnd() continue } - ot.CaptureTxEnd(&types.Receipt{GasUsed: execResult.UsedGas}, nil) + ot.Tracer().OnTxEnd(&types.Receipt{GasUsed: execResult.UsedGas}, nil) traceResult.Output = common.Copy(execResult.ReturnData) if err = ibs.FinalizeTx(evm.ChainRules(), noop); err != nil { if first { diff --git a/turbo/jsonrpc/tracing.go b/turbo/jsonrpc/tracing.go index 0be54df48a2..969c0d52732 100644 --- a/turbo/jsonrpc/tracing.go +++ b/turbo/jsonrpc/tracing.go @@ -18,7 +18,7 @@ import ( "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/evmtypes" - "github.com/ledgerwatch/erigon/eth/tracers" + tracerConfig "github.com/ledgerwatch/erigon/eth/tracers/config" polygontracer "github.com/ledgerwatch/erigon/polygon/tracer" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/turbo/adapter/ethapi" @@ -27,16 +27,16 @@ import ( ) // TraceBlockByNumber implements debug_traceBlockByNumber. Returns Geth style block traces. -func (api *PrivateDebugAPIImpl) TraceBlockByNumber(ctx context.Context, blockNum rpc.BlockNumber, config *tracers.TraceConfig, stream *jsoniter.Stream) error { +func (api *PrivateDebugAPIImpl) TraceBlockByNumber(ctx context.Context, blockNum rpc.BlockNumber, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error { return api.traceBlock(ctx, rpc.BlockNumberOrHashWithNumber(blockNum), config, stream) } // TraceBlockByHash implements debug_traceBlockByHash. Returns Geth style block traces. -func (api *PrivateDebugAPIImpl) TraceBlockByHash(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error { +func (api *PrivateDebugAPIImpl) TraceBlockByHash(ctx context.Context, hash common.Hash, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error { return api.traceBlock(ctx, rpc.BlockNumberOrHashWithHash(hash, true), config, stream) } -func (api *PrivateDebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, config *tracers.TraceConfig, stream *jsoniter.Stream) error { +func (api *PrivateDebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error { tx, err := api.db.BeginRo(ctx) if err != nil { stream.WriteNil() @@ -79,7 +79,7 @@ func (api *PrivateDebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rp } if config == nil { - config = &tracers.TraceConfig{} + config = &tracerConfig.TraceConfig{} } if config.BorTraceEnabled == nil { @@ -204,7 +204,7 @@ func (api *PrivateDebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rp } // TraceTransaction implements debug_traceTransaction. Returns Geth style transaction traces. -func (api *PrivateDebugAPIImpl) TraceTransaction(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error { +func (api *PrivateDebugAPIImpl) TraceTransaction(ctx context.Context, hash common.Hash, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error { tx, err := api.db.BeginRo(ctx) if err != nil { stream.WriteNil() @@ -309,7 +309,7 @@ func (api *PrivateDebugAPIImpl) TraceTransaction(ctx context.Context, hash commo return transactions.TraceTx(ctx, txn, msg, blockCtx, txCtx, ibs, config, chainConfig, stream, api.evmCallTimeout) } -func (api *PrivateDebugAPIImpl) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *tracers.TraceConfig, stream *jsoniter.Stream) error { +func (api *PrivateDebugAPIImpl) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error { dbtx, err := api.db.BeginRo(ctx) if err != nil { return fmt.Errorf("create ro transaction: %v", err) @@ -380,7 +380,7 @@ func (api *PrivateDebugAPIImpl) TraceCall(ctx context.Context, args ethapi.CallA return transactions.TraceTx(ctx, transaction, msg, blockCtx, txCtx, ibs, config, chainConfig, stream, api.evmCallTimeout) } -func (api *PrivateDebugAPIImpl) TraceCallMany(ctx context.Context, bundles []Bundle, simulateContext StateContext, config *tracers.TraceConfig, stream *jsoniter.Stream) error { +func (api *PrivateDebugAPIImpl) TraceCallMany(ctx context.Context, bundles []Bundle, simulateContext StateContext, config *tracerConfig.TraceConfig, stream *jsoniter.Stream) error { var ( hash common.Hash replayTransactions types.Transactions @@ -392,7 +392,7 @@ func (api *PrivateDebugAPIImpl) TraceCallMany(ctx context.Context, bundles []Bun ) if config == nil { - config = &tracers.TraceConfig{} + config = &tracerConfig.TraceConfig{} } overrideBlockHash = make(map[uint64]common.Hash) diff --git a/turbo/node/node.go b/turbo/node/node.go index 8f0d620f147..b2230db1b31 100644 --- a/turbo/node/node.go +++ b/turbo/node/node.go @@ -115,7 +115,7 @@ func New( nodeConfig *nodecfg.Config, ethConfig *ethconfig.Config, logger log.Logger, - tracer tracers.Tracer, + tracer *tracers.Tracer, ) (*ErigonNode, error) { //prepareBuckets(optionalParams.CustomBuckets) node, err := node.New(ctx, nodeConfig, logger) diff --git a/turbo/stages/stageloop.go b/turbo/stages/stageloop.go index 32714a5c525..116fc0201e4 100644 --- a/turbo/stages/stageloop.go +++ b/turbo/stages/stageloop.go @@ -28,6 +28,7 @@ import ( "github.com/ledgerwatch/erigon/consensus/misc" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/rawdb/blockio" + "github.com/ledgerwatch/erigon/core/tracing" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/eth/ethconfig" @@ -477,8 +478,12 @@ func NewDefaultStages(ctx context.Context, recents *lru.ARCCache[libcommon.Hash, *bor.Snapshot], signatures *lru.ARCCache[libcommon.Hash, libcommon.Address], logger log.Logger, - tracer tracers.Tracer, + tracer *tracers.Tracer, ) []*stagedsync.Stage { + var tracingHooks *tracing.Hooks + if tracer != nil { + tracingHooks = tracer.Hooks + } dirs := cfg.Dirs blockWriter := blockio.NewBlockWriter(cfg.HistoryV3) @@ -523,7 +528,7 @@ func NewDefaultStages(ctx context.Context, nil, controlServer.ChainConfig, controlServer.Engine, - &vm.Config{Tracer: tracer}, + &vm.Config{Tracer: tracingHooks}, notifications.Accumulator, cfg.StateStream, /*stateStream=*/ false, @@ -559,9 +564,13 @@ func NewPipelineStages(ctx context.Context, silkworm *silkworm.Silkworm, forkValidator *engine_helpers.ForkValidator, logger log.Logger, - tracer tracers.Tracer, + tracer *tracers.Tracer, checkStateRoot bool, ) []*stagedsync.Stage { + var tracingHooks *tracing.Hooks + if tracer != nil { + tracingHooks = tracer.Hooks + } dirs := cfg.Dirs blockWriter := blockio.NewBlockWriter(cfg.HistoryV3) @@ -598,7 +607,7 @@ func NewPipelineStages(ctx context.Context, nil, controlServer.ChainConfig, controlServer.Engine, - &vm.Config{Tracer: tracer}, + &vm.Config{Tracer: tracingHooks}, notifications.Accumulator, cfg.StateStream, /*stateStream=*/ false, @@ -634,7 +643,7 @@ func NewPipelineStages(ctx context.Context, nil, controlServer.ChainConfig, controlServer.Engine, - &vm.Config{Tracer: tracer}, + &vm.Config{Tracer: tracingHooks}, notifications.Accumulator, cfg.StateStream, /*stateStream=*/ false, @@ -660,7 +669,11 @@ func NewPipelineStages(ctx context.Context, func NewInMemoryExecution(ctx context.Context, db kv.RwDB, cfg *ethconfig.Config, controlServer *sentry_multi_client.MultiClient, dirs datadir.Dirs, notifications *shards.Notifications, blockReader services.FullBlockReader, blockWriter *blockio.BlockWriter, agg *state.AggregatorV3, - silkworm *silkworm.Silkworm, logger log.Logger, tracer tracers.Tracer) *stagedsync.Sync { + silkworm *silkworm.Silkworm, logger log.Logger, tracer *tracers.Tracer) *stagedsync.Sync { + var tracingHooks *tracing.Hooks + if tracer != nil { + tracingHooks = tracer.Hooks + } return stagedsync.New( cfg.Sync, stagedsync.StateStages(ctx, @@ -675,7 +688,7 @@ func NewInMemoryExecution(ctx context.Context, db kv.RwDB, cfg *ethconfig.Config nil, controlServer.ChainConfig, controlServer.Engine, - &vm.Config{Tracer: tracer}, + &vm.Config{Tracer: tracingHooks}, notifications.Accumulator, cfg.StateStream, true, diff --git a/turbo/tracing/tracing.go b/turbo/tracing/tracing.go index 68c89f35e1b..dcd5cc93951 100644 --- a/turbo/tracing/tracing.go +++ b/turbo/tracing/tracing.go @@ -9,7 +9,7 @@ import ( // SetupTracerCtx performs the tracing setup according to the parameters // containted in the given urfave context. -func SetupTracerCtx(ctx *cli.Context) (tracers.Tracer, error) { +func SetupTracerCtx(ctx *cli.Context) (*tracers.Tracer, error) { tracerName := ctx.String(TracerFlag.Name) if tracerName == "" { return nil, nil diff --git a/turbo/transactions/tracing.go b/turbo/transactions/tracing.go index afb5d027a42..5c7bf1deba2 100644 --- a/turbo/transactions/tracing.go +++ b/turbo/transactions/tracing.go @@ -22,6 +22,7 @@ import ( "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/eth/stagedsync" "github.com/ledgerwatch/erigon/eth/tracers" + tracerConfig "github.com/ledgerwatch/erigon/eth/tracers/config" "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/erigon/turbo/rpchelper" "github.com/ledgerwatch/erigon/turbo/services" @@ -132,7 +133,7 @@ func TraceTx( blockCtx evmtypes.BlockContext, txCtx evmtypes.TxContext, ibs *state.IntraBlockState, - config *tracers.TraceConfig, + config *tracerConfig.TraceConfig, chainConfig *chain.Config, stream *jsoniter.Stream, callTimeout time.Duration, @@ -147,12 +148,12 @@ func TraceTx( execCb := func(evm *vm.EVM, refunds bool) (*core.ExecutionResult, error) { gp := new(core.GasPool).AddGas(message.Gas()).AddBlobGas(message.BlobGas()) - tracer.CaptureTxStart(evm, tx) + tracer.OnTxStart(evm.GetVMContext(), tx, message.From()) result, err := core.ApplyMessage(evm, message, gp, refunds, false /* gasBailout */) if err != nil { - tracer.CaptureTxEnd(nil, err) + tracer.OnTxEnd(nil, err) } else { - tracer.CaptureTxEnd(&types.Receipt{GasUsed: result.UsedGas}, nil) + tracer.OnTxEnd(&types.Receipt{GasUsed: result.UsedGas}, nil) } return result, err } @@ -162,11 +163,11 @@ func TraceTx( func AssembleTracer( ctx context.Context, - config *tracers.TraceConfig, + config *tracerConfig.TraceConfig, txHash libcommon.Hash, stream *jsoniter.Stream, callTimeout time.Duration, -) (tracers.Tracer, bool, context.CancelFunc, error) { +) (*tracers.Tracer, bool, context.CancelFunc, error) { // Assemble the structured logger or the JavaScript tracer switch { case config != nil && config.Tracer != nil: @@ -199,9 +200,9 @@ func AssembleTracer( return tracer, false, cancel, nil case config == nil: - return logger.NewJsonStreamLogger(nil, ctx, stream), true, func() {}, nil + return logger.NewJsonStreamLogger(nil, ctx, stream).Tracer(), true, func() {}, nil default: - return logger.NewJsonStreamLogger(config.LogConfig, ctx, stream), true, func() {}, nil + return logger.NewJsonStreamLogger(config.LogConfig, ctx, stream).Tracer(), true, func() {}, nil } } @@ -209,16 +210,16 @@ func ExecuteTraceTx( blockCtx evmtypes.BlockContext, txCtx evmtypes.TxContext, ibs *state.IntraBlockState, - config *tracers.TraceConfig, + config *tracerConfig.TraceConfig, chainConfig *chain.Config, stream *jsoniter.Stream, - tracer tracers.Tracer, + tracer *tracers.Tracer, streaming bool, execCb func(evm *vm.EVM, refunds bool) (*core.ExecutionResult, error), ) error { // Run the transaction with tracing enabled. - evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer}) - ibs.SetLogger(tracer) + evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer.Hooks}) + ibs.SetLogger(tracer.Hooks) var refunds = true if config != nil && config.NoRefunds != nil && *config.NoRefunds { refunds = false @@ -260,7 +261,7 @@ func ExecuteTraceTx( stream.WriteString(returnVal) stream.WriteObjectEnd() } else { - r, err := tracer.(tracers.Tracer).GetResult() + r, err := tracer.GetResult() if err != nil { stream.WriteNil() return err