eth, eth/tracers: include intrinsic gas in calltracer, expose for all tracers (#22038)

* eth/tracers: share tx gas price with js tracer

* eth/tracers: use `go generate`

* eth/tracers: try with another version of go-bindata

* eth/tracers: export txGas

* eth, eth/tracers: pass intrinsic gas to js tracers

eth/tracers: include tx gas in tracers usedGas

eth/tracers: fix prestate tracer's sender balance

eth/tracers: rm unnecessary import

eth/tracers: pass intrinsicGas separately to tracer

eth/tracers: fix tests broken by lack of txdata

eth, eth/tracers: minor fix

* eth/tracers: regenerate assets + unexport test-struct + add testcase

* eth/tracers: simplify tests + make table-driven

Co-authored-by: Guillaume Ballet <gballet@gmail.com>
Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
Sina Mahmoodi 2020-12-27 21:57:19 +01:00 committed by GitHub
parent 25c0bd9b43
commit 9c6b5b904a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 93 deletions

View File

@ -794,7 +794,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
} }
} }
// Constuct the JavaScript tracer to execute with // Constuct the JavaScript tracer to execute with
if tracer, err = tracers.New(*config.Tracer); err != nil { if tracer, err = tracers.New(*config.Tracer, txContext); err != nil {
return nil, err return nil, err
} }
// Handle timeouts and RPC cancellations // Handle timeouts and RPC cancellations

File diff suppressed because one or more lines are too long

View File

@ -55,7 +55,7 @@
var toBal = bigInt(this.prestate[toHex(ctx.to)].balance.slice(2), 16); var toBal = bigInt(this.prestate[toHex(ctx.to)].balance.slice(2), 16);
this.prestate[toHex(ctx.to)].balance = '0x'+toBal.subtract(ctx.value).toString(16); this.prestate[toHex(ctx.to)].balance = '0x'+toBal.subtract(ctx.value).toString(16);
this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).toString(16); this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).add((ctx.gasUsed + ctx.intrinsicGas) * ctx.gasPrice).toString(16);
// Decrement the caller's nonce, and remove empty create targets // Decrement the caller's nonce, and remove empty create targets
this.prestate[toHex(ctx.from)].nonce--; this.prestate[toHex(ctx.from)].nonce--;

View File

