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.
This commit is contained in:
committed by
GitHub
parent
c5df05b9a9
commit
0fda25e471
@ -287,8 +287,6 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) {
|
||||
// Tracer provides an implementation of Tracer that evaluates a Javascript
|
||||
// function for each VM execution step.
|
||||
type Tracer struct {
|
||||
inited bool // Flag whether the context was already inited from the EVM
|
||||
|
||||
vm *duktape.Context // Javascript VM instance
|
||||
|
||||
tracerObject int // Stack index of the tracer JavaScript object
|
||||
@ -529,7 +527,7 @@ func wrapError(context string, err error) error {
|
||||
}
|
||||
|
||||
// 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"
|
||||
if create {
|
||||
jst.ctx["type"] = "CREATE"
|
||||
@ -540,77 +538,67 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b
|
||||
jst.ctx["gas"] = gas
|
||||
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.
|
||||
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 {
|
||||
if jst.err == nil {
|
||||
// Initialize the context if it wasn't done yet
|
||||
if !jst.inited {
|
||||
jst.ctx["block"] = env.Context.BlockNumber.Uint64()
|
||||
// Compute intrinsic gas
|
||||
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
|
||||
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
|
||||
var input []byte
|
||||
if data, ok := jst.ctx["input"].([]byte); ok {
|
||||
input = data
|
||||
}
|
||||
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jst.ctx["intrinsicGas"] = intrinsicGas
|
||||
jst.inited = true
|
||||
}
|
||||
// If tracing was interrupted, set the error and stop
|
||||
if atomic.LoadUint32(&jst.interrupt) > 0 {
|
||||
jst.err = jst.reason
|
||||
return nil
|
||||
}
|
||||
jst.opWrapper.op = op
|
||||
jst.stackWrapper.stack = stack
|
||||
jst.memoryWrapper.memory = memory
|
||||
jst.contractWrapper.contract = contract
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
return
|
||||
}
|
||||
// If tracing was interrupted, set the error and stop
|
||||
if atomic.LoadUint32(&jst.interrupt) > 0 {
|
||||
jst.err = jst.reason
|
||||
return
|
||||
}
|
||||
jst.opWrapper.op = op
|
||||
jst.stackWrapper.stack = scope.Stack
|
||||
jst.memoryWrapper.memory = scope.Memory
|
||||
jst.contractWrapper.contract = scope.Contract
|
||||
|
||||
*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()
|
||||
}
|
||||
|
||||
if _, err := jst.call("step", "log", "db"); err != nil {
|
||||
jst.err = wrapError("step", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
|
||||
if jst.err == nil {
|
||||
// Apart from the error, everything matches the previous invocation
|
||||
jst.errorValue = new(string)
|
||||
*jst.errorValue = err.Error()
|
||||
|
||||
_, err := jst.call("fault", "log", "db")
|
||||
if err != nil {
|
||||
jst.err = wrapError("fault", err)
|
||||
}
|
||||
func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
if jst.err != nil {
|
||||
return
|
||||
}
|
||||
// Apart from the error, everything matches the previous invocation
|
||||
jst.errorValue = new(string)
|
||||
*jst.errorValue = err.Error()
|
||||
|
||||
if _, err := jst.call("fault", "log", "db"); err != nil {
|
||||
jst.err = wrapError("fault", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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["time"] = t.String()
|
||||
jst.ctx["gasUsed"] = gasUsed
|
||||
@ -618,7 +606,6 @@ func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, er
|
||||
if err != nil {
|
||||
jst.ctx["error"] = err.Error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
|
||||
|
@ -47,7 +47,8 @@ type dummyStatedb struct {
|
||||
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 {
|
||||
blockCtx vm.BlockContext
|
||||
@ -67,7 +68,7 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
|
||||
contract := vm.NewContract(account{}, account{}, value, startGas)
|
||||
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)
|
||||
tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
|
||||
if err != nil {
|
||||
@ -150,14 +151,55 @@ func TestHaltBetweenSteps(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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")
|
||||
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() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user