forked from cerc-io/plugeth
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:
parent
25c0bd9b43
commit
9c6b5b904a
@ -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
@ -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--;
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user