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