github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/eth/tracers/internal/tracetest/calltrace_test.go (about) 1 // Copyright 2021 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 "io/ioutil" 22 "math/big" 23 "path/filepath" 24 "reflect" 25 "strings" 26 "testing" 27 "unicode" 28 29 "github.com/scroll-tech/go-ethereum/common" 30 "github.com/scroll-tech/go-ethereum/common/hexutil" 31 "github.com/scroll-tech/go-ethereum/common/math" 32 "github.com/scroll-tech/go-ethereum/core" 33 "github.com/scroll-tech/go-ethereum/core/rawdb" 34 "github.com/scroll-tech/go-ethereum/core/types" 35 "github.com/scroll-tech/go-ethereum/core/vm" 36 "github.com/scroll-tech/go-ethereum/crypto" 37 "github.com/scroll-tech/go-ethereum/eth/tracers" 38 "github.com/scroll-tech/go-ethereum/params" 39 "github.com/scroll-tech/go-ethereum/rlp" 40 "github.com/scroll-tech/go-ethereum/rollup/fees" 41 "github.com/scroll-tech/go-ethereum/tests" 42 43 // Force-load native and js pacakges, to trigger registration 44 _ "github.com/scroll-tech/go-ethereum/eth/tracers/js" 45 _ "github.com/scroll-tech/go-ethereum/eth/tracers/native" 46 ) 47 48 // To generate a new callTracer test, copy paste the makeTest method below into 49 // a Geth console and call it with a transaction hash you which to export. 50 51 /* 52 // makeTest generates a callTracer test by running a prestate reassembled and a 53 // call trace run, assembling all the gathered information into a test case. 54 var makeTest = function(tx, rewind) { 55 // Generate the genesis block from the block, transaction and prestate data 56 var block = eth.getBlock(eth.getTransaction(tx).blockHash); 57 var genesis = eth.getBlock(block.parentHash); 58 59 delete genesis.gasUsed; 60 delete genesis.logsBloom; 61 delete genesis.parentHash; 62 delete genesis.receiptsRoot; 63 delete genesis.sha3Uncles; 64 delete genesis.size; 65 delete genesis.transactions; 66 delete genesis.transactionsRoot; 67 delete genesis.uncles; 68 69 genesis.gasLimit = genesis.gasLimit.toString(); 70 genesis.number = genesis.number.toString(); 71 genesis.timestamp = genesis.timestamp.toString(); 72 73 genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind}); 74 for (var key in genesis.alloc) { 75 genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString(); 76 } 77 genesis.config = admin.nodeInfo.protocols.eth.config; 78 79 // Generate the call trace and produce the test input 80 var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind}); 81 delete result.time; 82 83 console.log(JSON.stringify({ 84 genesis: genesis, 85 context: { 86 number: block.number.toString(), 87 difficulty: block.difficulty, 88 timestamp: block.timestamp.toString(), 89 gasLimit: block.gasLimit.toString(), 90 miner: block.miner, 91 }, 92 input: eth.getRawTransaction(tx), 93 result: result, 94 }, null, 2)); 95 } 96 */ 97 98 type callContext struct { 99 Number math.HexOrDecimal64 `json:"number"` 100 Difficulty *math.HexOrDecimal256 `json:"difficulty"` 101 Time math.HexOrDecimal64 `json:"timestamp"` 102 GasLimit math.HexOrDecimal64 `json:"gasLimit"` 103 Miner common.Address `json:"miner"` 104 } 105 106 // callTrace is the result of a callTracer run. 107 type callTrace struct { 108 Type string `json:"type"` 109 From common.Address `json:"from"` 110 To common.Address `json:"to"` 111 Input hexutil.Bytes `json:"input"` 112 Output hexutil.Bytes `json:"output"` 113 Gas *hexutil.Uint64 `json:"gas,omitempty"` 114 GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` 115 Value *hexutil.Big `json:"value,omitempty"` 116 Error string `json:"error,omitempty"` 117 Calls []callTrace `json:"calls,omitempty"` 118 } 119 120 // callTracerTest defines a single test to check the call tracer against. 121 type callTracerTest struct { 122 Genesis *core.Genesis `json:"genesis"` 123 Context *callContext `json:"context"` 124 Input string `json:"input"` 125 Result *callTrace `json:"result"` 126 } 127 128 // Iterates over all the input-output datasets in the tracer test harness and 129 // runs the JavaScript tracers against them. 130 func TestCallTracerLegacy(t *testing.T) { 131 testCallTracer("callTracerLegacy", "call_tracer_legacy", t) 132 } 133 134 func TestCallTracerJs(t *testing.T) { 135 testCallTracer("callTracerJs", "call_tracer", t) 136 } 137 138 func TestCallTracerNative(t *testing.T) { 139 testCallTracer("callTracer", "call_tracer", t) 140 } 141 142 func testCallTracer(tracerName string, dirPath string, t *testing.T) { 143 files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath)) 144 if err != nil { 145 t.Fatalf("failed to retrieve tracer test suite: %v", err) 146 } 147 for _, file := range files { 148 if !strings.HasSuffix(file.Name(), ".json") { 149 continue 150 } 151 file := file // capture range variable 152 t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { 153 t.Parallel() 154 155 var ( 156 test = new(callTracerTest) 157 tx = new(types.Transaction) 158 ) 159 // Call tracer test found, read if from disk 160 if blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil { 161 t.Fatalf("failed to read testcase: %v", err) 162 } else if err := json.Unmarshal(blob, test); err != nil { 163 t.Fatalf("failed to parse testcase: %v", err) 164 } 165 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 166 t.Fatalf("failed to parse testcase input: %v", err) 167 } 168 // Configure a blockchain with the given prestate 169 var ( 170 signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 171 origin, _ = signer.Sender(tx) 172 txContext = vm.TxContext{ 173 Origin: origin, 174 GasPrice: tx.GasPrice(), 175 } 176 context = vm.BlockContext{ 177 CanTransfer: core.CanTransfer, 178 Transfer: core.Transfer, 179 Coinbase: test.Context.Miner, 180 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 181 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 182 Difficulty: (*big.Int)(test.Context.Difficulty), 183 GasLimit: uint64(test.Context.GasLimit), 184 } 185 _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 186 ) 187 tracer, err := tracers.New(tracerName, new(tracers.Context)) 188 if err != nil { 189 t.Fatalf("failed to create call tracer: %v", err) 190 } 191 evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) 192 msg, err := tx.AsMessage(signer, nil) 193 if err != nil { 194 t.Fatalf("failed to prepare transaction for tracing: %v", err) 195 } 196 l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) 197 if err != nil { 198 t.Fatalf("failed to calculate l1DataFee: %v", err) 199 } 200 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) 201 if _, err = st.TransitionDb(); err != nil { 202 t.Fatalf("failed to execute transaction: %v", err) 203 } 204 // Retrieve the trace result and compare against the etalon 205 res, err := tracer.GetResult() 206 if err != nil { 207 t.Fatalf("failed to retrieve trace result: %v", err) 208 } 209 ret := new(callTrace) 210 if err := json.Unmarshal(res, ret); err != nil { 211 t.Fatalf("failed to unmarshal trace result: %v", err) 212 } 213 214 if !jsonEqual(ret, test.Result) { 215 // uncomment this for easier debugging 216 //have, _ := json.MarshalIndent(ret, "", " ") 217 //want, _ := json.MarshalIndent(test.Result, "", " ") 218 //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want)) 219 t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result) 220 } 221 }) 222 } 223 } 224 225 // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to 226 // comparison 227 func jsonEqual(x, y interface{}) bool { 228 xTrace := new(callTrace) 229 yTrace := new(callTrace) 230 if xj, err := json.Marshal(x); err == nil { 231 json.Unmarshal(xj, xTrace) 232 } else { 233 return false 234 } 235 if yj, err := json.Marshal(y); err == nil { 236 json.Unmarshal(yj, yTrace) 237 } else { 238 return false 239 } 240 return reflect.DeepEqual(xTrace, yTrace) 241 } 242 243 // camel converts a snake cased input string into a camel cased output. 244 func camel(str string) string { 245 pieces := strings.Split(str, "_") 246 for i := 1; i < len(pieces); i++ { 247 pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:] 248 } 249 return strings.Join(pieces, "") 250 } 251 func BenchmarkTracers(b *testing.B) { 252 files, err := ioutil.ReadDir(filepath.Join("testdata", "call_tracer")) 253 if err != nil { 254 b.Fatalf("failed to retrieve tracer test suite: %v", err) 255 } 256 for _, file := range files { 257 if !strings.HasSuffix(file.Name(), ".json") { 258 continue 259 } 260 file := file // capture range variable 261 b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { 262 blob, err := ioutil.ReadFile(filepath.Join("testdata", "call_tracer", file.Name())) 263 if err != nil { 264 b.Fatalf("failed to read testcase: %v", err) 265 } 266 test := new(callTracerTest) 267 if err := json.Unmarshal(blob, test); err != nil { 268 b.Fatalf("failed to parse testcase: %v", err) 269 } 270 benchTracer("callTracerNative", test, b) 271 }) 272 } 273 } 274 275 func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { 276 // Configure a blockchain with the given prestate 277 tx := new(types.Transaction) 278 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 279 b.Fatalf("failed to parse testcase input: %v", err) 280 } 281 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 282 msg, err := tx.AsMessage(signer, nil) 283 if err != nil { 284 b.Fatalf("failed to prepare transaction for tracing: %v", err) 285 } 286 origin, _ := signer.Sender(tx) 287 txContext := vm.TxContext{ 288 Origin: origin, 289 GasPrice: tx.GasPrice(), 290 } 291 context := vm.BlockContext{ 292 CanTransfer: core.CanTransfer, 293 Transfer: core.Transfer, 294 Coinbase: test.Context.Miner, 295 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 296 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 297 Difficulty: (*big.Int)(test.Context.Difficulty), 298 GasLimit: uint64(test.Context.GasLimit), 299 } 300 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 301 302 b.ReportAllocs() 303 b.ResetTimer() 304 for i := 0; i < b.N; i++ { 305 tracer, err := tracers.New(tracerName, new(tracers.Context)) 306 if err != nil { 307 b.Fatalf("failed to create call tracer: %v", err) 308 } 309 evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) 310 snap := statedb.Snapshot() 311 l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) 312 if err != nil { 313 b.Fatalf("failed to calculate l1DataFee: %v", err) 314 } 315 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) 316 if _, err = st.TransitionDb(); err != nil { 317 b.Fatalf("failed to execute transaction: %v", err) 318 } 319 if _, err = tracer.GetResult(); err != nil { 320 b.Fatal(err) 321 } 322 statedb.RevertToSnapshot(snap) 323 } 324 } 325 326 // TestZeroValueToNotExitCall tests the calltracer(s) on the following: 327 // Tx to A, A calls B with zero value. B does not already exist. 328 // Expected: that enter/exit is invoked and the inner call is shown in the result 329 func TestZeroValueToNotExitCall(t *testing.T) { 330 var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") 331 privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef") 332 if err != nil { 333 t.Fatalf("err %v", err) 334 } 335 signer := types.NewEIP155Signer(big.NewInt(1)) 336 tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{ 337 GasPrice: big.NewInt(0), 338 Gas: 50000, 339 To: &to, 340 }) 341 if err != nil { 342 t.Fatalf("err %v", err) 343 } 344 origin, _ := signer.Sender(tx) 345 txContext := vm.TxContext{ 346 Origin: origin, 347 GasPrice: big.NewInt(1), 348 } 349 context := vm.BlockContext{ 350 CanTransfer: core.CanTransfer, 351 Transfer: core.Transfer, 352 Coinbase: common.Address{}, 353 BlockNumber: new(big.Int).SetUint64(8000000), 354 Time: new(big.Int).SetUint64(5), 355 Difficulty: big.NewInt(0x30000), 356 GasLimit: uint64(6000000), 357 } 358 var code = []byte{ 359 byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero 360 byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS 361 byte(vm.CALL), 362 } 363 var alloc = core.GenesisAlloc{ 364 to: core.GenesisAccount{ 365 Nonce: 1, 366 Code: code, 367 }, 368 origin: core.GenesisAccount{ 369 Nonce: 0, 370 Balance: big.NewInt(500000000000000), 371 }, 372 } 373 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) 374 // Create the tracer, the EVM environment and run it 375 tracer, err := tracers.New("callTracer", nil) 376 if err != nil { 377 t.Fatalf("failed to create call tracer: %v", err) 378 } 379 evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) 380 msg, err := tx.AsMessage(signer, nil) 381 if err != nil { 382 t.Fatalf("failed to prepare transaction for tracing: %v", err) 383 } 384 l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) 385 if err != nil { 386 t.Fatalf("failed to calculate l1DataFee: %v", err) 387 } 388 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) 389 if _, err = st.TransitionDb(); err != nil { 390 t.Fatalf("failed to execute transaction: %v", err) 391 } 392 // Retrieve the trace result and compare against the etalon 393 res, err := tracer.GetResult() 394 if err != nil { 395 t.Fatalf("failed to retrieve trace result: %v", err) 396 } 397 have := new(callTrace) 398 if err := json.Unmarshal(res, have); err != nil { 399 t.Fatalf("failed to unmarshal trace result: %v", err) 400 } 401 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"}]}` 402 want := new(callTrace) 403 json.Unmarshal([]byte(wantStr), want) 404 if !jsonEqual(have, want) { 405 t.Error("have != want") 406 } 407 }