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
|
||||
if tracer, err = tracers.New(*config.Tracer); err != nil {
|
||||
if tracer, err = tracers.New(*config.Tracer, txContext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 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);
|
||||
|
||||
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
|
||||
this.prestate[toHex(ctx.from)].nonce--;
|
||||
|
|
|
@ -27,10 +27,11 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/crypto"
|
||||
"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.
|
||||
|
@ -316,7 +317,7 @@ type Tracer struct {
|
|||
// New instantiates a new tracer instance. code specifies a Javascript snippet,
|
||||
// which must evaluate to an expression returning an object with 'step', 'fault'
|
||||
// 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
|
||||
if tracer, ok := tracer(code); ok {
|
||||
code = tracer
|
||||
|
@ -335,6 +336,8 @@ func New(code string) (*Tracer, error) {
|
|||
depthValue: new(uint),
|
||||
refundValue: new(uint),
|
||||
}
|
||||
tracer.ctx["gasPrice"] = txCtx.GasPrice
|
||||
|
||||
// Set up builtins for this environment
|
||||
tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
|
||||
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
|
||||
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, 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
|
||||
|
@ -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.
|
||||
func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
||||
jst.ctx["output"] = output
|
||||
jst.ctx["gasUsed"] = gasUsed
|
||||
jst.ctx["time"] = t.String()
|
||||
jst.ctx["gasUsed"] = gasUsed
|
||||
|
||||
if err != nil {
|
||||
jst.ctx["error"] = err.Error()
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package tracers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
@ -50,94 +49,77 @@ type dummyStatedb struct {
|
|||
|
||||
func (*dummyStatedb) GetRefund() uint64 { return 1337 }
|
||||
|
||||
func runTrace(tracer *Tracer) (json.RawMessage, error) {
|
||||
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
|
||||
type vmContext struct {
|
||||
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}
|
||||
|
||||
_, 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 {
|
||||
return nil, err
|
||||
}
|
||||
return tracer.GetResult()
|
||||
}
|
||||
|
||||
// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory access
|
||||
func TestRegressionPanicSlice(t *testing.T) {
|
||||
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestTracer(t *testing.T) {
|
||||
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 := runTrace(tracer, ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
if _, err = runTrace(tracer); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegressionPanicSlice tests that we don't panic on bad arguments to stack peeks
|
||||
func TestRegressionPanicPeek(t *testing.T) {
|
||||
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = runTrace(tracer); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory getUint
|
||||
func TestRegressionPanicGetUint(t *testing.T) {
|
||||
tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = runTrace(tracer); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracing(t *testing.T) {
|
||||
tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}")
|
||||
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))
|
||||
for i, tt := range []struct {
|
||||
code string
|
||||
want string
|
||||
}{
|
||||
{ // tests that we don't panic on bad arguments to memory access
|
||||
code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}",
|
||||
want: `[{},{},{}]`,
|
||||
}, { // tests that we don't panic on bad arguments to stack peeks
|
||||
code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}",
|
||||
want: `["0","0","0"]`,
|
||||
}, { // tests that we don't panic on bad arguments to memory getUint
|
||||
code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}",
|
||||
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`,
|
||||
}, { // tests that depth is reported correctly
|
||||
code: "{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}",
|
||||
want: `[0,1,2]`,
|
||||
}, { // tests to-string of opcodes
|
||||
code: "{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}",
|
||||
want: `["PUSH1","PUSH1","STOP"]`,
|
||||
}, { // tests intrinsic gas
|
||||
code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}",
|
||||
want: `"100000.6.21000"`,
|
||||
},
|
||||
} {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +127,8 @@ func TestHalt(t *testing.T) {
|
|||
t.Skip("duktape doesn't support abortion")
|
||||
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -155,17 +138,17 @@ func TestHalt(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ func TestPrestateTracerCreate2(t *testing.T) {
|
|||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
|
||||
|
||||
// Create the tracer, the EVM environment and run it
|
||||
tracer, err := New("prestateTracer")
|
||||
tracer, err := New("prestateTracer", txContext)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
// Create the tracer, the EVM environment and run it
|
||||
tracer, err := New("callTracer")
|
||||
tracer, err := New("callTracer", txContext)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue