github.com/theQRL/go-zond@v0.2.1/zond/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/theQRL/go-zond/common" 14 "github.com/theQRL/go-zond/common/hexutil" 15 "github.com/theQRL/go-zond/core" 16 "github.com/theQRL/go-zond/core/rawdb" 17 "github.com/theQRL/go-zond/core/types" 18 "github.com/theQRL/go-zond/core/vm" 19 "github.com/theQRL/go-zond/params" 20 "github.com/theQRL/go-zond/rlp" 21 "github.com/theQRL/go-zond/tests" 22 23 // Force-load the native, to trigger registration 24 "github.com/theQRL/go-zond/zond/tracers" 25 ) 26 27 // flatCallTrace is the result of a callTracerParity run. 28 type flatCallTrace struct { 29 Action flatCallTraceAction `json:"action"` 30 BlockHash common.Hash `json:"-"` 31 BlockNumber uint64 `json:"-"` 32 Error string `json:"error,omitempty"` 33 Result flatCallTraceResult `json:"result,omitempty"` 34 Subtraces int `json:"subtraces"` 35 TraceAddress []int `json:"traceAddress"` 36 TransactionHash common.Hash `json:"-"` 37 TransactionPosition uint64 `json:"-"` 38 Type string `json:"type"` 39 Time string `json:"-"` 40 } 41 42 type flatCallTraceAction struct { 43 Author common.Address `json:"author,omitempty"` 44 RewardType string `json:"rewardType,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) 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 GasLimit: uint64(test.Context.GasLimit), 101 BaseFee: big.NewInt(params.InitialBaseFee), 102 } 103 triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) 104 defer triedb.Close() 105 106 // Create the tracer, the ZVM environment and run it 107 tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) 108 if err != nil { 109 return fmt.Errorf("failed to create call tracer: %v", err) 110 } 111 zvm := vm.NewZVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) 112 113 msg, err := core.TransactionToMessage(tx, signer, nil) 114 if err != nil { 115 return fmt.Errorf("failed to prepare transaction for tracing: %v", err) 116 } 117 st := core.NewStateTransition(zvm, msg, new(core.GasPool).AddGas(tx.Gas())) 118 119 if _, err = st.TransitionDb(); err != nil { 120 return fmt.Errorf("failed to execute transaction: %v", err) 121 } 122 123 // Retrieve the trace result and compare against the etalon 124 res, err := tracer.GetResult() 125 if err != nil { 126 return fmt.Errorf("failed to retrieve trace result: %v", err) 127 } 128 ret := make([]flatCallTrace, 0) 129 if err := json.Unmarshal(res, &ret); err != nil { 130 return fmt.Errorf("failed to unmarshal trace result: %v", err) 131 } 132 if !jsonEqualFlat(ret, test.Result) { 133 t.Logf("tracer name: %s", tracerName) 134 135 // uncomment this for easier debugging 136 // have, _ := json.MarshalIndent(ret, "", " ") 137 // want, _ := json.MarshalIndent(test.Result, "", " ") 138 // t.Logf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want)) 139 140 // uncomment this for harder debugging <3 meowsbits 141 // lines := deep.Equal(ret, test.Result) 142 // for _, l := range lines { 143 // t.Logf("%s", l) 144 // t.FailNow() 145 // } 146 147 t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result) 148 } 149 return nil 150 } 151 152 // TODO(now.youtrack.cloud/issue/TGZ-13) 153 // Iterates over all the input-output datasets in the tracer parity test harness and 154 // runs the Native tracer against them. 155 func TestFlatCallTracerNative(t *testing.T) { 156 testFlatCallTracer("flatCallTracer", "call_tracer_flat", t) 157 } 158 159 func testFlatCallTracer(tracerName string, dirPath string, t *testing.T) { 160 files, err := os.ReadDir(filepath.Join("testdata", dirPath)) 161 if err != nil { 162 t.Fatalf("failed to retrieve tracer test suite: %v", err) 163 } 164 for _, file := range files { 165 if !strings.HasSuffix(file.Name(), ".json") { 166 continue 167 } 168 file := file // capture range variable 169 t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { 170 t.Parallel() 171 172 err := flatCallTracerTestRunner(tracerName, file.Name(), dirPath, t) 173 if err != nil { 174 t.Fatal(err) 175 } 176 }) 177 } 178 } 179 180 // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to 181 // comparison 182 func jsonEqualFlat(x, y interface{}) bool { 183 xTrace := new([]flatCallTrace) 184 yTrace := new([]flatCallTrace) 185 if xj, err := json.Marshal(x); err == nil { 186 json.Unmarshal(xj, xTrace) 187 } else { 188 return false 189 } 190 if yj, err := json.Marshal(y); err == nil { 191 json.Unmarshal(yj, yTrace) 192 } else { 193 return false 194 } 195 return reflect.DeepEqual(xTrace, yTrace) 196 } 197 198 func BenchmarkFlatCallTracer(b *testing.B) { 199 files, err := filepath.Glob("testdata/call_tracer_flat/*.json") 200 if err != nil { 201 b.Fatalf("failed to read testdata: %v", err) 202 } 203 204 for _, file := range files { 205 filename := strings.TrimPrefix(file, "testdata/call_tracer_flat/") 206 b.Run(camel(strings.TrimSuffix(filename, ".json")), func(b *testing.B) { 207 for n := 0; n < b.N; n++ { 208 err := flatCallTracerTestRunner("flatCallTracer", filename, "call_tracer_flat", b) 209 if err != nil { 210 b.Fatal(err) 211 } 212 } 213 }) 214 } 215 }