Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth/tracers: add native flatCallTracer (aka parity style tracer) #26377

Merged
merged 54 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
92ecfa4
eth/tracers/internal/tracetest,eth/tracers/internal/tracetest/testdat…
ziogaschr Nov 9, 2022
9369489
eth/tracers/native,go.mod,go.sum: add flatCall native tracer
ziogaschr Nov 9, 2022
db274bc
basic formatting
ziogaschr Nov 18, 2022
95f7e3f
add some undeclared structs
ziogaschr Nov 18, 2022
457aba0
read callFrameMarshaling.TypeString in flatTracer
ziogaschr Nov 18, 2022
271a8b8
change callFrame.Type to stringOpCode type for gencodec marshaling
ziogaschr Nov 19, 2022
3786e6a
remove string quotes from stringOpCode.UnmarshalJSON
ziogaschr Nov 19, 2022
68d8d4f
cleanup flatTracer code
ziogaschr Nov 19, 2022
33c5e8f
remove debug code
ziogaschr Nov 19, 2022
f8df70a
remove dead code
ziogaschr Nov 19, 2022
b0ec084
go mod tidy
ziogaschr Nov 19, 2022
0237599
Merge branch 'master' into feat/flat-call-tracer
ziogaschr Nov 21, 2022
1115e4a
go mod tidy
ziogaschr Nov 21, 2022
f41caea
fix flatCallTracer tests for using parity configuration (WiP)
ziogaschr Nov 21, 2022
e7e3a5f
flatCall tracer uses parent value for DELEGATECALL when zero
ziogaschr Nov 21, 2022
5c56e2c
fix top call frame gas used
s1na Dec 22, 2022
c9dd179
fix value inherited in delegatecall
s1na Dec 22, 2022
0e3ccf2
Exclude precompiles from calls
s1na Dec 22, 2022
3ab85b1
fix merge conflict
s1na Dec 22, 2022
b2eeb0d
copy addrs
s1na Dec 22, 2022
65ebeaa
refactor types
s1na Dec 22, 2022
b2f54e8
avoid json bounce in GetResult
s1na Dec 22, 2022
4cba0b4
minor cleanups
s1na Dec 22, 2022
cd7109c
s/convertedParityErrors/convertParityErrors for flat tracer tests
ziogaschr Dec 22, 2022
1f32a3a
fix test
s1na Dec 28, 2022
063f3c1
add blockNumber to context
s1na Dec 28, 2022
a402ba8
refactor result processing
s1na Dec 29, 2022
e904a32
eth/tracers/native: change struct fields to pointers to be ommited fr…
ziogaschr Jan 27, 2023
af65397
eth/tracers/native: copy child call
ziogaschr Jan 27, 2023
d4da340
eth/tracers/native: Child calls in flatCallTracer must have a value, …
ziogaschr Jan 30, 2023
1ec73ba
Merge branch 'master' into feat/flat-call-tracer
ziogaschr Feb 1, 2023
c457436
eth/tracers/native: update flatCallTracer to reflect new interfaces a…
ziogaschr Feb 1, 2023
251a960
fix lint issue
s1na Feb 1, 2023
5d13a74
eth/tracers/internal/tracetest: update eth/tracers/internal/tracetest…
ziogaschr Feb 3, 2023
bb887f3
eth/tracers/internal/tracetest: comment out debugging helpers
ziogaschr Feb 3, 2023
aebff89
go.mod, go.sum: run `go mod tidy`
ziogaschr Feb 7, 2023
03efcd3
eth/tracers/native: remove handling of value of a delegated call
ziogaschr Feb 7, 2023
0890135
eth/tracers/native: change call_tracer_flat tests config to follow th…
ziogaschr Feb 7, 2023
d23e55c
Merge branch 'master' into feat/flat-call-tracer
ziogaschr Feb 10, 2023
04c37e4
eth/tracers/native: call_flat tracer will display calls to precompiles
ziogaschr Feb 10, 2023
40df204
eth/tracers/native: remove convertParityErrors from flat tracer
ziogaschr Feb 10, 2023
4ac7982
eth/tracers/native: remove unused code
ziogaschr Feb 10, 2023
69e7925
eth/tracers/native: geth gas usage is more accurate, accounting for i…
ziogaschr Feb 13, 2023
fe3cb25
eth/tracers/native: flatCallTracer option to return Parity like forma…
ziogaschr Feb 13, 2023
41c2b50
eth/tracers/native: call_flat tracer option to handle calls to precom…
ziogaschr Feb 13, 2023
384facf
eth/tracers/internal/tracetest/testdata/call_tracer_flat: add test fo…
ziogaschr Feb 13, 2023
584c4c0
minor styling
s1na Feb 14, 2023
2f8fda0
eth/tracers/native: flatCall blockNumber and transactionPosition are …
ziogaschr Feb 14, 2023
916e279
eth/tracers: fix panic by filling the BLockNumber for blocks and bett…
ziogaschr Feb 14, 2023
4f9a9aa
Revert gasUsed compatibility trick
s1na Feb 14, 2023
1e34a1d
eth/tracers/internal/tracetest/testdata/call_tracer_flat: fix tests f…
ziogaschr Feb 14, 2023
ce1b7cc
Revert gencodec type field conversion
s1na Feb 27, 2023
65a0222
add comment
s1na Feb 27, 2023
8e4714d
Keep result in case of revert
s1na Feb 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,10 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
for i, tx := range task.block.Transactions() {
msg, _ := tx.AsMessage(signer, task.block.BaseFee())
txctx := &Context{
BlockHash: task.block.Hash(),
TxIndex: i,
TxHash: tx.Hash(),
BlockHash: task.block.Hash(),
BlockNumber: task.block.Number(),
TxIndex: i,
TxHash: tx.Hash(),
}
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config)
if err != nil {
Expand Down Expand Up @@ -629,9 +630,10 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
// Generate the next state snapshot fast without tracing
msg, _ := tx.AsMessage(signer, block.BaseFee())
txctx := &Context{
BlockHash: blockHash,
TxIndex: i,
TxHash: tx.Hash(),
BlockHash: blockHash,
BlockNumber: block.Number(),
TxIndex: i,
TxHash: tx.Hash(),
}
res, err := api.traceTx(ctx, msg, txctx, blockCtx, statedb, config)
if err != nil {
Expand Down Expand Up @@ -671,9 +673,10 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
for task := range jobs {
msg, _ := txs[task.index].AsMessage(signer, block.BaseFee())
txctx := &Context{
BlockHash: blockHash,
TxIndex: task.index,
TxHash: txs[task.index].Hash(),
BlockHash: blockHash,
BlockNumber: block.Number(),
TxIndex: task.index,
TxHash: txs[task.index].Hash(),
}
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config)
if err != nil {
Expand Down Expand Up @@ -882,9 +885,10 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
defer release()

txctx := &Context{
BlockHash: blockHash,
TxIndex: int(index),
TxHash: hash,
BlockHash: blockHash,
BlockNumber: block.Number(),
TxIndex: int(index),
TxHash: hash,
}
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config)
}
Expand Down
213 changes: 213 additions & 0 deletions eth/tracers/internal/tracetest/flat_calltrace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package tracetest

import (
"encoding/json"
"fmt"
"math/big"
"os"
"path/filepath"
"reflect"
"strings"
"testing"

"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/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests"

// Force-load the native, to trigger registration
"github.com/ethereum/go-ethereum/eth/tracers"
)

// flatCallTrace is the result of a callTracerParity run.
type flatCallTrace struct {
Action flatCallTraceAction `json:"action"`
BlockHash common.Hash `json:"-"`
BlockNumber uint64 `json:"-"`
Error string `json:"error,omitempty"`
Result flatCallTraceResult `json:"result,omitempty"`
Subtraces int `json:"subtraces"`
TraceAddress []int `json:"traceAddress"`
TransactionHash common.Hash `json:"-"`
TransactionPosition uint64 `json:"-"`
Type string `json:"type"`
Time string `json:"-"`
}

type flatCallTraceAction struct {
Author common.Address `json:"author,omitempty"`
RewardType string `json:"rewardType,omitempty"`
SelfDestructed common.Address `json:"address,omitempty"`
Balance hexutil.Big `json:"balance,omitempty"`
CallType string `json:"callType,omitempty"`
CreationMethod string `json:"creationMethod,omitempty"`
From common.Address `json:"from,omitempty"`
Gas hexutil.Uint64 `json:"gas,omitempty"`
Init hexutil.Bytes `json:"init,omitempty"`
Input hexutil.Bytes `json:"input,omitempty"`
RefundAddress common.Address `json:"refundAddress,omitempty"`
To common.Address `json:"to,omitempty"`
Value hexutil.Big `json:"value,omitempty"`
}

type flatCallTraceResult struct {
Address common.Address `json:"address,omitempty"`
Code hexutil.Bytes `json:"code,omitempty"`
GasUsed hexutil.Uint64 `json:"gasUsed,omitempty"`
Output hexutil.Bytes `json:"output,omitempty"`
}

// flatCallTracerTest defines a single test to check the call tracer against.
type flatCallTracerTest struct {
Genesis core.Genesis `json:"genesis"`
Context callContext `json:"context"`
Input string `json:"input"`
TracerConfig json.RawMessage `json:"tracerConfig"`
Result []flatCallTrace `json:"result"`
}

func flatCallTracerTestRunner(tracerName string, filename string, dirPath string, t testing.TB) error {
// Call tracer test found, read if from disk
blob, err := os.ReadFile(filepath.Join("testdata", dirPath, filename))
if err != nil {
return fmt.Errorf("failed to read testcase: %v", err)
}
test := new(flatCallTracerTest)
if err := json.Unmarshal(blob, test); err != nil {
return fmt.Errorf("failed to parse testcase: %v", err)
}
// Configure a blockchain with the given prestate
tx := new(types.Transaction)
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
return fmt.Errorf("failed to parse testcase input: %v", err)
}
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
origin, _ := signer.Sender(tx)
txContext := vm.TxContext{
Origin: origin,
GasPrice: tx.GasPrice(),
}
context := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
Coinbase: test.Context.Miner,
BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
Time: uint64(test.Context.Time),
Difficulty: (*big.Int)(test.Context.Difficulty),
GasLimit: uint64(test.Context.GasLimit),
}
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)

