github.com/calmw/ethereum@v0.1.1/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/calmw/ethereum/common" 14 "github.com/calmw/ethereum/common/hexutil" 15 "github.com/calmw/ethereum/core" 16 "github.com/calmw/ethereum/core/rawdb" 17 "github.com/calmw/ethereum/core/types" 18 "github.com/calmw/ethereum/core/vm" 19 "github.com/calmw/ethereum/rlp" 20 "github.com/calmw/ethereum/tests" 21 22 // Force-load the native, to trigger registration 23 "github.com/calmw/ethereum/eth/tracers" 24 ) 25 26 // flatCallTrace is the result of a callTracerParity run. 27 type flatCallTrace struct { 28 Action flatCallTraceAction `json:"action"` 29 BlockHash common.Hash `json:"-"` 30 BlockNumber uint64 `json:"-"` 31 Error string `json:"error,omitempty"` 32 Result flatCallTraceResult `json:"result,omitempty"` 33 Subtraces int `json:"subtraces"` 34 TraceAddress []int `json:"traceAddress"` 35 TransactionHash common.Hash `json:"-"` 36 TransactionPosition uint64 `json:"-"` 37 Type string `json:"type"` 38 Time string `json:"-"` 39 } 40 41 type flatCallTraceAction struct { 42 Author common.Address `json:"author,omitempty"` 43 RewardType string `json:"rewardType,omitempty"` 44 SelfDestructed common.Address `json:"address,omitempty"` 45 Balance hexutil.Big `json:"balance,omitempty"` 46 CallType string `json:"callType,omitempty"` 47 CreationMethod string `json:"creationMethod,omitempty"` 48 From common.Address `json:"from,omitempty"` 49 Gas hexutil.Uint64 `json:"gas,omitempty"` 50 Init hexutil.Bytes `json:"init,omitempty"` 51 Input hexutil.Bytes `json:"input,omitempty"` 52 RefundAddress common.Address `json:"refundAddress,omitempty"` 53 To common.Address `json:"to,omitempty"` 54 Value hexutil.Big `json:"value,omitempty"` 55 } 56 57 type flatCallTraceResult struct { 58 Address common.Address `json:"address,omitempty"` 59 Code hexutil.Bytes `json:"code,omitempty"` 60 GasUsed hexutil.Uint64 `json:"gasUsed,omitempty"` 61 Output hexutil.Bytes `json:"output,omitempty"` 62 } 63 64 // flatCallTracerTest defines a single test to check the call tracer against. 65 type flatCallTracerTest struct { 66 Genesis core.Genesis `json:"genesis"` 67 Context callContext `json:"context"` 68 Input string `json:"input"` 69 TracerConfig json.RawMessage `json:"tracerConfig"` 70 Result []flatCallTrace `json:"result"` 71 } 72 73 func flatCallTracerTestRunner(tracerName string, filename string, dirPath string, t testing.TB) error { 74 // Call tracer test found, read if from disk 75 blob, err := os.ReadFile(filepath.Join("testdata", dirPath, filename)) 76 if err != nil { 77 return fmt.Errorf("failed to read testcase: %v", err) 78 } 79 test := new(flatCallTracerTest) 80 if err := json.Unmarshal(blob, test); err != nil { 81 return fmt.Errorf("failed to parse testcase: %v", err) 82 } 83 // Configure a blockchain with the given prestate 84 tx := new(types.Transaction) 85 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 86 return fmt.Errorf("failed to parse testcase input: %v", err) 87 } 88 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) 89 origin, _ := signer.Sender(tx) 90 txContext := vm.TxContext{ 91 Origin: origin, 92 GasPrice: tx.GasPrice(), 93 } 94 context := vm.BlockContext{ 95 CanTransfer: core.CanTransfer, 96 Transfer: core.Transfer, 97 Coinbase: test.Context.Miner, 98 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 99 Time: uint64(test.Context.Time), 100 Difficulty: (*big.Int)(test.Context.Difficulty), 101 GasLimit: uint64(test.Context.GasLimit), 102 } 103 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 104 105 // Create the tracer, the EVM environment and run it 106 tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) 107 if err != nil { 108 return fmt.Errorf("failed to create call tracer: %v", err) 109 } 110 evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) 111 112 msg, err := core.TransactionToMessage(tx, signer, nil) 113 if err != nil { 114 return fmt.Errorf("failed to prepare transaction for tracing: %v", err) 115 } 116 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 117 118 if _, err = st.TransitionDb(); err != nil { 119 return fmt.Errorf("failed to execute transaction: %v", err) 120 } 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("tracer name: %s", tracerName) 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 file := file // capture range variable 167 t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { 168 t.Parallel() 169 170 err := flatCallTracerTestRunner(tracerName, file.Name(), dirPath, t) 171 if err != nil { 172 t.Fatal(err) 173 } 174 }) 175 } 176 } 177 178 // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to 179 // comparison 180 func jsonEqualFlat(x, y interface{}) bool { 181 xTrace := new([]flatCallTrace) 182 yTrace := new([]flatCallTrace) 183 if xj, err := json.Marshal(x); err == nil { 184 json.Unmarshal(xj, xTrace) 185 } else { 186 return false 187 } 188 if yj, err := json.Marshal(y); err == nil { 189 json.Unmarshal(yj, yTrace) 190 } else { 191 return false 192 } 193 return reflect.DeepEqual(xTrace, yTrace) 194 } 195 196 func BenchmarkFlatCallTracer(b *testing.B) { 197 files, err := filepath.Glob("testdata/call_tracer_flat/*.json") 198 if err != nil { 199 b.Fatalf("failed to read testdata: %v", err) 200 } 201 202 for _, file := range files { 203 filename := strings.TrimPrefix(file, "testdata/call_tracer_flat/") 204 b.Run(camel(strings.TrimSuffix(filename, ".json")), func(b *testing.B) { 205 for n := 0; n < b.N; n++ { 206 err := flatCallTracerTestRunner("flatCallTracer", filename, "call_tracer_flat", b) 207 if err != nil { 208 b.Fatal(err) 209 } 210 } 211 }) 212 } 213 }