Merge branch 'extended-tracer-backport-1.13.5' into extended-tracer-sei

This commit is contained in:
Matthieu Vachon 2024-03-18 16:33:37 -04:00
commit 9beae74e50
17 changed files with 205 additions and 34 deletions

View file

@ -60,7 +60,7 @@ func blockTestCmd(ctx *cli.Context) error {
DisableStack: ctx.Bool(DisableStackFlag.Name), DisableStack: ctx.Bool(DisableStackFlag.Name),
DisableStorage: ctx.Bool(DisableStorageFlag.Name), DisableStorage: ctx.Bool(DisableStorageFlag.Name),
EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
}, os.Stderr).Hooks() }, os.Stderr)
} }
// Load the test content from the input file // Load the test content from the input file
src, err := os.ReadFile(ctx.Args().First()) src, err := os.ReadFile(ctx.Args().First())

View file

@ -228,7 +228,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
if vmConfig.Tracer != nil { if tracer != nil {
vmConfig.Tracer = tracer.Hooks vmConfig.Tracer = tracer.Hooks
} }
statedb.SetTxContext(tx.Hash(), txIndex) statedb.SetTxContext(tx.Hash(), txIndex)
@ -240,7 +240,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
) )
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
if tracer != nil { if tracer != nil && tracer.OnTxStart != nil {
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
} }
// (ret []byte, usedGas uint64, failed bool, err error) // (ret []byte, usedGas uint64, failed bool, err error)
@ -251,7 +251,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
gaspool.SetGas(prevGas) gaspool.SetGas(prevGas)
if tracer != nil { if tracer != nil {
tracer.OnTxEnd(nil, err) if tracer.OnTxEnd != nil {
tracer.OnTxEnd(nil, err)
}
if err := writeTraceResult(tracer, traceOutput); err != nil { if err := writeTraceResult(tracer, traceOutput); err != nil {
log.Warn("Error writing tracer output", "err", err) log.Warn("Error writing tracer output", "err", err)
} }
@ -298,7 +300,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
receipt.TransactionIndex = uint(txIndex) receipt.TransactionIndex = uint(txIndex)
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
if tracer != nil { if tracer != nil {
tracer.Hooks.OnTxEnd(receipt, nil) if tracer.Hooks.OnTxEnd != nil {
tracer.Hooks.OnTxEnd(receipt, nil)
}
writeTraceResult(tracer, traceOutput) writeTraceResult(tracer, traceOutput)
} }
} }
@ -418,7 +422,7 @@ func calcDifficulty(config *params.ChainConfig, number, currentTime, parentTime
func writeTraceResult(tracer *directory.Tracer, f io.WriteCloser) error { func writeTraceResult(tracer *directory.Tracer, f io.WriteCloser) error {
defer f.Close() defer f.Close()
result, err := tracer.GetResult() result, err := tracer.GetResult()
if err != nil { if err != nil || result == nil {
return err return err
} }
err = json.NewEncoder(f).Encode(result) err = json.NewEncoder(f).Encode(result)

View file

@ -126,10 +126,10 @@ func Transition(ctx *cli.Context) error {
if err != nil { if err != nil {
return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
} }
logger := logger.NewJSONLogger(logConfig, traceFile).Hooks() logger := logger.NewJSONLogger(logConfig, traceFile)
tracer := &directory.Tracer{ tracer := &directory.Tracer{
Hooks: logger, Hooks: logger,
// JSONLogger streams out result to file. // jsonLogger streams out result to file.
GetResult: func() (json.RawMessage, error) { return nil, nil }, GetResult: func() (json.RawMessage, error) { return nil, nil },
Stop: func(err error) {}, Stop: func(err error) {},
} }

View file

@ -128,7 +128,7 @@ func runCmd(ctx *cli.Context) error {
blobBaseFee = new(big.Int) // TODO (MariusVanDerWijden) implement blob fee in state tests blobBaseFee = new(big.Int) // TODO (MariusVanDerWijden) implement blob fee in state tests
) )
if ctx.Bool(MachineFlag.Name) { if ctx.Bool(MachineFlag.Name) {
tracer = logger.NewJSONLogger(logconfig, os.Stdout).Hooks() tracer = logger.NewJSONLogger(logconfig, os.Stdout)
} else if ctx.Bool(DebugFlag.Name) { } else if ctx.Bool(DebugFlag.Name) {
debugLogger = logger.NewStructLogger(logconfig) debugLogger = logger.NewStructLogger(logconfig)
tracer = debugLogger.Hooks() tracer = debugLogger.Hooks()

View file

@ -61,7 +61,7 @@ func stateTestCmd(ctx *cli.Context) error {
var cfg vm.Config var cfg vm.Config
switch { switch {
case ctx.Bool(MachineFlag.Name): case ctx.Bool(MachineFlag.Name):
cfg.Tracer = logger.NewJSONLogger(config, os.Stderr).Hooks() cfg.Tracer = logger.NewJSONLogger(config, os.Stderr)
case ctx.Bool(DebugFlag.Name): case ctx.Bool(DebugFlag.Name):
cfg.Tracer = logger.NewStructLogger(config).Hooks() cfg.Tracer = logger.NewStructLogger(config).Hooks()

View file

@ -17,9 +17,12 @@
package main package main
import ( import (
"bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -321,6 +324,107 @@ func TestT8n(t *testing.T) {
} }
} }
func lineIterator(path string) func() (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return func() (string, error) { return err.Error(), err }
}
scanner := bufio.NewScanner(strings.NewReader(string(data)))
return func() (string, error) {
if scanner.Scan() {
return scanner.Text(), nil
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", io.EOF // scanner gobbles io.EOF, but we want it
}
}
// TestT8nTracing is a test that checks the tracing-output from t8n.
func TestT8nTracing(t *testing.T) {
t.Parallel()
tt := new(testT8n)
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
for i, tc := range []struct {
base string
input t8nInput
expExitCode int
extraArgs []string
expectedTraces []string
}{
{
base: "./testdata/31",
input: t8nInput{
"alloc.json", "txs.json", "env.json", "Cancun", "",
},
extraArgs: []string{"--trace"},
expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl"},
},
{
base: "./testdata/31",
input: t8nInput{
"alloc.json", "txs.json", "env.json", "Cancun", "",
},
extraArgs: []string{"--trace.tracer", `
{
result: function(){
return "hello world"
},
fault: function(){}
}`},
expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json"},
},
} {
args := []string{"t8n"}
args = append(args, tc.input.get(tc.base)...)
// Place the output somewhere we can find it
outdir := t.TempDir()
args = append(args, "--output.basedir", outdir)
args = append(args, tc.extraArgs...)
var qArgs []string // quoted args for debugging purposes
for _, arg := range args {
if len(arg) == 0 {
qArgs = append(qArgs, `""`)
} else {
qArgs = append(qArgs, arg)
}
}
tt.Logf("args: %v\n", strings.Join(qArgs, " "))
tt.Run("evm-test", args...)
t.Log(string(tt.Output()))
// Compare the expected traces
for _, traceFile := range tc.expectedTraces {
haveFn := lineIterator(filepath.Join(outdir, traceFile))
wantFn := lineIterator(filepath.Join(tc.base, traceFile))
for line := 0; ; line++ {
want, wErr := wantFn()
have, hErr := haveFn()
if want != have {
t.Fatalf("test %d, trace %v, line %d\nwant: %v\nhave: %v\n",
i, traceFile, line, want, have)
}
if wErr != nil && hErr != nil {
break
}
if wErr != nil {
t.Fatal(wErr)
}
if hErr != nil {
t.Fatal(hErr)
}
t.Logf("%v\n", want)
}
}
if have, want := tt.ExitStatus(), tc.expExitCode; have != want {
t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want)
}
}
}
type t9nInput struct { type t9nInput struct {
inTxs string inTxs string
stFork string stFork string

1
cmd/evm/testdata/31/README.md vendored Normal file
View file

@ -0,0 +1 @@
This test does some EVM execution, and can be used to test the tracers and trace-outputs.

16
cmd/evm/testdata/31/alloc.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
"balance" : "0x016345785d8a0000",
"code" : "0x",
"nonce" : "0x00",
"storage" : {
}
},
"0x1111111111111111111111111111111111111111" : {
"balance" : "0x1",
"code" : "0x604060406040604000",
"nonce" : "0x00",
"storage" : {
}
}
}

20
cmd/evm/testdata/31/env.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
"currentNumber" : "0x01",
"currentTimestamp" : "0x03e8",
"currentGasLimit" : "0x1000000000",
"previousHash" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da",
"currentDataGasUsed" : "0x2000",
"parentTimestamp" : "0x00",
"parentDifficulty" : "0x00",
"parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"parentBeaconBlockRoot" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"currentRandom" : "0x0000000000000000000000000000000000000000000000000000000000020000",
"withdrawals" : [
],
"parentBaseFee" : "0x08",
"parentGasUsed" : "0x00",
"parentGasLimit" : "0x1000000000",
"parentExcessBlobGas" : "0x1000",
"parentBlobGasUsed" : "0x2000"
}

View file

@ -0,0 +1,6 @@
{"pc":0,"op":96,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":96,"gas":"0x13495","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":4,"op":96,"gas":"0x13492","gasCost":"0x3","memSize":0,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":6,"op":96,"gas":"0x1348f","gasCost":"0x3","memSize":0,"stack":["0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":8,"op":0,"gas":"0x1348c","gasCost":"0x0","memSize":0,"stack":["0x40","0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0xc"}

14
cmd/evm/testdata/31/txs.json vendored Normal file
View file

@ -0,0 +1,14 @@
[
{
"gas": "0x186a0",
"gasPrice": "0x600",
"input": "0x",
"nonce": "0x0",
"to": "0x1111111111111111111111111111111111111111",
"value": "0x1",
"v" : "0x0",
"r" : "0x0",
"s" : "0x0",
"secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
}
]

View file

@ -3248,7 +3248,7 @@ func testDeleteRecreateSlots(t *testing.T, scheme string) {
}) })
// Import the canonical chain // Import the canonical chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{
Tracer: logger.NewJSONLogger(nil, os.Stdout).Hooks(), Tracer: logger.NewJSONLogger(nil, os.Stdout),
}, nil, nil) }, nil, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
@ -3330,7 +3330,7 @@ func testDeleteRecreateAccount(t *testing.T, scheme string) {
}) })
// Import the canonical chain // Import the canonical chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{
Tracer: logger.NewJSONLogger(nil, os.Stdout).Hooks(), Tracer: logger.NewJSONLogger(nil, os.Stdout),
}, nil, nil) }, nil, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)

