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 diffMode to prestateTracer #25422

Merged
merged 37 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c97352a
eth/tracers: add a simple diffstate implemention
jsvisa Jul 27, 2022
7acf179
eth/tracers/native: rename diffstateTracer to stateDiffTracer
jsvisa Jul 28, 2022
f27afc9
eth/tracers/internal: add stateDiffTracer testcase
jsvisa Jul 28, 2022
9184369
eth/tracers/statediff: change the diff in CaptureTxEnd
jsvisa Jul 28, 2022
a95eeaa
eth/tracers/statediff: don't exclude the created contracts
jsvisa Jul 28, 2022
76e04ff
eth/tracers/native: refund the from address with rest gas
jsvisa Jul 28, 2022
810df7f
eth/tracers/native: statediff add miner's state
jsvisa Jul 28, 2022
60b0bf1
eth/tracers/internal: don't repeat yourself
jsvisa Jul 29, 2022
aaffa05
eth/tracers/internal: move common func into util.go
jsvisa Jul 29, 2022
2fa7b17
eth/tracers/native: merge statediff into prestate
jsvisa Aug 9, 2022
bdcd832
eth/tracers/internal: test prestate
jsvisa Aug 9, 2022
73ad31d
eth/tracers/native: post in prestate as optional
jsvisa Aug 9, 2022
2d95b35
eth/tracer/internal/tracetest: test prestate with collectPost
jsvisa Aug 9, 2022
1592d53
eth/tracers/native: pre/post prestate
jsvisa Sep 21, 2022
ce9f84b
eth/tracers/native: prestate calculate the post state pre txend
jsvisa Sep 21, 2022
f6401a2
eth/tracers/native: rm notused from/miner balance calculate
jsvisa Sep 21, 2022
f6b148d
eth/tracers/native: omitempty prestate
jsvisa Sep 21, 2022
0ed11f2
eth/tracers/native: keep the modified fields
jsvisa Sep 21, 2022
81b6a4d
eth/tracers/tracetest: test with pre && post trace
jsvisa Sep 21, 2022
6cc9770
eth/tracers/tracetest: move collectPost into indepent dir
jsvisa Sep 21, 2022
43d6612
eth/tracers/tracetest: omitempty
jsvisa Sep 21, 2022
90fb1ac
eth/tracers: rm not used t.from
jsvisa Sep 23, 2022
9b0e264
eth/tracers: missing the tx's created contract
jsvisa Sep 23, 2022
a39764d
eth/tracers/internal: fix the issue nonce maybe null
jsvisa Sep 23, 2022
e18711a
eth/tracers: clear empty slot in prestate
jsvisa Sep 23, 2022
30f509a
eth/traces: remove the not empty contracts in pre
jsvisa Sep 23, 2022
47eaf6c
eth/tracers: add create prestate testcase
jsvisa Sep 23, 2022
d4c3be5
eth/tracers: delete the not modified address in pre
jsvisa Sep 23, 2022
0e97016
eth/traces: add suicide,inner_create testcase
jsvisa Sep 23, 2022
48a3f3d
eth/tracers: add create_suicide testcase
jsvisa Sep 23, 2022
6bca612
eth/tracers: fix the issue of pointer compare
jsvisa Sep 28, 2022
37370ee
eth/tracers/native: collectPost -> diffMode
jsvisa Sep 28, 2022
c4e26ed
eth/tracers/internal: rename collectPost to diffMode
jsvisa Sep 28, 2022
c7a9093
eth/tracers: prestate omitempty
jsvisa Sep 28, 2022
7913c47
eth/tracers: fix the test folder name
jsvisa Sep 28, 2022
388789b
eth/tracers: omitempty of prestate's code,storage
jsvisa Sep 28, 2022
d572f7e
eth/tracers: fix the testcase of empty code
jsvisa Sep 28, 2022
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
86 changes: 2 additions & 84 deletions eth/tracers/internal/tracetest/calltrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ import (
"math/big"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"unicode"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
Expand All @@ -38,62 +36,8 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests"

// Force-load native and js packages, to trigger registration
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
)

// To generate a new callTracer test, copy paste the makeTest method below into
// a Geth console and call it with a transaction hash you which to export.

/*
// makeTest generates a callTracer test by running a prestate reassembled and a
// call trace run, assembling all the gathered information into a test case.
var makeTest = function(tx, rewind) {
// Generate the genesis block from the block, transaction and prestate data
var block = eth.getBlock(eth.getTransaction(tx).blockHash);
var genesis = eth.getBlock(block.parentHash);

delete genesis.gasUsed;
delete genesis.logsBloom;
delete genesis.parentHash;
delete genesis.receiptsRoot;
delete genesis.sha3Uncles;
delete genesis.size;
delete genesis.transactions;
delete genesis.transactionsRoot;
delete genesis.uncles;

genesis.gasLimit = genesis.gasLimit.toString();
genesis.number = genesis.number.toString();
genesis.timestamp = genesis.timestamp.toString();

genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind});
for (var key in genesis.alloc) {
genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString();
}
genesis.config = admin.nodeInfo.protocols.eth.config;

// Generate the call trace and produce the test input
var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind});
delete result.time;

console.log(JSON.stringify({
genesis: genesis,
context: {
number: block.number.toString(),
difficulty: block.difficulty,
timestamp: block.timestamp.toString(),
gasLimit: block.gasLimit.toString(),
miner: block.miner,
},
input: eth.getRawTransaction(tx),
result: result,
}, null, 2));
}
*/

