github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/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 "context" 21 "encoding/json" 22 "io/ioutil" 23 "math/big" 24 "path/filepath" 25 "reflect" 26 "strings" 27 "testing" 28 "unicode" 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/eth/tracers" 39 "github.com/ethereum/go-ethereum/params" 40 "github.com/ethereum/go-ethereum/rlp" 41 "github.com/ethereum/go-ethereum/tests" 42 43 // Force-load native and js pacakges, to trigger registration 44 _ "github.com/ethereum/go-ethereum/eth/tracers/js" 45 _ "github.com/ethereum/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 TestCallTracerNative(t *testing.T) { 135 testCallTracer("callTracer", "call_tracer", t) 136 } 137 138 func testCallTracer(tracerName string, dirPath string, t *testing.T) { 139 files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath)) 140 if err != nil { 141 t.Fatalf("failed to retrieve tracer test suite: %v", err) 142 } 143 for _, file := range files { 144 if !strings.HasSuffix(file.Name(), ".json") { 145 continue 146 } 147 file := file // capture range variable 148 t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { 149 t.Parallel() 150 151 var ( 152 test = new(callTracerTest) 153 tx = new(types.Transaction) 154 ) 155 // Call tracer test found, read if from disk 156 if blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil { 157 t.Fatalf("failed to read testcase: %v", err) 158 } else if err := json.Unmarshal(blob, test); err != nil { 159 t.Fatalf("failed to parse testcase: %v", err) 160 } 161 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 162 t.Fatalf("failed to parse testcase input: %v", err) 163 } 164 // Configure a blockchain with the given prestate 165 var ( 166 signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 167 origin, _ = signer.Sender(tx) 168 txContext = vm.TxContext{ 169 Origin: origin, 170 GasPrice: tx.GasPrice(), 171 } 172 blockContext = vm.BlockContext{ 173 CanTransfer: core.CanTransfer, 174 Transfer: core.Transfer, 175 Coinbase: test.Context.Miner, 176 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 177 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 178 Difficulty: (*big.Int)(test.Context.Difficulty), 179 GasLimit: uint64(test.Context.GasLimit), 180 } 181 _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 182 ) 183 tracer, err := tracers.New(tracerName, new(tracers.Context)) 184 if err != nil { 185 t.Fatalf("failed to create call tracer: %v", err) 186 } 187 evm := vm.NewEVM(blockContext, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) 188 msg, err := tx.AsMessage(signer, nil) 189 if err != nil { 190 t.Fatalf("failed to prepare transaction for tracing: %v", err) 191 } 192 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 193 if _, err = st.TransitionDb(context.Background()); err != nil { 194 t.Fatalf("failed to execute transaction: %v", err) 195 } 196 // Retrieve the trace result and compare against the etalon 197 res, err := tracer.GetResult() 198 if err != nil { 199 t.Fatalf("failed to retrieve trace result: %v", err) 200 } 201 ret := new(callTrace) 202 if err := json.Unmarshal(res, ret); err != nil { 203 t.Fatalf("failed to unmarshal trace result: %v", err) 204 } 205 206 if !jsonEqual(ret, test.Result) { 207 // uncomment this for easier debugging 208 //have, _ := json.MarshalIndent(ret, "", " ") 209 //want, _ := json.MarshalIndent(test.Result, "", " ") 210 //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want)) 211 t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result) 212 } 213 }) 214 } 215 } 216 217 // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to 218 // comparison 219 func jsonEqual(x, y interface{}) bool { 220 xTrace := new(callTrace) 221 yTrace := new(callTrace) 222 if xj, err := json.Marshal(x); err == nil { 223 json.Unmarshal(xj, xTrace) 224 } else { 225 return false 226 } 227 if yj, err := json.Marshal(y); err == nil { 228 json.Unmarshal(yj, yTrace) 229 } else { 230 return false 231 } 232 return reflect.DeepEqual(xTrace, yTrace) 233 } 234 235 // camel converts a snake cased input string into a camel cased output. 236 func camel(str string) string { 237 pieces := strings.Split(str, "_") 238 for i := 1; i < len(pieces); i++ { 239 pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:] 240 } 241 return strings.Join(pieces, "") 242 } 243 func BenchmarkTracers(b *testing.B) { 244 files, err := ioutil.ReadDir(filepath.Join("testdata", "call_tracer")) 245 if err != nil { 246 b.Fatalf("failed to retrieve tracer test suite: %v", err) 247 } 248 for _, file := range files { 249 if !strings.HasSuffix(file.Name(), ".json") { 250 continue 251 } 252 file := file // capture range variable 253 b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { 254 blob, err := ioutil.ReadFile(filepath.Join("testdata", "call_tracer", file.Name())) 255 if err != nil { 256 b.Fatalf("failed to read testcase: %v", err) 257 } 258 test := new(callTracerTest) 259 if err := json.Unmarshal(blob, test); err != nil { 260 b.Fatalf("failed to parse testcase: %v", err) 261 } 262 benchTracer("callTracerNative", test, b) 263 }) 264 } 265 } 266 267 func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { 268 // Configure a blockchain with the given prestate 269 tx := new(types.Transaction) 270 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 271 b.Fatalf("failed to parse testcase input: %v", err) 272 } 273 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) 274 msg, err := tx.AsMessage(signer, nil) 275 if err != nil { 276 b.Fatalf("failed to prepare transaction for tracing: %v", err) 277 } 278 origin, _ := signer.Sender(tx) 279 txContext := vm.TxContext{ 280 Origin: origin, 281 GasPrice: tx.GasPrice(), 282 } 283 blockContext := vm.BlockContext{ 284 CanTransfer: core.CanTransfer, 285 Transfer: core.Transfer, 286 Coinbase: test.Context.Miner, 287 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 288 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 289 Difficulty: (*big.Int)(test.Context.Difficulty), 290 GasLimit: uint64(test.Context.GasLimit), 291 } 292 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 293 294 b.ReportAllocs() 295 b.ResetTimer() 296 for i := 0; i < b.N; i++ { 297 tracer, err := tracers.New(tracerName, new(tracers.Context)) 298 if err != nil { 299 b.Fatalf("failed to create call tracer: %v", err) 300 } 301 302 evm := vm.NewEVM(blockContext, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) 303 snap := statedb.Snapshot() 304 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 305 306 if _, err = st.TransitionDb(context.Background()); err != nil { 307 b.Fatalf("failed to execute transaction: %v", err) 308 } 309 310 if _, err = tracer.GetResult(); err != nil { 311 b.Fatal(err) 312 } 313 314 statedb.RevertToSnapshot(snap) 315 } 316 } 317 318 // TestZeroValueToNotExitCall tests the calltracer(s) on the following: 319 // Tx to A, A calls B with zero value. B does not already exist. 320 // Expected: that enter/exit is invoked and the inner call is shown in the result 321 func TestZeroValueToNotExitCall(t *testing.T) { 322 var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") 323 privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef") 324 if err != nil { 325 t.Fatalf("err %v", err) 326 } 327 signer := types.NewEIP155Signer(big.NewInt(1)) 328 tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{ 329 GasPrice: big.NewInt(0), 330 Gas: 50000, 331 To: &to, 332 }) 333 if err != nil { 334 t.Fatalf("err %v", err) 335 } 336 origin, _ := signer.Sender(tx) 337 txContext := vm.TxContext{ 338 Origin: origin, 339 GasPrice: big.NewInt(1), 340 } 341 blockContext := vm.BlockContext{ 342 CanTransfer: core.CanTransfer, 343 Transfer: core.Transfer, 344 Coinbase: common.Address{}, 345 BlockNumber: new(big.Int).SetUint64(8000000), 346 Time: new(big.Int).SetUint64(5), 347 Difficulty: big.NewInt(0x30000), 348 GasLimit: uint64(6000000), 349 } 350 var code = []byte{ 351 byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero 352 byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS 353 byte(vm.CALL), 354 } 355 var alloc = core.GenesisAlloc{ 356 to: core.GenesisAccount{ 357 Nonce: 1, 358 Code: code, 359 }, 360 origin: core.GenesisAccount{ 361 Nonce: 0, 362 Balance: big.NewInt(500000000000000), 363 }, 364 } 365 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) 366 // Create the tracer, the EVM environment and run it 367 tracer, err := tracers.New("callTracer", nil) 368 if err != nil { 369 t.Fatalf("failed to create call tracer: %v", err) 370 } 371 372 evm := vm.NewEVM(blockContext, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) 373 msg, err := tx.AsMessage(signer, nil) 374 if err != nil { 375 t.Fatalf("failed to prepare transaction for tracing: %v", err) 376 } 377 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 378 379 if _, err = st.TransitionDb(context.Background()); err != nil { 380 t.Fatalf("failed to execute transaction: %v", err) 381 } 382 383 // Retrieve the trace result and compare against the etalon 384 res, err := tracer.GetResult() 385 if err != nil { 386 t.Fatalf("failed to retrieve trace result: %v", err) 387 } 388 have := new(callTrace) 389 if err := json.Unmarshal(res, have); err != nil { 390 t.Fatalf("failed to unmarshal trace result: %v", err) 391 } 392 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"}]}` 393 want := new(callTrace) 394 json.Unmarshal([]byte(wantStr), want) 395 if !jsonEqual(have, want) { 396 t.Error("have != want") 397 } 398 }