// Create the tracer, the EVM environment and run it
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil {
return fmt.Errorf("failed to create call tracer: %v", err)
}
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})

msg, err := tx.AsMessage(signer, nil)
if err != nil {
return fmt.Errorf("failed to prepare transaction for tracing: %v", err)
}
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))

if _, err = st.TransitionDb(); err != nil {
return fmt.Errorf("failed to execute transaction: %v", err)
}

// Retrieve the trace result and compare against the etalon
res, err := tracer.GetResult()
if err != nil {
return fmt.Errorf("failed to retrieve trace result: %v", err)
}
ret := new([]flatCallTrace)
if err := json.Unmarshal(res, ret); err != nil {
return fmt.Errorf("failed to unmarshal trace result: %v", err)
}
if !jsonEqualFlat(ret, test.Result) {
t.Logf("tracer name: %s", tracerName)

// uncomment this for easier debugging
// have, _ := json.MarshalIndent(ret, "", " ")
// want, _ := json.MarshalIndent(test.Result, "", " ")
// t.Logf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))

// uncomment this for harder debugging <3 meowsbits
// lines := deep.Equal(ret, test.Result)
// for _, l := range lines {
// t.Logf("%s", l)
// t.FailNow()
// }

t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
}
return nil
}

// Iterates over all the input-output datasets in the tracer parity test harness and
// runs the Native tracer against them.
func TestFlatCallTracerNative(t *testing.T) {
testFlatCallTracer("flatCallTracer", "call_tracer_flat", t)
}

