github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/eth/tracers/internal/tracetest/flat_calltrace_test.go (about) 1 package tracetest 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "math/big" 7 "os" 8 "path/filepath" 9 "reflect" 10 "strings" 11 "testing" 12 13 "github.com/ethereum/go-ethereum/common" 14 "github.com/ethereum/go-ethereum/common/hexutil" 15 "github.com/ethereum/go-ethereum/core" 16 "github.com/ethereum/go-ethereum/core/rawdb" 17 "github.com/ethereum/go-ethereum/core/types" 18 "github.com/ethereum/go-ethereum/core/vm" 19 "github.com/ethereum/go-ethereum/eth/tracers" 20 "github.com/ethereum/go-ethereum/rlp" 21 "github.com/ethereum/go-ethereum/tests" 22 ) 23 24 // flatCallTrace is the result of a callTracerParity run. 25 type flatCallTrace struct { 26 Action flatCallTraceAction `json:"action"` 27 BlockHash common.Hash `json:"-"` 28 BlockNumber uint64 `json:"-"` 29 Error string `json:"error,omitempty"` 30 Result flatCallTraceResult `json:"result,omitempty"` 31 Subtraces int `json:"subtraces"` 32 TraceAddress []int `json:"traceAddress"` 33 TransactionHash common.Hash `json:"-"` 34 TransactionPosition uint64 `json:"-"` 35 Type string `json:"type"` 36 Time string `json:"-"` 37 } 38 39 type flatCallTraceAction struct { 40 Author common.Address `json:"author,omitempty"` 41 RewardType string `json:"rewardType,omitempty"` 42 SelfDestructed common.Address `json:"address,omitempty"` 43 Balance hexutil.Big `json:"balance,omitempty"` 44 CallType string `json:"callType,omitempty"` 45 CreationMethod string `json:"creationMethod,omitempty"` 46 From common.Address `json:"from,omitempty"` 47 Gas hexutil.Uint64 `json:"gas,omitempty"` 48 Init hexutil.Bytes `json:"init,omitempty"` 49 Input hexutil.Bytes `json:"input,omitempty"` 50 RefundAddress common.Address `json:"refundAddress,omitempty"` 51 To common.Address `json:"to,omitempty"` 52 Value hexutil.Big `json:"value,omitempty"` 53 } 54 55 type flatCallTraceResult struct { 56 Address common.Address `json:"address,omitempty"` 57 Code hexutil.Bytes `json:"code,omitempty"` 58 GasUsed hexutil.Uint64 `json:"gasUsed,omitempty"` 59 Output hexutil.Bytes `json:"output,omitempty"` 60 } 61 62 // flatCallTracerTest defines a single test to check the call tracer against. 63 type flatCallTracerTest struct { 64 Genesis *core.Genesis `json:"genesis"` 65 Context *callContext `json:"context"` 66 Input string `json:"input"` 67 TracerConfig json.RawMessage `json:"tracerConfig"` 68 Result []flatCallTrace `json:"result"` 69 } 70 71 func flatCallTracerTestRunner(tracerName string, filename string, dirPath string, t testing.TB) error { 72 // Call tracer test found, read if from disk 73 blob, err := os.ReadFile(filepath.Join("testdata", dirPath, filename)) 74 if err != nil { 75 return fmt.Errorf("failed to read testcase: %v", err) 76 } 77 test := new(flatCallTracerTest) 78 if err := json.Unmarshal(blob, test); err != nil { 79 return fmt.Errorf("failed to parse testcase: %v", err) 80 } 81 // Configure a blockchain with the given prestate 82 tx := new(types.Transaction) 83 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 84 return fmt.Errorf("failed to parse testcase input: %v", err) 85 } 86 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) 87 context := test.Context.toBlockContext(test.Genesis) 88 state := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) 89 defer state.Close() 90 91 // Create the tracer, the EVM environment and run it 92 tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) 93 if err != nil { 94 return fmt.Errorf("failed to create call tracer: %v", err) 95 } 96 97 state.StateDB.SetLogger(tracer.Hooks) 98 msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) 99 if err != nil { 100 return fmt.Errorf("failed to prepare transaction for tracing: %v", err) 101 } 102 evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks}) 103 tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) 104 vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 105 if err != nil { 106 return fmt.Errorf("failed to execute transaction: %v", err) 107 } 108 tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) 109 110 // Retrieve the trace result and compare against the etalon 111 res, err := tracer.GetResult() 112 if err != nil { 113 return fmt.Errorf("failed to retrieve trace result: %v", err) 114 } 115 ret := make([]flatCallTrace, 0) 116 if err := json.Unmarshal(res, &ret); err != nil { 117 return fmt.Errorf("failed to unmarshal trace result: %v", err) 118 } 119 if !jsonEqualFlat(ret, test.Result) { 120 t.Logf("test %s failed", filename) 121 122 // uncomment this for easier debugging 123 // have, _ := json.MarshalIndent(ret, "", " ") 124 // want, _ := json.MarshalIndent(test.Result, "", " ") 125 // t.Logf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want)) 126 127 // uncomment this for harder debugging <3 meowsbits 128 // lines := deep.Equal(ret, test.Result) 129 // for _, l := range lines { 130 // t.Logf("%s", l) 131 // t.FailNow() 132 // } 133 134 t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result) 135 } 136 return nil 137 } 138 139 // Iterates over all the input-output datasets in the tracer parity test harness and 140 // runs the Native tracer against them. 141 func TestFlatCallTracerNative(t *testing.T) { 142 testFlatCallTracer("flatCallTracer", "call_tracer_flat", t) 143 } 144 145 func testFlatCallTracer(tracerName string, dirPath string, t *testing.T) { 146 files, err := os.ReadDir(filepath.Join("testdata", dirPath)) 147 if err != nil { 148 t.Fatalf("failed to retrieve tracer test suite: %v", err) 149 } 150 for _, file := range files { 151 if !strings.HasSuffix(file.Name(), ".json") { 152 continue 153 } 154 file := file // capture range variable 155 t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { 156 t.Parallel() 157 158 err := flatCallTracerTestRunner(tracerName, file.Name(), dirPath, t) 159 if err != nil { 160 t.Fatal(err) 161 } 162 }) 163 } 164 } 165 166 // jsonEqualFlat is similar to reflect.DeepEqual, but does a 'bounce' via json prior to 167 // comparison 168 func jsonEqualFlat(x, y interface{}) bool { 169 xTrace := new([]flatCallTrace) 170 yTrace := new([]flatCallTrace) 171 if xj, err := json.Marshal(x); err == nil { 172 json.Unmarshal(xj, xTrace) 173 } else { 174 return false 175 } 176 if yj, err := json.Marshal(y); err == nil { 177 json.Unmarshal(yj, yTrace) 178 } else { 179 return false 180 } 181 return reflect.DeepEqual(xTrace, yTrace) 182 } 183 184 func BenchmarkFlatCallTracer(b *testing.B) { 185 files, err := filepath.Glob("testdata/call_tracer_flat/*.json") 186 if err != nil { 187 b.Fatalf("failed to read testdata: %v", err) 188 } 189 190 for _, file := range files { 191 filename := strings.TrimPrefix(file, "testdata/call_tracer_flat/") 192 b.Run(camel(strings.TrimSuffix(filename, ".json")), func(b *testing.B) { 193 for n := 0; n < b.N; n++ { 194 err := flatCallTracerTestRunner("flatCallTracer", filename, "call_tracer_flat", b) 195 if err != nil { 196 b.Fatal(err) 197 } 198 } 199 }) 200 } 201 }