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