func testFlatCallTracer(tracerName string, dirPath string, t *testing.T) {
files, err := os.ReadDir(filepath.Join("testdata", dirPath))
if err != nil {
t.Fatalf("failed to retrieve tracer test suite: %v", err)
}
for _, file := range files {
if !strings.HasSuffix(file.Name(), ".json") {
continue
}
file := file // capture range variable
t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
t.Parallel()

err := flatCallTracerTestRunner(tracerName, file.Name(), dirPath, t)
if err != nil {
t.Fatal(err)
}
})
}
}

// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
// comparison
func jsonEqualFlat(x, y interface{}) bool {
xTrace := new([]flatCallTrace)
yTrace := new([]flatCallTrace)
if xj, err := json.Marshal(x); err == nil {
json.Unmarshal(xj, xTrace)
} else {
return false
}
if yj, err := json.Marshal(y); err == nil {
json.Unmarshal(yj, yTrace)
} else {
return false
}
return reflect.DeepEqual(xTrace, yTrace)
}

func BenchmarkFlatCallTracer(b *testing.B) {
files, err := filepath.Glob("testdata/call_tracer_flat/*.json")
if err != nil {
b.Fatalf("failed to read testdata: %v", err)
}

for _, file := range files {
filename := strings.TrimPrefix(file, "testdata/call_tracer_flat/")
b.Run(camel(strings.TrimSuffix(filename, ".json")), func(b *testing.B) {
for n := 0; n < b.N; n++ {
err := flatCallTracerTestRunner("flatCallTracer", filename, "call_tracer_flat", b)
if err != nil {
b.Fatal(err)
}
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"genesis": {
"difficulty": "50486697699375",
"extraData": "0xd783010406844765746887676f312e362e32856c696e7578",
"gasLimit": "4788482",
"hash": "0xf6bbc5bbe34d5c93fd5b4712cd498d1026b8b0f586efefe7fe30231ed6b8a1a5",
"miner": "0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1",
"mixHash": "0xabca93555584c0463ee5c212251dd002bb3a93a157e06614276f93de53d4fdb8",
"nonce": "0xa64136fcb9c2d4ca",
"number": "1719576",
"stateRoot": "0xab5eec2177a92d633e282936af66c46e24cfa8f2fdc2b8155f33885f483d06f3",
"timestamp": "1466150166",
"totalDifficulty": "28295412423546970038",
"alloc": {
"0xf8bda96b67036ee48107f2a0695ea673479dda56": {
"balance": "0x1529e844f9ecdeec",
"nonce": "33",
"code": "0x",
"storage": {}
}
},
"config": {
"chainId": 1,
"daoForkSupport": true,
"eip150Block": 0,
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
"eip155Block": 3000000,
"eip158Block": 0,
"ethash": {},
"homesteadBlock": 1150000,
"byzantiumBlock": 8772000,
"constantinopleBlock": 9573000,
"petersburgBlock": 10500839,
"istanbulBlock": 10500839
}
},
"context": {
"number": "1719577",
"difficulty": "50486697732143",
"timestamp": "1466150178",
"gasLimit": "4788484",
"miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226"
},
"input": "0xf874218504a817c800832318608080a35b620186a05a131560135760016020526000565b600080601f600039601f565b6000f31ba0575fa000a1f06659a7b6d3c7877601519a4997f04293f0dfa0eee6d8cd840c77a04c52ce50719ee2ff7a0c5753f4ee69c0340666f582dbb5148845a354ca726e4a",
"result": [
{
"action": {
"from": "0xf8bda96b67036ee48107f2a0695ea673479dda56",
"gas": "0x22410c",
"init": "0x5b620186a05a131560135760016020526000565b600080601f600039601f565b6000f3",
"value": "0x0"
},
"blockNumber": 1719577,
"result": {
"address": "0xb2e6a2546c45889427757171ab05b8b438525b42",
"code": "0x",
"gasUsed": "0x219202"
},
"subtraces": 0,
"traceAddress": [],
"type": "create"
}
]
}
Loading