Unverified Commit 0fda25e4 authored by Martin Holst Swende's avatar Martin Holst Swende Committed by GitHub

eth/tracers, core: use scopecontext in tracers, provide statedb in capturestart (#22333)

Fixes the CaptureStart api to include the EVM, thus being able to set the statedb early on. This pr also exposes the struct we used internally in the interpreter to encapsulate the contract, mem, stack, rstack, so we pass it as a single struct to the tracer, and removes the error returns on the capture methods. 
parent c5df05b9
...@@ -76,9 +76,9 @@ func enable1884(jt *JumpTable) { ...@@ -76,9 +76,9 @@ func enable1884(jt *JumpTable) {
} }
} }
func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(callContext.contract.Address())) balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(scope.Contract.Address()))
callContext.stack.push(balance) scope.Stack.push(balance)
return nil, nil return nil, nil
} }
...@@ -95,9 +95,9 @@ func enable1344(jt *JumpTable) { ...@@ -95,9 +95,9 @@ func enable1344(jt *JumpTable) {
} }
// opChainID implements CHAINID opcode // opChainID implements CHAINID opcode
func opChainID(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { func opChainID(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID) chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID)
callContext.stack.push(chainId) scope.Stack.push(chainId)
return nil, nil return nil, nil
} }
......
...@@ -239,7 +239,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas ...@@ -239,7 +239,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil) evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
} }
return nil, gas, nil return nil, gas, nil
...@@ -250,7 +250,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas ...@@ -250,7 +250,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// Capture the tracer start/end events in debug mode // Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now()) }(gas, time.Now())
...@@ -472,7 +472,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ...@@ -472,7 +472,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value) evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
} }
start := time.Now() start := time.Now()
......
This diff is collapsed.
...@@ -104,7 +104,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu ...@@ -104,7 +104,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
stack.push(x) stack.push(x)
stack.push(y) stack.push(y)
opFn(&pc, evmInterpreter, &callCtx{nil, stack, nil}) opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 { if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
} }
...@@ -219,7 +219,7 @@ func TestAddMod(t *testing.T) { ...@@ -219,7 +219,7 @@ func TestAddMod(t *testing.T) {
stack.push(z) stack.push(z)
stack.push(y) stack.push(y)
stack.push(x) stack.push(x)
opAddmod(&pc, evmInterpreter, &callCtx{nil, stack, nil}) opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
actual := stack.pop() actual := stack.pop()
if actual.Cmp(expected) != 0 { if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual)
...@@ -241,7 +241,7 @@ func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcas ...@@ -241,7 +241,7 @@ func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcas
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
stack.push(x) stack.push(x)
stack.push(y) stack.push(y)
opFn(&pc, interpreter, &callCtx{nil, stack, nil}) opFn(&pc, interpreter, &ScopeContext{nil, stack, nil})
actual := stack.pop() actual := stack.pop()
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
} }
...@@ -299,7 +299,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { ...@@ -299,7 +299,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
a.SetBytes(arg) a.SetBytes(arg)
stack.push(a) stack.push(a)
} }
op(&pc, evmInterpreter, &callCtx{nil, stack, nil}) op(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
stack.pop() stack.pop()
} }
} }
...@@ -525,12 +525,12 @@ func TestOpMstore(t *testing.T) { ...@@ -525,12 +525,12 @@ func TestOpMstore(t *testing.T) {
pc := uint64(0) pc := uint64(0)
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
stack.pushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int)) stack.pushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int))
opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
t.Fatalf("Mstore fail, got %v, expected %v", got, v) t.Fatalf("Mstore fail, got %v, expected %v", got, v)
} }
stack.pushN(*new(uint256.Int).SetUint64(0x1), *new(uint256.Int)) stack.pushN(*new(uint256.Int).SetUint64(0x1), *new(uint256.Int))
opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
t.Fatalf("Mstore failed to overwrite previous value") t.Fatalf("Mstore failed to overwrite previous value")
} }
...@@ -553,7 +553,7 @@ func BenchmarkOpMstore(bench *testing.B) { ...@@ -553,7 +553,7 @@ func BenchmarkOpMstore(bench *testing.B) {
bench.ResetTimer() bench.ResetTimer()
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
stack.pushN(*value, *memStart) stack.pushN(*value, *memStart)
opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
} }
} }
...@@ -572,7 +572,7 @@ func BenchmarkOpSHA3(bench *testing.B) { ...@@ -572,7 +572,7 @@ func BenchmarkOpSHA3(bench *testing.B) {
bench.ResetTimer() bench.ResetTimer()
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
stack.pushN(*uint256.NewInt().SetUint64(32), *start) stack.pushN(*uint256.NewInt().SetUint64(32), *start)
opSha3(&pc, evmInterpreter, &callCtx{mem, stack, nil}) opSha3(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
} }
} }
......
...@@ -62,12 +62,12 @@ type Interpreter interface { ...@@ -62,12 +62,12 @@ type Interpreter interface {
CanRun([]byte) bool CanRun([]byte) bool
} }
// callCtx contains the things that are per-call, such as stack and memory, // ScopeContext contains the things that are per-call, such as stack and memory,
// but not transients like pc and gas // but not transients like pc and gas
type callCtx struct { type ScopeContext struct {
memory *Memory Memory *Memory
stack *Stack Stack *Stack
contract *Contract Contract *Contract
} }
// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
...@@ -163,10 +163,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( ...@@ -163,10 +163,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
op OpCode // current opcode op OpCode // current opcode
mem = NewMemory() // bound memory mem = NewMemory() // bound memory
stack = newstack() // local stack stack = newstack() // local stack
callContext = &callCtx{ callContext = &ScopeContext{
memory: mem, Memory: mem,
stack: stack, Stack: stack,
contract: contract, Contract: contract,
} }
// For optimisation reason we're using uint64 as the program counter. // For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC // It's theoretically possible to go above 2^64. The YP defines the PC
...@@ -191,9 +191,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( ...@@ -191,9 +191,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
defer func() { defer func() {
if err != nil { if err != nil {
if !logged { if !logged {
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, in.returnData, contract, in.evm.depth, err) in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
} else { } else {
in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
} }
} }
}() }()
...@@ -275,7 +275,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( ...@@ -275,7 +275,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
} }
if in.cfg.Debug { if in.cfg.Debug {
in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, in.returnData, contract, in.evm.depth, err) in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true logged = true
} }
......
...@@ -21,7 +21,7 @@ import ( ...@@ -21,7 +21,7 @@ import (
) )
type ( type (
executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error)
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64
// memorySizeFunc returns the required size, and whether the operation overflowed a uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64
memorySizeFunc func(*Stack) (size uint64, overflow bool) memorySizeFunc func(*Stack) (size uint64, overflow bool)
......
...@@ -18,7 +18,6 @@ package vm ...@@ -18,7 +18,6 @@ package vm
import ( import (
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
...@@ -32,8 +31,6 @@ import ( ...@@ -32,8 +31,6 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
var errTraceLimitReached = errors.New("the number of logs reached the specified limit")
// Storage represents a contract's storage. // Storage represents a contract's storage.
type Storage map[common.Hash]common.Hash type Storage map[common.Hash]common.Hash
...@@ -107,10 +104,10 @@ func (s *StructLog) ErrorString() string { ...@@ -107,10 +104,10 @@ func (s *StructLog) ErrorString() string {
// Note that reference types are actual VM data structures; make copies // Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call. // if you need to retain them beyond the current call.
type Tracer interface { type Tracer interface {
CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
} }
// StructLogger is an EVM state logger and implements Tracer. // StructLogger is an EVM state logger and implements Tracer.
...@@ -139,17 +136,19 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { ...@@ -139,17 +136,19 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
} }
// CaptureStart implements the Tracer interface to initialize the tracing operation. // CaptureStart implements the Tracer interface to initialize the tracing operation.
func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
return nil
} }
// CaptureState logs a new structured log message and pushes it out to the environment // CaptureState logs a new structured log message and pushes it out to the environment
// //
// CaptureState also tracks SLOAD/SSTORE ops to track storage change. // CaptureState also tracks SLOAD/SSTORE ops to track storage change.
func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error { func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
memory := scope.Memory
stack := scope.Stack
contract := scope.Contract
// check if already accumulated the specified number of logs // check if already accumulated the specified number of logs
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
return errTraceLimitReached return
} }
// Copy a snapshot of the current memory state to a new buffer // Copy a snapshot of the current memory state to a new buffer
var mem []byte var mem []byte
...@@ -199,17 +198,15 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui ...@@ -199,17 +198,15 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
// create a new snapshot of the EVM. // create a new snapshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, env.StateDB.GetRefund(), err} log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log) l.logs = append(l.logs, log)
return nil
} }
// CaptureFault implements the Tracer interface to trace an execution fault // CaptureFault implements the Tracer interface to trace an execution fault
// while running an opcode. // while running an opcode.
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
return nil
} }
// CaptureEnd is called after the call finishes to finalize the tracing. // CaptureEnd is called after the call finishes to finalize the tracing.
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
l.output = output l.output = output
l.err = err l.err = err
if l.cfg.Debug { if l.cfg.Debug {
...@@ -218,7 +215,6 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration ...@@ -218,7 +215,6 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration
fmt.Printf(" error: %v\n", err) fmt.Printf(" error: %v\n", err)
} }
} }
return nil
} }
// StructLogs returns the captured log entries. // StructLogs returns the captured log entries.
...@@ -292,7 +288,7 @@ func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger { ...@@ -292,7 +288,7 @@ func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger {
return l return l
} }
func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
if !create { if !create {
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
from.String(), to.String(), from.String(), to.String(),
...@@ -307,10 +303,11 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b ...@@ -307,10 +303,11 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b
| Pc | Op | Cost | Stack | RStack | Refund | | Pc | Op | Cost | Stack | RStack | Refund |
|-------|-------------|------|-----------|-----------|---------| |-------|-------------|------|-----------|-----------|---------|
`) `)
return nil
} }
func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error { // CaptureState also tracks SLOAD/SSTORE ops to track storage change.
func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
stack := scope.Stack
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
if !t.cfg.DisableStack { if !t.cfg.DisableStack {
...@@ -327,18 +324,13 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64 ...@@ -327,18 +324,13 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64
if err != nil { if err != nil {
fmt.Fprintf(t.out, "Error: %v\n", err) fmt.Fprintf(t.out, "Error: %v\n", err)
} }
return nil
} }
func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
return nil
} }
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error { func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) {
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err) output, gasUsed, err)
return nil
} }
...@@ -41,12 +41,16 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger { ...@@ -41,12 +41,16 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
return l return l
} }
func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { func (l *JSONLogger) CaptureStart(env *EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
return nil
} }
func (l *JSONLogger) CaptureFault(*EVM, uint64, OpCode, uint64, uint64, *ScopeContext, int, error) {}
// CaptureState outputs state information on the logger. // CaptureState outputs state information on the logger.
func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error { func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
memory := scope.Memory
stack := scope.Stack
log := StructLog{ log := StructLog{
Pc: pc, Pc: pc,
Op: op, Op: op,
...@@ -72,16 +76,11 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint ...@@ -72,16 +76,11 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint
if !l.cfg.DisableReturnData { if !l.cfg.DisableReturnData {
log.ReturnData = rData log.ReturnData = rData
} }
return l.encoder.Encode(log) l.encoder.Encode(log)
}
// CaptureFault outputs state information on the logger.
func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
return nil
} }
// CaptureEnd is triggered at end of execution. // CaptureEnd is triggered at end of execution.
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
type endLog struct { type endLog struct {
Output string `json:"output"` Output string `json:"output"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`
...@@ -89,7 +88,7 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, ...@@ -89,7 +88,7 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration,
Err string `json:"error,omitempty"` Err string `json:"error,omitempty"`
} }
if err != nil { if err != nil {
return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, err.Error()}) l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, err.Error()})
} }
return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, ""}) l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, ""})
} }
...@@ -53,16 +53,20 @@ func TestStoreCapture(t *testing.T) { ...@@ -53,16 +53,20 @@ func TestStoreCapture(t *testing.T) {
var ( var (
env = NewEVM(BlockContext{}, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{})
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
mem = NewMemory()
stack = newstack()
contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0) contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0)
scope = &ScopeContext{
Memory: NewMemory(),
Stack: newstack(),
Contract: contract,
}
) )
stack.push(uint256.NewInt().SetUint64(1)) scope.Stack.push(uint256.NewInt().SetUint64(1))
stack.push(uint256.NewInt()) scope.Stack.push(uint256.NewInt())
var index common.Hash var index common.Hash
logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, nil, contract, 0, nil) logger.CaptureState(env, 0, SSTORE, 0, 0, scope, nil, 0, nil)
if len(logger.storage[contract.Address()]) == 0 { if len(logger.storage[contract.Address()]) == 0 {
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()])) t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(),
len(logger.storage[contract.Address()]))
} }
exp := common.BigToHash(big.NewInt(1)) exp := common.BigToHash(big.NewInt(1))
if logger.storage[contract.Address()][index] != exp { if logger.storage[contract.Address()][index] != exp {
......
...@@ -326,23 +326,18 @@ type stepCounter struct { ...@@ -326,23 +326,18 @@ type stepCounter struct {
steps int steps int
} }
func (s *stepCounter) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { func (s *stepCounter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
return nil
} }
func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rData []byte, contract *vm.Contract, depth int, err error) error { func (s *stepCounter) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
s.steps++
// Enable this for more output
//s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err)
return nil
} }
func (s *stepCounter) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { func (s *stepCounter) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
return nil
}
func (s *stepCounter) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
return nil s.steps++
// Enable this for more output
//s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err)
} }
// benchmarkNonModifyingCode benchmarks code, but if the code modifies the // benchmarkNonModifyingCode benchmarks code, but if the code modifies the
......
...@@ -287,8 +287,6 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) { ...@@ -287,8 +287,6 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) {
// Tracer provides an implementation of Tracer that evaluates a Javascript // Tracer provides an implementation of Tracer that evaluates a Javascript
// function for each VM execution step. // function for each VM execution step.
type Tracer struct { type Tracer struct {
inited bool // Flag whether the context was already inited from the EVM
vm *duktape.Context // Javascript VM instance vm *duktape.Context // Javascript VM instance
tracerObject int // Stack index of the tracer JavaScript object tracerObject int // Stack index of the tracer JavaScript object
...@@ -529,7 +527,7 @@ func wrapError(context string, err error) error { ...@@ -529,7 +527,7 @@ func wrapError(context string, err error) error {
} }
// CaptureStart implements the Tracer interface to initialize the tracing operation. // CaptureStart implements the Tracer interface to initialize the tracing operation.
func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
jst.ctx["type"] = "CALL" jst.ctx["type"] = "CALL"
if create { if create {
jst.ctx["type"] = "CREATE" jst.ctx["type"] = "CREATE"
...@@ -540,77 +538,67 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b ...@@ -540,77 +538,67 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b
jst.ctx["gas"] = gas jst.ctx["gas"] = gas
jst.ctx["value"] = value jst.ctx["value"] = value
return nil // Initialize the context
jst.ctx["block"] = env.Context.BlockNumber.Uint64()
jst.dbWrapper.db = env.StateDB
// Compute intrinsic gas
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul)
if err != nil {
return
}
jst.ctx["intrinsicGas"] = intrinsicGas
} }
// CaptureState implements the Tracer interface to trace a single step of VM execution. // CaptureState implements the Tracer interface to trace a single step of VM execution.
func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rdata []byte, contract *vm.Contract, depth int, err error) error { func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
if jst.err == nil { if jst.err != nil {
// Initialize the context if it wasn't done yet return
if !jst.inited { }
jst.ctx["block"] = env.Context.BlockNumber.Uint64() // If tracing was interrupted, set the error and stop
// Compute intrinsic gas if atomic.LoadUint32(&jst.interrupt) > 0 {
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) jst.err = jst.reason
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) return
var input []byte }
if data, ok := jst.ctx["input"].([]byte); ok { jst.opWrapper.op = op
input = data jst.stackWrapper.stack = scope.Stack
} jst.memoryWrapper.memory = scope.Memory
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) jst.contractWrapper.contract = scope.Contract
if err != nil {
return err *jst.pcValue = uint(pc)
} *jst.gasValue = uint(gas)
jst.ctx["intrinsicGas"] = intrinsicGas *jst.costValue = uint(cost)
jst.inited = true *jst.depthValue = uint(depth)
} *jst.refundValue = uint(env.StateDB.GetRefund())
// If tracing was interrupted, set the error and stop
if atomic.LoadUint32(&jst.interrupt) > 0 { jst.errorValue = nil
jst.err = jst.reason if err != nil {
return nil jst.errorValue = new(string)
} *jst.errorValue = err.Error()
jst.opWrapper.op = op }
jst.stackWrapper.stack = stack
jst.memoryWrapper.memory = memory if _, err := jst.call("step", "log", "db"); err != nil {
jst.contractWrapper.contract = contract jst.err = wrapError("step", err)
jst.dbWrapper.db = env.StateDB
*jst.pcValue = uint(pc)
*jst.gasValue = uint(gas)
*jst.costValue = uint(cost)
*jst.depthValue = uint(depth)
*jst.refundValue = uint(env.StateDB.GetRefund())
jst.errorValue = nil
if err != nil {
jst.errorValue = new(string)
*jst.errorValue = err.Error()
}
_, err := jst.call("step", "log", "db")
if err != nil {
jst.err = wrapError("step", err)
}
} }
return nil
} }
// CaptureFault implements the Tracer interface to trace an execution fault // CaptureFault implements the Tracer interface to trace an execution fault
// while running an opcode. func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { if jst.err != nil {
if jst.err == nil { return
// Apart from the error, everything matches the previous invocation }
jst.errorValue = new(string) // Apart from the error, everything matches the previous invocation
*jst.errorValue = err.Error() jst.errorValue = new(string)
*jst.errorValue = err.Error()
_, err := jst.call("fault", "log", "db") if _, err := jst.call("fault", "log", "db"); err != nil {
if err != nil { jst.err = wrapError("fault", err)
jst.err = wrapError("fault", err)
}
} }
return nil
} }
// CaptureEnd is called after the call finishes to finalize the tracing. // CaptureEnd is called after the call finishes to finalize the tracing.
func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
jst.ctx["output"] = output jst.ctx["output"] = output
jst.ctx["time"] = t.String() jst.ctx["time"] = t.String()
jst.ctx["gasUsed"] = gasUsed jst.ctx["gasUsed"] = gasUsed
...@@ -618,7 +606,6 @@ func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, er ...@@ -618,7 +606,6 @@ func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, er
if err != nil { if err != nil {
jst.ctx["error"] = err.Error() jst.ctx["error"] = err.Error()
} }
return nil
} }
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
......
...@@ -47,7 +47,8 @@ type dummyStatedb struct { ...@@ -47,7 +47,8 @@ type dummyStatedb struct {
state.StateDB state.StateDB
} }
func (*dummyStatedb) GetRefund() uint64 { return 1337 } func (*dummyStatedb) GetRefund() uint64 { return 1337 }
func (*dummyStatedb) GetBalance(addr common.Address) *big.Int { return new(big.Int) }
type vmContext struct { type vmContext struct {
blockCtx vm.BlockContext blockCtx vm.BlockContext
...@@ -67,7 +68,7 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { ...@@ -67,7 +68,7 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
contract := vm.NewContract(account{}, account{}, value, startGas) contract := vm.NewContract(account{}, account{}, value, startGas)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
tracer.CaptureStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value) tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
ret, err := env.Interpreter().Run(contract, []byte{}, false) ret, err := env.Interpreter().Run(contract, []byte{}, false)
tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err) tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
if err != nil { if err != nil {
...@@ -150,14 +151,55 @@ func TestHaltBetweenSteps(t *testing.T) { ...@@ -150,14 +151,55 @@ func TestHaltBetweenSteps(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
}
tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil)
timeout := errors.New("stahp") timeout := errors.New("stahp")
tracer.Stop(timeout) tracer.Stop(timeout)
tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil)
if _, err := tracer.GetResult(); err.Error() != timeout.Error() { if _, err := tracer.GetResult(); err.Error() != timeout.Error() {
t.Errorf("Expected timeout error, got %v", err) t.Errorf("Expected timeout error, got %v", err)
} }
} }
// TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb
// in 'result'
func TestNoStepExec(t *testing.T) {
runEmptyTrace := func(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
startGas := uint64(10000)
contract := vm.NewContract(account{}, account{}, big.NewInt(0), startGas)
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, big.NewInt(0))
tracer.CaptureEnd(nil, startGas-contract.Gas, 1, nil)
return tracer.GetResult()
}
execTracer := func(code string) []byte {
t.Helper()
ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
tracer, err := New(code, ctx.txCtx)
if err != nil {
t.Fatal(err)
}
ret, err := runEmptyTrace(tracer, ctx)
if err != nil {
t.Fatal(err)
}
return ret
}
for i, tt := range []struct {
code string
want string
}{
{ // tests that we don't panic on accessing the db methods
code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx, db){ return db.getBalance(ctx.to)} }",
want: `"0"`,
},
} {
if have := execTracer(tt.code); tt.want != string(have) {
t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment