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