View file

@ -43,6 +43,8 @@ type ScopeContext struct {
Contract *Contract Contract *Contract
} }
// MemoryData returns the underlying memory slice. Callers must not modify the contents
// of the returned data.
func (ctx *ScopeContext) MemoryData() []byte { func (ctx *ScopeContext) MemoryData() []byte {
if ctx.Memory == nil { if ctx.Memory == nil {
return nil return nil
@ -50,6 +52,8 @@ func (ctx *ScopeContext) MemoryData() []byte {
return ctx.Memory.Data() 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 { func (ctx *ScopeContext) StackData() []uint256.Int {
if ctx.Stack == nil { if ctx.Stack == nil {
return nil return nil
@ -57,18 +61,23 @@ func (ctx *ScopeContext) StackData() []uint256.Int {
return ctx.Stack.Data() return ctx.Stack.Data()
} }
// Caller returns the current caller.
func (ctx *ScopeContext) Caller() common.Address { func (ctx *ScopeContext) Caller() common.Address {
return ctx.Contract.Caller() return ctx.Contract.Caller()
} }
// Address returns the address where this scope of execution is taking place.
func (ctx *ScopeContext) Address() common.Address { func (ctx *ScopeContext) Address() common.Address {
return ctx.Contract.Address() return ctx.Contract.Address()
} }
// CallValue returns the value supplied with this call.
func (ctx *ScopeContext) CallValue() *big.Int { func (ctx *ScopeContext) CallValue() *big.Int {
return ctx.Contract.Value() 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 { func (ctx *ScopeContext) CallInput() []byte {
return ctx.Contract.Input return ctx.Contract.Input
} }
@ -189,15 +198,15 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
contract.Input = input contract.Input = input
if debug { if debug {
defer func() { defer func() { // this deferred method handles exit-with-error
if err != nil { if err == nil {
if !logged { return
if in.evm.Config.Tracer.OnOpcode != nil { }
in.evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.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.evm.depth, VMErrorFromErr(err))
} else if in.evm.Config.Tracer.OnFault != nil { }
in.evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err)) if logged && in.evm.Config.Tracer.OnFault != nil {
} in.evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err))
} }
}() }()
} }

View file

@ -783,7 +783,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// Swap out the noop logger to the standard tracer // Swap out the noop logger to the standard tracer
writer = bufio.NewWriter(dump) writer = bufio.NewWriter(dump)
vmConf = vm.Config{ vmConf = vm.Config{
Tracer: logger.NewJSONLogger(&logConfig, writer).Hooks(), Tracer: logger.NewJSONLogger(&logConfig, writer),
EnablePreimageRecording: true, EnablePreimageRecording: true,
} }
} }

View file

@ -27,7 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
) )
type JSONLogger struct { type jsonLogger struct {
encoder *json.Encoder encoder *json.Encoder
cfg *Config cfg *Config
env *tracing.VMContext env *tracing.VMContext
@ -35,15 +35,11 @@ type JSONLogger struct {
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects // NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
// into the provided stream. // into the provided stream.
func NewJSONLogger(cfg *Config, writer io.Writer) *JSONLogger { func NewJSONLogger(cfg *Config, writer io.Writer) *tracing.Hooks {
l := &JSONLogger{encoder: json.NewEncoder(writer), cfg: cfg} l := &jsonLogger{encoder: json.NewEncoder(writer), cfg: cfg}
if l.cfg == nil { if l.cfg == nil {
l.cfg = &Config{} l.cfg = &Config{}
} }
return l
}
func (l *JSONLogger) Hooks() *tracing.Hooks {
return &tracing.Hooks{ return &tracing.Hooks{
OnTxStart: l.OnTxStart, OnTxStart: l.OnTxStart,
OnExit: l.OnExit, OnExit: l.OnExit,
@ -52,12 +48,12 @@ func (l *JSONLogger) Hooks() *tracing.Hooks {
} }
} }
func (l *JSONLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, depth int, err error) { func (l *jsonLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, depth int, err error) {
// TODO: Add rData to this interface as well // TODO: Add rData to this interface as well
l.OnOpcode(pc, op, gas, cost, scope, nil, depth, err) l.OnOpcode(pc, op, gas, cost, scope, nil, depth, err)
} }
func (l *JSONLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
memory := scope.MemoryData() memory := scope.MemoryData()
stack := scope.StackData() stack := scope.StackData()
@ -83,7 +79,7 @@ func (l *JSONLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracin
l.encoder.Encode(log) l.encoder.Encode(log)
} }
func (l *JSONLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if depth > 0 { if depth > 0 {
return return
} }
@ -99,6 +95,6 @@ func (l *JSONLogger) OnExit(depth int, output []byte, gasUsed uint64, err error,
l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg}) l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg})
} }
func (l *JSONLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { func (l *jsonLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
l.env = env l.env = env
} }

View file

@ -157,7 +157,7 @@ func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) {
} }
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
w := bufio.NewWriter(buf) w := bufio.NewWriter(buf)
config.Tracer = logger.NewJSONLogger(&logger.Config{}, w).Hooks() config.Tracer = logger.NewJSONLogger(&logger.Config{}, w)
err2 := test(config) err2 := test(config)
if !reflect.DeepEqual(err, err2) { if !reflect.DeepEqual(err, err2) {
t.Errorf("different error for second run: %v", err2) t.Errorf("different error for second run: %v", err2)