@ -27,10 +27,11 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"gopkg.in/olebedev/go-duktape.v3" duktape "gopkg.in/olebedev/go-duktape.v3"
) )
// bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js. // bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js.
@ -316,7 +317,7 @@ type Tracer struct {
// New instantiates a new tracer instance. code specifies a Javascript snippet, // New instantiates a new tracer instance. code specifies a Javascript snippet,
// which must evaluate to an expression returning an object with 'step', 'fault' // which must evaluate to an expression returning an object with 'step', 'fault'
// and 'result' functions. // and 'result' functions.
func New(code string) (*Tracer, error) { func New(code string, txCtx vm.TxContext) (*Tracer, error) {
// Resolve any tracers by name and assemble the tracer object // Resolve any tracers by name and assemble the tracer object
if tracer, ok := tracer(code); ok { if tracer, ok := tracer(code); ok {
code = tracer code = tracer
@ -335,6 +336,8 @@ func New(code string) (*Tracer, error) {
depthValue: new(uint), depthValue: new(uint),
refundValue: new(uint), refundValue: new(uint),
} }
tracer.ctx["gasPrice"] = txCtx.GasPrice
// Set up builtins for this environment // Set up builtins for this environment
tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int { tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
ctx.PushString(hexutil.Encode(popSlice(ctx))) ctx.PushString(hexutil.Encode(popSlice(ctx)))
@ -546,6 +549,18 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
// Initialize the context if it wasn't done yet // Initialize the context if it wasn't done yet
if !jst.inited { if !jst.inited {
jst.ctx["block"] = env.Context.BlockNumber.Uint64() 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, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul)
if err != nil {
return err
}
jst.ctx["intrinsicGas"] = intrinsicGas
jst.inited = true jst.inited = true
} }
// If tracing was interrupted, set the error and stop // If tracing was interrupted, set the error and stop
@ -597,8 +612,8 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
// 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) error {
jst.ctx["output"] = output jst.ctx["output"] = output
jst.ctx["gasUsed"] = gasUsed
jst.ctx["time"] = t.String() jst.ctx["time"] = t.String()
jst.ctx["gasUsed"] = gasUsed
if err != nil { if err != nil {
jst.ctx["error"] = err.Error() jst.ctx["error"] = err.Error()

View File

@ -17,7 +17,6 @@
package tracers package tracers
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"math/big" "math/big"
@ -50,94 +49,77 @@ type dummyStatedb struct {
func (*dummyStatedb) GetRefund() uint64 { return 1337 } func (*dummyStatedb) GetRefund() uint64 { return 1337 }
func runTrace(tracer *Tracer) (json.RawMessage, error) { type vmContext struct {
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) blockCtx vm.BlockContext
txCtx vm.TxContext
}
contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) func testCtx() *vmContext {
return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
}
func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
var (
startGas uint64 = 10000
value = big.NewInt(0)
)
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}
_, err := env.Interpreter().Run(contract, []byte{}, false) tracer.CaptureStart(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 { if err != nil {
return nil, err return nil, err
} }
return tracer.GetResult() return tracer.GetResult()
} }
// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory access func TestTracer(t *testing.T) {
func TestRegressionPanicSlice(t *testing.T) { execTracer := func(code string) []byte {
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}") t.Helper()
if err != nil { ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
t.Fatal(err) tracer, err := New(code, ctx.txCtx)
if err != nil {
t.Fatal(err)
}
ret, err := runTrace(tracer, ctx)
if err != nil {
t.Fatal(err)
}
return ret
} }
if _, err = runTrace(tracer); err != nil { for i, tt := range []struct {
t.Fatal(err) code string
} want string
} }{
{ // tests that we don't panic on bad arguments to memory access
// TestRegressionPanicSlice tests that we don't panic on bad arguments to stack peeks code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}",
func TestRegressionPanicPeek(t *testing.T) { want: `[{},{},{}]`,
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}") }, { // tests that we don't panic on bad arguments to stack peeks
if err != nil { code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}",
t.Fatal(err) want: `["0","0","0"]`,
} }, { // tests that we don't panic on bad arguments to memory getUint
if _, err = runTrace(tracer); err != nil { code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}",
t.Fatal(err) want: `["0","0","0"]`,
} }, { // tests some general counting
} code: "{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}",
want: `3`,
// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory getUint }, { // tests that depth is reported correctly
func TestRegressionPanicGetUint(t *testing.T) { code: "{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}",
tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}") want: `[0,1,2]`,
if err != nil { }, { // tests to-string of opcodes
t.Fatal(err) code: "{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}",
} want: `["PUSH1","PUSH1","STOP"]`,
if _, err = runTrace(tracer); err != nil { }, { // tests intrinsic gas
t.Fatal(err) code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}",
} want: `"100000.6.21000"`,
} },
} {
func TestTracing(t *testing.T) { if have := execTracer(tt.code); tt.want != string(have) {
tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}") t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code)
if err != nil { }
t.Fatal(err)
}
ret, err := runTrace(tracer)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(ret, []byte("3")) {
t.Errorf("Expected return value to be 3, got %s", string(ret))
}
}
func TestStack(t *testing.T) {
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}")
if err != nil {
t.Fatal(err)
}
ret, err := runTrace(tracer)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(ret, []byte("[0,1,2]")) {
t.Errorf("Expected return value to be [0,1,2], got %s", string(ret))
}
}
func TestOpcodes(t *testing.T) {
tracer, err := New("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}")
if err != nil {
t.Fatal(err)
}
ret, err := runTrace(tracer)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(ret, []byte("[\"PUSH1\",\"PUSH1\",\"STOP\"]")) {
t.Errorf("Expected return value to be [\"PUSH1\",\"PUSH1\",\"STOP\"], got %s", string(ret))
} }
} }
@ -145,7 +127,8 @@ func TestHalt(t *testing.T) {
t.Skip("duktape doesn't support abortion") t.Skip("duktape doesn't support abortion")
timeout := errors.New("stahp") timeout := errors.New("stahp")
tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}") vmctx := testCtx()
tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", vmctx.txCtx)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -155,17 +138,17 @@ func TestHalt(t *testing.T) {
tracer.Stop(timeout) tracer.Stop(timeout)
}() }()
if _, err = runTrace(tracer); err.Error() != "stahp in server-side tracer function 'step'" { if _, err = runTrace(tracer, vmctx); err.Error() != "stahp in server-side tracer function 'step'" {
t.Errorf("Expected timeout error, got %v", err) t.Errorf("Expected timeout error, got %v", err)
} }
} }
func TestHaltBetweenSteps(t *testing.T) { func TestHaltBetweenSteps(t *testing.T) {
tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}") vmctx := testCtx()
tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", vmctx.txCtx)
if err != nil { if err != nil {
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) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0)

View File

@ -173,7 +173,7 @@ func TestPrestateTracerCreate2(t *testing.T) {
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
// Create the tracer, the EVM environment and run it // Create the tracer, the EVM environment and run it
tracer, err := New("prestateTracer") tracer, err := New("prestateTracer", txContext)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
@ -248,7 +248,7 @@ func TestCallTracer(t *testing.T) {
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
// Create the tracer, the EVM environment and run it // Create the tracer, the EVM environment and run it
tracer, err := New("callTracer") tracer, err := New("callTracer", txContext)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }