github.com/shrimpyuk/bor@v0.2.15-0.20220224151350-fb4ec6020bae/eth/tracers/tracers_test.go (about) 1 // Copyright 2017 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 tracers 18 19 import ( 20 "crypto/ecdsa" 21 "crypto/rand" 22 "encoding/json" 23 "io/ioutil" 24 "math/big" 25 "path/filepath" 26 "reflect" 27 "strings" 28 "testing" 29 30 "github.com/ethereum/go-ethereum/common" 31 "github.com/ethereum/go-ethereum/common/hexutil" 32 "github.com/ethereum/go-ethereum/common/math" 33 "github.com/ethereum/go-ethereum/core" 34 "github.com/ethereum/go-ethereum/core/rawdb" 35 "github.com/ethereum/go-ethereum/core/types" 36 "github.com/ethereum/go-ethereum/core/vm" 37 "github.com/ethereum/go-ethereum/crypto" 38 "github.com/ethereum/go-ethereum/params" 39 "github.com/ethereum/go-ethereum/rlp" 40 "github.com/ethereum/go-ethereum/tests" 41 ) 42 43 // To generate a new callTracer test, copy paste the makeTest method below into 44 // a Geth console and call it with a transaction hash you which to export. 45 46 /* 47 // makeTest generates a callTracer test by running a prestate reassembled and a 48 // call trace run, assembling all the gathered information into a test case. 49 var makeTest = function(tx, rewind) { 50 // Generate the genesis block from the block, transaction and prestate data 51 var block = eth.getBlock(eth.getTransaction(tx).blockHash); 52 var genesis = eth.getBlock(block.parentHash); 53 54 delete genesis.gasUsed; 55 delete genesis.logsBloom; 56 delete genesis.parentHash; 57 delete genesis.receiptsRoot; 58 delete genesis.sha3Uncles; 59 delete genesis.size; 60 delete genesis.transactions; 61 delete genesis.transactionsRoot; 62 delete genesis.uncles; 63 64 genesis.gasLimit = genesis.gasLimit.toString(); 65 genesis.number = genesis.number.toString(); 66 genesis.timestamp = genesis.timestamp.toString(); 67 68 genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind}); 69 for (var key in genesis.alloc) { 70 genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString(); 71 } 72 genesis.config = admin.nodeInfo.protocols.eth.config; 73 74 // Generate the call trace and produce the test input 75 var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind}); 76 delete result.time; 77 78 console.log(JSON.stringify({ 79 genesis: genesis, 80 context: { 81 number: block.number.toString(), 82 difficulty: block.difficulty, 83 timestamp: block.timestamp.toString(), 84 gasLimit: block.gasLimit.toString(), 85 miner: block.miner, 86 }, 87 input: eth.getRawTransaction(tx), 88 result: result, 89 }, null, 2)); 90 } 91 */ 92 93 // callTrace is the result of a callTracer run. 94 type callTrace struct { 95 Type string `json:"type"` 96 From common.Address `json:"from"` 97 To common.Address `json:"to"` 98 Input hexutil.Bytes `json:"input"` 99 Output hexutil.Bytes `json:"output"` 100 Gas *hexutil.Uint64 `json:"gas,omitempty"` 101 GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` 102 Value *hexutil.Big `json:"value,omitempty"` 103 Error string `json:"error,omitempty"` 104 Calls []callTrace `json:"calls,omitempty"` 105 } 106 107 type callContext struct { 108 Number math.HexOrDecimal64 `json:"number"` 109 Difficulty *math.HexOrDecimal256 `json:"difficulty"` 110 Time math.HexOrDecimal64 `json:"timestamp"` 111 GasLimit math.HexOrDecimal64 `json:"gasLimit"` 112 Miner common.Address `json:"miner"` 113 } 114 115 // callTracerTest defines a single test to check the call tracer against. 116 type callTracerTest struct { 117 Genesis *core.Genesis `json:"genesis"` 118 Context *callContext `json:"context"` 119 Input string `json:"input"` 120 Result *callTrace `json:"result"` 121 } 122 123 func TestPrestateTracerCreate2(t *testing.T) { 124 unsignedTx := types.NewTransaction(1, common.HexToAddress("0x00000000000000000000000000000000deadbeef"), 125 new(big.Int), 5000000, big.NewInt(1), []byte{}) 126 127 privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) 128 if err != nil { 129 t.Fatalf("err %v", err) 130 } 131 signer := types.NewEIP155Signer(big.NewInt(1)) 132 tx, err := types.SignTx(unsignedTx, signer, privateKeyECDSA) 133 if err != nil { 134 t.Fatalf("err %v", err) 135 } 136 /** 137 This comes from one of the test-vectors on the Skinny Create2 - EIP 138 139 address 0x00000000000000000000000000000000deadbeef 140 salt 0x00000000000000000000000000000000000000000000000000000000cafebabe 141 init_code 0xdeadbeef 142 gas (assuming no mem expansion): 32006 143 result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7 144 */ 145 origin, _ := signer.Sender(tx) 146 txContext := vm.TxContext{ 147 Origin: origin, 148 GasPrice: big.NewInt(1), 149 } 150 context := vm.BlockContext{ 151 CanTransfer: core.CanTransfer, 152 Transfer: core.Transfer, 153 Coinbase: common.Address{}, 154 BlockNumber: new(big.Int).SetUint64(8000000), 155 Time: new(big.Int).SetUint64(5), 156 Difficulty: big.NewInt(0x30000), 157 GasLimit: uint64(6000000), 158 } 159 alloc := core.GenesisAlloc{} 160 161 // The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns 162 // the address 163 alloc[common.HexToAddress("0x00000000000000000000000000000000deadbeef")] = core.GenesisAccount{ 164 Nonce: 1, 165 Code: hexutil.MustDecode("0x63deadbeef60005263cafebabe6004601c6000F560005260206000F3"), 166 Balance: big.NewInt(1), 167 } 168 alloc[origin] = core.GenesisAccount{ 169 Nonce: 1, 170 Code: []byte{}, 171 Balance: big.NewInt(500000000000000), 172 } 173 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) 174 175 // Create the tracer, the EVM environment and run it 176 tracer, err := New("prestateTracer", new(Context)) 177 if err != nil { 178 t.Fatalf("failed to create call tracer: %v", err) 179 } 180 evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) 181 182 msg, err := tx.AsMessage(signer, nil) 183 if err != nil { 184 t.Fatalf("failed to prepare transaction for tracing: %v", err) 185 } 186 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 187 if _, err = st.TransitionDb(); err != nil { 188 t.Fatalf("failed to execute transaction: %v", err) 189 } 190 // Retrieve the trace result and compare against the etalon 191 res, err := tracer.GetResult() 192 if err != nil { 193 t.Fatalf("failed to retrieve trace result: %v", err) 194 } 195 ret := make(map[string]interface{}) 196 if err := json.Unmarshal(res, &ret); err != nil { 197 t.Fatalf("failed to unmarshal trace result: %v", err) 198 } 199 if _, has := ret["0x60f3f640a8508fc6a86d45df051962668e1e8ac7"]; !has { 200 t.Fatalf("Expected 0x60f3f640a8508fc6a86d45df051962668e1e8ac7 in result") 201 } 202 } 203 204 // Iterates over all the input-output datasets in the tracer test harness and 205 // runs the JavaScript tracers against them. 206 func TestCallTracerLegacy(t *testing.T) { 207 testCallTracer("callTracerLegacy", "call_tracer_legacy", t) 208 } 209 210 func testCallTracer(tracer string, dirPath string, t *testing.T) { 211 files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath)) 212 if err != nil { 213 t.Fatalf("failed to retrieve tracer test suite: %v", err) 214 } 215 for _, file := range files { 216 if !strings.HasSuffix(file.Name(), ".json") { 217 continue 218 } 219 file := file // capture range variable 220 t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { 221 t.Parallel() 222 223 // Call tracer test found, read if from disk 224 blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name())) 225 if err != nil { 226 t.Fatalf("failed to read testcase: %v", err) 227 } 228 test := new(callTracerTest) 229 if err := json.Unmarshal(blob, test); err != nil { 230 t.Fatalf("failed to parse testcase: %v", err) 231 } 232 // Configure a blockchain with the given prestate 233 tx := new(types.Transaction) 234 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 235 t.Fatalf("failed to parse testcase input: %v", err) 236 } 237 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 238 origin, _ := signer.Sender(tx) 239 txContext := vm.TxContext{ 240 Origin: origin, 241 GasPrice: tx.GasPrice(), 242 } 243 context := vm.BlockContext{ 244 CanTransfer: core.CanTransfer, 245 Transfer: core.Transfer, 246 Coinbase: test.Context.Miner, 247 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 248 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 249 Difficulty: (*big.Int)(test.Context.Difficulty), 250 GasLimit: uint64(test.Context.GasLimit), 251 } 252 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 253 254 // Create the tracer, the EVM environment and run it 255 tracer, err := New(tracer, new(Context)) 256 if err != nil { 257 t.Fatalf("failed to create call tracer: %v", err) 258 } 259 evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) 260 261 msg, err := tx.AsMessage(signer, nil) 262 if err != nil { 263 t.Fatalf("failed to prepare transaction for tracing: %v", err) 264 } 265 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 266 if _, err = st.TransitionDb(); err != nil { 267 t.Fatalf("failed to execute transaction: %v", err) 268 } 269 // Retrieve the trace result and compare against the etalon 270 res, err := tracer.GetResult() 271 if err != nil { 272 t.Fatalf("failed to retrieve trace result: %v", err) 273 } 274 ret := new(callTrace) 275 if err := json.Unmarshal(res, ret); err != nil { 276 t.Fatalf("failed to unmarshal trace result: %v", err) 277 } 278 279 if !jsonEqual(ret, test.Result) { 280 // uncomment this for easier debugging 281 //have, _ := json.MarshalIndent(ret, "", " ") 282 //want, _ := json.MarshalIndent(test.Result, "", " ") 283 //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want)) 284 t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result) 285 } 286 }) 287 } 288 } 289 290 func TestCallTracer(t *testing.T) { 291 testCallTracer("callTracer", "call_tracer", t) 292 } 293 294 // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to 295 // comparison 296 func jsonEqual(x, y interface{}) bool { 297 xTrace := new(callTrace) 298 yTrace := new(callTrace) 299 if xj, err := json.Marshal(x); err == nil { 300 json.Unmarshal(xj, xTrace) 301 } else { 302 return false 303 } 304 if yj, err := json.Marshal(y); err == nil { 305 json.Unmarshal(yj, yTrace) 306 } else { 307 return false 308 } 309 return reflect.DeepEqual(xTrace, yTrace) 310 } 311 312 func BenchmarkTransactionTrace(b *testing.B) { 313 key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 314 from := crypto.PubkeyToAddress(key.PublicKey) 315 gas := uint64(1000000) // 1M gas 316 to := common.HexToAddress("0x00000000000000000000000000000000deadbeef") 317 signer := types.LatestSignerForChainID(big.NewInt(1337)) 318 tx, err := types.SignNewTx(key, signer, 319 &types.LegacyTx{ 320 Nonce: 1, 321 GasPrice: big.NewInt(500), 322 Gas: gas, 323 To: &to, 324 }) 325 if err != nil { 326 b.Fatal(err) 327 } 328 txContext := vm.TxContext{ 329 Origin: from, 330 GasPrice: tx.GasPrice(), 331 } 332 context := vm.BlockContext{ 333 CanTransfer: core.CanTransfer, 334 Transfer: core.Transfer, 335 Coinbase: common.Address{}, 336 BlockNumber: new(big.Int).SetUint64(uint64(5)), 337 Time: new(big.Int).SetUint64(uint64(5)), 338 Difficulty: big.NewInt(0xffffffff), 339 GasLimit: gas, 340 } 341 alloc := core.GenesisAlloc{} 342 // The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns 343 // the address 344 loop := []byte{ 345 byte(vm.JUMPDEST), // [ count ] 346 byte(vm.PUSH1), 0, // jumpdestination 347 byte(vm.JUMP), 348 } 349 alloc[common.HexToAddress("0x00000000000000000000000000000000deadbeef")] = core.GenesisAccount{ 350 Nonce: 1, 351 Code: loop, 352 Balance: big.NewInt(1), 353 } 354 alloc[from] = core.GenesisAccount{ 355 Nonce: 1, 356 Code: []byte{}, 357 Balance: big.NewInt(500000000000000), 358 } 359 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) 360 // Create the tracer, the EVM environment and run it 361 tracer := vm.NewStructLogger(&vm.LogConfig{ 362 Debug: false, 363 //DisableStorage: true, 364 //EnableMemory: false, 365 //EnableReturnData: false, 366 }) 367 evm := vm.NewEVM(context, txContext, statedb, params.AllEthashProtocolChanges, vm.Config{Debug: true, Tracer: tracer}) 368 msg, err := tx.AsMessage(signer, nil) 369 if err != nil { 370 b.Fatalf("failed to prepare transaction for tracing: %v", err) 371 } 372 b.ResetTimer() 373 b.ReportAllocs() 374 375 for i := 0; i < b.N; i++ { 376 snap := statedb.Snapshot() 377 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 378 _, err = st.TransitionDb() 379 if err != nil { 380 b.Fatal(err) 381 } 382 statedb.RevertToSnapshot(snap) 383 if have, want := len(tracer.StructLogs()), 244752; have != want { 384 b.Fatalf("trace wrong, want %d steps, have %d", want, have) 385 } 386 tracer.Reset() 387 } 388 } 389 390 func BenchmarkTracers(b *testing.B) { 391 files, err := ioutil.ReadDir(filepath.Join("testdata", "call_tracer")) 392 if err != nil { 393 b.Fatalf("failed to retrieve tracer test suite: %v", err) 394 } 395 for _, file := range files { 396 if !strings.HasSuffix(file.Name(), ".json") { 397 continue 398 } 399 file := file // capture range variable 400 b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { 401 blob, err := ioutil.ReadFile(filepath.Join("testdata", "call_tracer", file.Name())) 402 if err != nil { 403 b.Fatalf("failed to read testcase: %v", err) 404 } 405 test := new(callTracerTest) 406 if err := json.Unmarshal(blob, test); err != nil { 407 b.Fatalf("failed to parse testcase: %v", err) 408 } 409 benchTracer("callTracer", test, b) 410 }) 411 } 412 } 413 414 func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { 415 // Configure a blockchain with the given prestate 416 tx := new(types.Transaction) 417 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 418 b.Fatalf("failed to parse testcase input: %v", err) 419 } 420 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 421 msg, err := tx.AsMessage(signer, nil) 422 if err != nil { 423 b.Fatalf("failed to prepare transaction for tracing: %v", err) 424 } 425 origin, _ := signer.Sender(tx) 426 txContext := vm.TxContext{ 427 Origin: origin, 428 GasPrice: tx.GasPrice(), 429 } 430 context := vm.BlockContext{ 431 CanTransfer: core.CanTransfer, 432 Transfer: core.Transfer, 433 Coinbase: test.Context.Miner, 434 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 435 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 436 Difficulty: (*big.Int)(test.Context.Difficulty), 437 GasLimit: uint64(test.Context.GasLimit), 438 } 439 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 440 441 // Create the tracer, the EVM environment and run it 442 tracer, err := New(tracerName, new(Context)) 443 if err != nil { 444 b.Fatalf("failed to create call tracer: %v", err) 445 } 446 evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) 447 448 b.ReportAllocs() 449 b.ResetTimer() 450 for i := 0; i < b.N; i++ { 451 snap := statedb.Snapshot() 452 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 453 if _, err = st.TransitionDb(); err != nil { 454 b.Fatalf("failed to execute transaction: %v", err) 455 } 456 statedb.RevertToSnapshot(snap) 457 } 458 }