type callContext struct {
Number math.HexOrDecimal64 `json:"number"`
Difficulty *math.HexOrDecimal256 `json:"difficulty"`
Expand Down Expand Up @@ -203,7 +147,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
t.Fatalf("failed to unmarshal trace result: %v", err)
}

if !jsonEqual(ret, test.Result) {
if !jsonEqual(ret, test.Result, new(callTrace), new(callTrace)) {
// uncomment this for easier debugging
//have, _ := json.MarshalIndent(ret, "", " ")
//want, _ := json.MarshalIndent(test.Result, "", " ")
Expand All @@ -214,32 +158,6 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
}
}

// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
// comparison
func jsonEqual(x, y interface{}) bool {
xTrace := new(callTrace)
yTrace := new(callTrace)
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)
}

// camel converts a snake cased input string into a camel cased output.
func camel(str string) string {
pieces := strings.Split(str, "_")
for i := 1; i < len(pieces); i++ {
pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
}
return strings.Join(pieces, "")
}
func BenchmarkTracers(b *testing.B) {
files, err := os.ReadDir(filepath.Join("testdata", "call_tracer"))
if err != nil {
Expand Down Expand Up @@ -385,7 +303,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
wantStr := `{"type":"CALL","from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","to":"0x00000000000000000000000000000000deadbeef","value":"0x0","gas":"0x7148","gasUsed":"0x2d0","input":"0x","output":"0x","calls":[{"type":"CALL","from":"0x00000000000000000000000000000000deadbeef","to":"0x00000000000000000000000000000000000000ff","value":"0x0","gas":"0x6cbf","gasUsed":"0x0","input":"0x","output":"0x"}]}`
want := new(callTrace)
json.Unmarshal([]byte(wantStr), want)
if !jsonEqual(have, want) {
if !jsonEqual(have, want, new(callTrace), new(callTrace)) {
t.Error("have != want")
}
}
144 changes: 144 additions & 0 deletions eth/tracers/internal/tracetest/prestate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package tracetest

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

"github.com/ethereum/go-ethereum/common"
"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/eth/tracers"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests"
)

// prestateTrace is the result of a prestateTrace run.
type prestateTrace = map[common.Address]*account
type account struct {
Balance string `json:"balance,omitempty"`
Nonce uint64 `json:"nonce,omitempty"`
Code string `json:"code,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
}
type prePostStateTrace struct {
Pre prestateTrace `json:"pre"`
Post prestateTrace `json:"post"`
}

// prestateTraceTest defines a single test to check the stateDiff tracer against.
type prestateTraceTest struct {
Genesis *core.Genesis `json:"genesis"`
Context *callContext `json:"context"`
Input string `json:"input"`
TracerConfig json.RawMessage `json:"tracerConfig"`
Result interface{} `json:"result"`
}

func TestPrestateTracer(t *testing.T) {
testPrestateDiffTracer("prestateTracer", "prestate_tracer", t, func() interface{} { return new(prestateTrace) })
}

func TestPrestateWithDiffModeTracer(t *testing.T) {
testPrestateDiffTracer("prestateTracer", "prestate_tracer_with_diff_mode", t, func() interface{} { return new(prePostStateTrace) })
}

func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T, typeBuilder func() interface{}) {
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()

var (
test = new(prestateTraceTest)
tx = new(types.Transaction)
)
// Call tracer test found, read if from disk
if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
t.Fatalf("failed to read testcase: %v", err)
} else if err := json.Unmarshal(blob, test); err != nil {
t.Fatalf("failed to parse testcase: %v", err)
}
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
t.Fatalf("failed to parse testcase input: %v", err)
}
// Configure a blockchain with the given prestate
var (
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: new(big.Int).SetUint64(uint64(test.Context.Time)),
Difficulty: (*big.Int)(test.Context.Difficulty),
GasLimit: uint64(test.Context.GasLimit),
}
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
)
tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil {
t.Fatalf("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 {
t.Fatalf("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 {
t.Fatalf("failed to execute transaction: %v", err)
}
// Retrieve the trace result and compare against the etalon
res, err := tracer.GetResult()
if err != nil {
t.Fatalf("failed to retrieve trace result: %v", err)
}
ret := typeBuilder()
if err := json.Unmarshal(res, ret); err != nil {
t.Fatalf("failed to unmarshal trace result: %v", err)
}

if !jsonEqual(ret, test.Result, typeBuilder(), typeBuilder()) {
// uncomment this for easier debugging
// have, _ := json.MarshalIndent(ret, "", " ")
// want, _ := json.MarshalIndent(test.Result, "", " ")
// t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
}
})
}
}
Loading