mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
eth/tracers/native: Add custom opcode tracer
This commit is contained in:
parent
e31709db65
commit
6a545dbaae
3 changed files with 361 additions and 0 deletions
72
eth/tracers/native/gen_callframe_enter.go
Normal file
72
eth/tracers/native/gen_callframe_enter.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
var _ = (*callFrameEnterMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (c CallFrameEnter) MarshalJSON() ([]byte, error) {
|
||||
type CallFrameEnter struct {
|
||||
Op vm.OpCode `json:"op"`
|
||||
From common.Address `json:"from"`
|
||||
To common.Address `json:"to"`
|
||||
Input hexutil.Bytes `json:"input"`
|
||||
Gas math.HexOrDecimal64 `json:"gas"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
OpName string `json:"opName,omitempty"`
|
||||
}
|
||||
var enc CallFrameEnter
|
||||
enc.Op = c.Op
|
||||
enc.From = c.From
|
||||
enc.To = c.To
|
||||
enc.Input = c.Input
|
||||
enc.Gas = math.HexOrDecimal64(c.Gas)
|
||||
enc.Value = (*hexutil.Big)(c.Value)
|
||||
enc.OpName = c.OpName()
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (c *CallFrameEnter) UnmarshalJSON(input []byte) error {
|
||||
type CallFrameEnter struct {
|
||||
Op *vm.OpCode `json:"op"`
|
||||
From *common.Address `json:"from"`
|
||||
To *common.Address `json:"to"`
|
||||
Input *hexutil.Bytes `json:"input"`
|
||||
Gas *math.HexOrDecimal64 `json:"gas"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
}
|
||||
var dec CallFrameEnter
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Op != nil {
|
||||
c.Op = *dec.Op
|
||||
}
|
||||
if dec.From != nil {
|
||||
c.From = *dec.From
|
||||
}
|
||||
if dec.To != nil {
|
||||
c.To = *dec.To
|
||||
}
|
||||
if dec.Input != nil {
|
||||
c.Input = *dec.Input
|
||||
}
|
||||
if dec.Gas != nil {
|
||||
c.Gas = uint64(*dec.Gas)
|
||||
}
|
||||
if dec.Value != nil {
|
||||
c.Value = (*big.Int)(dec.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
62
eth/tracers/native/gen_callframe_exit.go
Normal file
62
eth/tracers/native/gen_callframe_exit.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
)
|
||||
|
||||
var _ = (*callFrameExitMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (c CallFrameExit) MarshalJSON() ([]byte, error) {
|
||||
type CallFrameExit struct {
|
||||
From common.Address `json:"from"`
|
||||
To common.Address `json:"to"`
|
||||
Output hexutil.Bytes `json:"output"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
var enc CallFrameExit
|
||||
enc.From = c.From
|
||||
enc.To = c.To
|
||||
enc.Output = c.Output
|
||||
enc.GasUsed = math.HexOrDecimal64(c.GasUsed)
|
||||
enc.Error = c.Error
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (c *CallFrameExit) UnmarshalJSON(input []byte) error {
|
||||
type CallFrameExit struct {
|
||||
From *common.Address `json:"from"`
|
||||
To *common.Address `json:"to"`
|
||||
Output *hexutil.Bytes `json:"output"`
|
||||
GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
var dec CallFrameExit
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.From != nil {
|
||||
c.From = *dec.From
|
||||
}
|
||||
if dec.To != nil {
|
||||
c.To = *dec.To
|
||||
}
|
||||
if dec.Output != nil {
|
||||
c.Output = *dec.Output
|
||||
}
|
||||
if dec.GasUsed != nil {
|
||||
c.GasUsed = uint64(*dec.GasUsed)
|
||||
}
|
||||
if dec.Error != nil {
|
||||
c.Error = dec.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
227
eth/tracers/native/opcode.go
Normal file
227
eth/tracers/native/opcode.go
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tracers.DefaultDirectory.Register("opcodeTracer", newOpcodeTracer, false)
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type CallFrameEnter -field-override callFrameEnterMarshaling -out gen_callframe_enter.go
|
||||
|
||||
// CallFrameEnter is emitted every call frame entered.
|
||||
type CallFrameEnter struct {
|
||||
Op vm.OpCode `json:"op"`
|
||||
From common.Address `json:"from"`
|
||||
To common.Address `json:"to"`
|
||||
Input []byte `json:"input"`
|
||||
Gas uint64 `json:"gas"`
|
||||
Value *big.Int `json:"value"`
|
||||
}
|
||||
|
||||
// overrides for gencodec
|
||||
type callFrameEnterMarshaling struct {
|
||||
Input hexutil.Bytes
|
||||
Gas math.HexOrDecimal64
|
||||
Value *hexutil.Big
|
||||
OpName string `json:"opName,omitempty"` // adds call to OpName() in MarshalJSON
|
||||
}
|
||||
|
||||
// OpName formats the operand name in a human-readable format.
|
||||
func (c *CallFrameEnter) OpName() string {
|
||||
return c.Op.String()
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type CallFrameExit -field-override callFrameExitMarshaling -out gen_callframe_exit.go
|
||||
|
||||
// CallFrameExit is emitted every call frame exits.
|
||||
type CallFrameExit struct {
|
||||
From common.Address `json:"from"`
|
||||
To common.Address `json:"to"`
|
||||
Output []byte `json:"output"`
|
||||
GasUsed uint64 `json:"gasUsed"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// overrides for gencodec
|
||||
type callFrameExitMarshaling struct {
|
||||
Output hexutil.Bytes
|
||||
GasUsed math.HexOrDecimal64
|
||||
}
|
||||
|
||||
type opcodeTracer struct {
|
||||
noopTracer
|
||||
traces []json.RawMessage
|
||||
callStack []common.Address
|
||||
cfg opcodeTracerConfig
|
||||
env *vm.EVM
|
||||
}
|
||||
|
||||
type opcodeTracerConfig struct {
|
||||
EnableMemory bool // enable memory capture
|
||||
DisableStack bool // disable stack capture
|
||||
EnableReturnData bool // enable return data capture
|
||||
EnableCallFrames bool // enable call frame enter capture
|
||||
}
|
||||
|
||||
// newCallTracer returns a native go tracer which tracks
|
||||
// call frames of a tx, and implements vm.EVMLogger.
|
||||
func newOpcodeTracer(ctx *tracers.Context, config json.RawMessage) (tracers.Tracer, error) {
|
||||
var cfg opcodeTracerConfig
|
||||
if config != nil {
|
||||
if err := json.Unmarshal(config, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// First callframe contains tx context info
|
||||
// and is populated on start and end.
|
||||
return &opcodeTracer{traces: make([]json.RawMessage, 0), callStack: make([]common.Address, 0), cfg: cfg}, nil
|
||||
}
|
||||
|
||||
func (t *opcodeTracer) appendTrace(item interface{}) {
|
||||
data, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.traces = append(t.traces, data)
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (t *opcodeTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
t.env = env
|
||||
t.callStack = append(t.callStack, from, to)
|
||||
if t.cfg.EnableCallFrames {
|
||||
op := vm.CALL
|
||||
if create {
|
||||
op = vm.CREATE
|
||||
}
|
||||
t.appendTrace(CallFrameEnter{
|
||||
Op: op,
|
||||
From: from,
|
||||
To: to,
|
||||
Gas: gas,
|
||||
Value: value,
|
||||
Input: input,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *opcodeTracer) CaptureFault(pc uint64, op vm.OpCode, gas uint64, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
// TODO: Add rData to this interface as well
|
||||
t.CaptureState(pc, op, gas, cost, scope, nil, depth, err)
|
||||
}
|
||||
|
||||
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
|
||||
func (t *opcodeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
memory := scope.Memory
|
||||
stack := scope.Stack
|
||||
|
||||
log := logger.StructLog{
|
||||
Pc: pc,
|
||||
Op: op,
|
||||
Gas: gas,
|
||||
GasCost: cost,
|
||||
MemorySize: memory.Len(),
|
||||
Depth: depth,
|
||||
RefundCounter: t.env.StateDB.GetRefund(),
|
||||
Err: err,
|
||||
}
|
||||
if t.cfg.EnableMemory {
|
||||
log.Memory = memory.Data()
|
||||
}
|
||||
if !t.cfg.DisableStack {
|
||||
log.Stack = stack.Data()
|
||||
}
|
||||
if t.cfg.EnableReturnData {
|
||||
log.ReturnData = rData
|
||||
}
|
||||
t.appendTrace(log)
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (t *opcodeTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
if t.cfg.EnableCallFrames {
|
||||
var errString *string
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
errString = &errStr
|
||||
}
|
||||
t.appendTrace(CallFrameExit{
|
||||
From: t.callStack[len(t.callStack)-1],
|
||||
To: t.callStack[len(t.callStack)-2],
|
||||
Output: output,
|
||||
GasUsed: gasUsed,
|
||||
Error: errString,
|
||||
})
|
||||
}
|
||||
t.callStack = t.callStack[:len(t.callStack)-2]
|
||||
}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (t *opcodeTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
t.callStack = append(t.callStack, to)
|
||||
if t.cfg.EnableCallFrames {
|
||||
t.appendTrace(CallFrameEnter{
|
||||
Op: typ,
|
||||
From: from,
|
||||
To: to,
|
||||
Gas: gas,
|
||||
Value: value,
|
||||
Input: input,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureExit is called when EVM exits a scope.
|
||||
func (t *opcodeTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
if t.cfg.EnableCallFrames {
|
||||
var errString *string
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
errString = &errStr
|
||||
}
|
||||
t.appendTrace(CallFrameExit{
|
||||
From: t.callStack[len(t.callStack)-1],
|
||||
To: t.callStack[len(t.callStack)-2],
|
||||
Output: output,
|
||||
GasUsed: gasUsed,
|
||||
Error: errString,
|
||||
})
|
||||
}
|
||||
t.callStack = t.callStack[:len(t.callStack)-1]
|
||||
}
|
||||
|
||||
// GetResult returns the json-encoded nested list of call traces, and any
|
||||
// error arising from the encoding or forceful termination (via `Stop`).
|
||||
func (t *opcodeTracer) GetResult() (json.RawMessage, error) {
|
||||
res, err := json.Marshal(t.traces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.RawMessage(res), nil
|
||||
}
|
||||
Loading…
Reference in a new issue