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