github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/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 "fmt" 22 "math/big" 23 "os" 24 "path/filepath" 25 "strings" 26 "testing" 27 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/common/hexutil" 30 "github.com/ethereum/go-ethereum/core" 31 "github.com/ethereum/go-ethereum/core/rawdb" 32 "github.com/ethereum/go-ethereum/core/types" 33 "github.com/ethereum/go-ethereum/core/vm" 34 "github.com/ethereum/go-ethereum/crypto" 35 "github.com/ethereum/go-ethereum/eth/tracers" 36 "github.com/ethereum/go-ethereum/params" 37 "github.com/ethereum/go-ethereum/rlp" 38 "github.com/ethereum/go-ethereum/tests" 39 ) 40 41 // callLog is the result of LOG opCode 42 type callLog struct { 43 Address common.Address `json:"address"` 44 Topics []common.Hash `json:"topics"` 45 Data hexutil.Bytes `json:"data"` 46 Position hexutil.Uint `json:"position"` 47 } 48 49 // callTrace is the result of a callTracer run. 50 type callTrace struct { 51 From common.Address `json:"from"` 52 Gas *hexutil.Uint64 `json:"gas"` 53 GasUsed *hexutil.Uint64 `json:"gasUsed"` 54 To *common.Address `json:"to,omitempty"` 55 Input hexutil.Bytes `json:"input"` 56 Output hexutil.Bytes `json:"output,omitempty"` 57 Error string `json:"error,omitempty"` 58 RevertReason string `json:"revertReason,omitempty"` 59 Calls []callTrace `json:"calls,omitempty"` 60 Logs []callLog `json:"logs,omitempty"` 61 Value *hexutil.Big `json:"value,omitempty"` 62 // Gencodec adds overridden fields at the end 63 Type string `json:"type"` 64 } 65 66 // callTracerTest defines a single test to check the call tracer against. 67 type callTracerTest struct { 68 Genesis *core.Genesis `json:"genesis"` 69 Context *callContext `json:"context"` 70 Input string `json:"input"` 71 TracerConfig json.RawMessage `json:"tracerConfig"` 72 Result *callTrace `json:"result"` 73 } 74 75 // Iterates over all the input-output datasets in the tracer test harness and 76 // runs the JavaScript tracers against them. 77 func TestCallTracerLegacy(t *testing.T) { 78 testCallTracer("callTracerLegacy", "call_tracer_legacy", t) 79 } 80 81 func TestCallTracerNative(t *testing.T) { 82 testCallTracer("callTracer", "call_tracer", t) 83 } 84 85 func TestCallTracerNativeWithLog(t *testing.T) { 86 testCallTracer("callTracer", "call_tracer_withLog", t) 87 } 88 89 func testCallTracer(tracerName string, dirPath string, t *testing.T) { 90 isLegacy := strings.HasSuffix(dirPath, "_legacy") 91 files, err := os.ReadDir(filepath.Join("testdata", dirPath)) 92 if err != nil { 93 t.Fatalf("failed to retrieve tracer test suite: %v", err) 94 } 95 for _, file := range files { 96 if !strings.HasSuffix(file.Name(), ".json") { 97 continue 98 } 99 file := file // capture range variable 100 t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { 101 t.Parallel() 102 103 var ( 104 test = new(callTracerTest) 105 tx = new(types.Transaction) 106 ) 107 // Call tracer test found, read if from disk 108 if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil { 109 t.Fatalf("failed to read testcase: %v", err) 110 } else if err := json.Unmarshal(blob, test); err != nil { 111 t.Fatalf("failed to parse testcase: %v", err) 112 } 113 if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil { 114 t.Fatalf("failed to parse testcase input: %v", err) 115 } 116 // Configure a blockchain with the given prestate 117 var ( 118 signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) 119 context = test.Context.toBlockContext(test.Genesis) 120 state = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) 121 ) 122 state.Close() 123 124 tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) 125 if err != nil { 126 t.Fatalf("failed to create call tracer: %v", err) 127 } 128 129 state.StateDB.SetLogger(tracer.Hooks) 130 msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) 131 if err != nil { 132 t.Fatalf("failed to prepare transaction for tracing: %v", err) 133 } 134 evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks}) 135 tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) 136 vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 137 if err != nil { 138 t.Fatalf("failed to execute transaction: %v", err) 139 } 140 tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) 141 // Retrieve the trace result and compare against the expected. 142 res, err := tracer.GetResult() 143 if err != nil { 144 t.Fatalf("failed to retrieve trace result: %v", err) 145 } 146 // The legacy javascript calltracer marshals json in js, which 147 // is not deterministic (as opposed to the golang json encoder). 148 if isLegacy { 149 // This is a tweak to make it deterministic. Can be removed when 150 // we remove the legacy tracer. 151 var x callTrace 152 json.Unmarshal(res, &x) 153 res, _ = json.Marshal(x) 154 } 155 want, err := json.Marshal(test.Result) 156 if err != nil { 157 t.Fatalf("failed to marshal test: %v", err) 158 } 159 if string(want) != string(res) { 160 t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want)) 161 } 162 // Sanity check: compare top call's gas used against vm result 163 type simpleResult struct { 164 GasUsed hexutil.Uint64 165 } 166 var topCall simpleResult 167 if err := json.Unmarshal(res, &topCall); err != nil { 168 t.Fatalf("failed to unmarshal top calls gasUsed: %v", err) 169 } 170 if uint64(topCall.GasUsed) != vmRet.UsedGas { 171 t.Fatalf("top call has invalid gasUsed. have: %d want: %d", topCall.GasUsed, vmRet.UsedGas) 172 } 173 }) 174 } 175 } 176 177 func BenchmarkTracers(b *testing.B) { 178 files, err := os.ReadDir(filepath.Join("testdata", "call_tracer")) 179 if err != nil { 180 b.Fatalf("failed to retrieve tracer test suite: %v", err) 181 } 182 for _, file := range files { 183 if !strings.HasSuffix(file.Name(), ".json") { 184 continue 185 } 186 file := file // capture range variable 187 b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { 188 blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name())) 189 if err != nil { 190 b.Fatalf("failed to read testcase: %v", err) 191 } 192 test := new(callTracerTest) 193 if err := json.Unmarshal(blob, test); err != nil { 194 b.Fatalf("failed to parse testcase: %v", err) 195 } 196 benchTracer("callTracer", test, b) 197 }) 198 } 199 } 200 201 func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { 202 // Configure a blockchain with the given prestate 203 tx := new(types.Transaction) 204 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 205 b.Fatalf("failed to parse testcase input: %v", err) 206 } 207 signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) 208 origin, _ := signer.Sender(tx) 209 txContext := vm.TxContext{ 210 Origin: origin, 211 GasPrice: tx.GasPrice(), 212 } 213 context := vm.BlockContext{ 214 CanTransfer: core.CanTransfer, 215 Transfer: core.Transfer, 216 Coinbase: test.Context.Miner, 217 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 218 Time: uint64(test.Context.Time), 219 Difficulty: (*big.Int)(test.Context.Difficulty), 220 GasLimit: uint64(test.Context.GasLimit), 221 } 222 msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) 223 if err != nil { 224 b.Fatalf("failed to prepare transaction for tracing: %v", err) 225 } 226 state := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) 227 defer state.Close() 228 229 b.ReportAllocs() 230 b.ResetTimer() 231 for i := 0; i < b.N; i++ { 232 tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil) 233 if err != nil { 234 b.Fatalf("failed to create call tracer: %v", err) 235 } 236 evm := vm.NewEVM(context, txContext, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks}) 237 snap := state.StateDB.Snapshot() 238 st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 239 if _, err = st.TransitionDb(); err != nil { 240 b.Fatalf("failed to execute transaction: %v", err) 241 } 242 if _, err = tracer.GetResult(); err != nil { 243 b.Fatal(err) 244 } 245 state.StateDB.RevertToSnapshot(snap) 246 } 247 } 248 249 func TestInternals(t *testing.T) { 250 var ( 251 config = params.MainnetChainConfig 252 to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") 253 originHex = "0x71562b71999873db5b286df957af199ec94617f7" 254 origin = common.HexToAddress(originHex) 255 signer = types.LatestSigner(config) 256 key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 257 context = vm.BlockContext{ 258 CanTransfer: core.CanTransfer, 259 Transfer: core.Transfer, 260 Coinbase: common.Address{}, 261 BlockNumber: new(big.Int).SetUint64(8000000), 262 Time: 5, 263 Difficulty: big.NewInt(0x30000), 264 GasLimit: uint64(6000000), 265 BaseFee: new(big.Int), 266 } 267 ) 268 mkTracer := func(name string, cfg json.RawMessage) *tracers.Tracer { 269 tr, err := tracers.DefaultDirectory.New(name, nil, cfg) 270 if err != nil { 271 t.Fatalf("failed to create call tracer: %v", err) 272 } 273 return tr 274 } 275 276 for _, tc := range []struct { 277 name string 278 code []byte 279 tracer *tracers.Tracer 280 want string 281 }{ 282 { 283 // TestZeroValueToNotExitCall tests the calltracer(s) on the following: 284 // Tx to A, A calls B with zero value. B does not already exist. 285 // Expected: that enter/exit is invoked and the inner call is shown in the result 286 name: "ZeroValueToNotExitCall", 287 code: []byte{ 288 byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero 289 byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS 290 byte(vm.CALL), 291 }, 292 tracer: mkTracer("callTracer", nil), 293 want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, originHex), 294 }, 295 { 296 name: "Stack depletion in LOG0", 297 code: []byte{byte(vm.LOG3)}, 298 tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), 299 want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, originHex), 300 }, 301 { 302 name: "Mem expansion in LOG0", 303 code: []byte{ 304 byte(vm.PUSH1), 0x1, 305 byte(vm.PUSH1), 0x0, 306 byte(vm.MSTORE), 307 byte(vm.PUSH1), 0xff, 308 byte(vm.PUSH1), 0x0, 309 byte(vm.LOG0), 310 }, 311 tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), 312 want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex), 313 }, 314 { 315 // Leads to OOM on the prestate tracer 316 name: "Prestate-tracer - CREATE2 OOM", 317 code: []byte{ 318 byte(vm.PUSH1), 0x1, 319 byte(vm.PUSH1), 0x0, 320 byte(vm.MSTORE), 321 byte(vm.PUSH1), 0x1, 322 byte(vm.PUSH5), 0xff, 0xff, 0xff, 0xff, 0xff, 323 byte(vm.PUSH1), 0x1, 324 byte(vm.PUSH1), 0x0, 325 byte(vm.CREATE2), 326 byte(vm.PUSH1), 0xff, 327 byte(vm.PUSH1), 0x0, 328 byte(vm.LOG0), 329 }, 330 tracer: mkTracer("prestateTracer", nil), 331 want: fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex), 332 }, 333 { 334 // CREATE2 which requires padding memory by prestate tracer 335 name: "Prestate-tracer - CREATE2 Memory padding", 336 code: []byte{ 337 byte(vm.PUSH1), 0x1, 338 byte(vm.PUSH1), 0x0, 339 byte(vm.MSTORE), 340 byte(vm.PUSH1), 0x1, 341 byte(vm.PUSH1), 0xff, 342 byte(vm.PUSH1), 0x1, 343 byte(vm.PUSH1), 0x0, 344 byte(vm.CREATE2), 345 byte(vm.PUSH1), 0xff, 346 byte(vm.PUSH1), 0x0, 347 byte(vm.LOG0), 348 }, 349 tracer: mkTracer("prestateTracer", nil), 350 want: fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600160ff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex), 351 }, 352 } { 353 t.Run(tc.name, func(t *testing.T) { 354 state := tests.MakePreState(rawdb.NewMemoryDatabase(), 355 types.GenesisAlloc{ 356 to: types.Account{ 357 Code: tc.code, 358 }, 359 origin: types.Account{ 360 Balance: big.NewInt(500000000000000), 361 }, 362 }, false, rawdb.HashScheme) 363 defer state.Close() 364 state.StateDB.SetLogger(tc.tracer.Hooks) 365 tx, err := types.SignNewTx(key, signer, &types.LegacyTx{ 366 To: &to, 367 Value: big.NewInt(0), 368 Gas: 80000, 369 GasPrice: big.NewInt(1), 370 }) 371 if err != nil { 372 t.Fatalf("test %v: failed to sign transaction: %v", tc.name, err) 373 } 374 txContext := vm.TxContext{ 375 Origin: origin, 376 GasPrice: tx.GasPrice(), 377 } 378 evm := vm.NewEVM(context, txContext, state.StateDB, config, vm.Config{Tracer: tc.tracer.Hooks}) 379 msg, err := core.TransactionToMessage(tx, signer, big.NewInt(0)) 380 if err != nil { 381 t.Fatalf("test %v: failed to create message: %v", tc.name, err) 382 } 383 tc.tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) 384 vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 385 if err != nil { 386 t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err) 387 } 388 tc.tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) 389 // Retrieve the trace result and compare against the expected 390 res, err := tc.tracer.GetResult() 391 if err != nil { 392 t.Fatalf("test %v: failed to retrieve trace result: %v", tc.name, err) 393 } 394 if string(res) != tc.want { 395 t.Errorf("test %v: trace mismatch\n have: %v\n want: %v\n", tc.name, string(res), tc.want) 396 } 397 }) 398 } 399 }