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