github.com/klaytn/klaytn@v1.12.1/node/cn/tracers/api.go (about) 1 // Modifications Copyright 2022 The klaytn Authors 2 // Copyright 2021 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from eth/tracers/api.go (2022/08/08). 19 // Modified and improved for the klaytn development. 20 21 package tracers 22 23 import ( 24 "bufio" 25 "bytes" 26 "context" 27 "errors" 28 "fmt" 29 "math/big" 30 "os" 31 "reflect" 32 "runtime" 33 "sync" 34 "sync/atomic" 35 "time" 36 37 klaytnapi "github.com/klaytn/klaytn/api" 38 "github.com/klaytn/klaytn/blockchain" 39 "github.com/klaytn/klaytn/blockchain/state" 40 "github.com/klaytn/klaytn/blockchain/types" 41 "github.com/klaytn/klaytn/blockchain/vm" 42 "github.com/klaytn/klaytn/common" 43 "github.com/klaytn/klaytn/common/hexutil" 44 "github.com/klaytn/klaytn/consensus" 45 "github.com/klaytn/klaytn/log" 46 "github.com/klaytn/klaytn/networks/rpc" 47 "github.com/klaytn/klaytn/params" 48 "github.com/klaytn/klaytn/rlp" 49 "github.com/klaytn/klaytn/storage/database" 50 ) 51 52 const ( 53 // defaultTraceTimeout is the amount of time a single transaction can execute 54 // by default before being forcefully aborted. 55 defaultTraceTimeout = 5 * time.Second 56 57 // defaultLoggerTimeout is the amount of time a logger can aggregate trace logs 58 defaultLoggerTimeout = 1 * time.Second 59 60 // defaultTraceReexec is the number of blocks the tracer is willing to go back 61 // and reexecute to produce missing historical state necessary to run a specific 62 // trace. 63 defaultTraceReexec = uint64(128) 64 65 // defaultTracechainMemLimit is the size of the triedb, at which traceChain 66 // switches over and tries to use a disk-backed database instead of building 67 // on top of memory. 68 // For non-archive nodes, this limit _will_ be overblown, as disk-backed tries 69 // will only be found every ~15K blocks or so. 70 // For klaytn, this value is set to a value 4 times larger compared to the ethereum setting. 71 defaultTracechainMemLimit = common.StorageSize(4 * 500 * 1024 * 1024) 72 73 // fastCallTracer is the go-version callTracer which is lighter and faster than 74 // Javascript version. 75 fastCallTracer = "fastCallTracer" 76 ) 77 78 var ( 79 HeavyAPIRequestLimit int32 = 500 80 heavyAPIRequestCount int32 = 0 81 ) 82 83 // Backend interface provides the common API services with access to necessary functions. 84 type Backend interface { 85 HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) 86 HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) 87 BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) 88 BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) 89 GetTxAndLookupInfo(txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) 90 RPCGasCap() *big.Int 91 ChainConfig() *params.ChainConfig 92 ChainDB() database.DBManager 93 Engine() consensus.Engine 94 // StateAtBlock returns the state corresponding to the stateroot of the block. 95 // N.B: For executing transactions on block N, the required stateRoot is block N-1, 96 // so this method should be called with the parent. 97 StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (*state.StateDB, error) 98 StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, error) 99 } 100 101 // CommonAPI contains 102 // - public methods that change behavior depending on `.unsafeTrace` flag. 103 // For instance, TraceTransaction and TraceCall may or may not support custom tracers. 104 // - private helper methods such as traceTx 105 type CommonAPI struct { 106 backend Backend 107 unsafeTrace bool 108 } 109 110 // API contains public methods that are considered "safe" to expose in public RPC. 111 type API struct { 112 CommonAPI 113 } 114 115 // UnsafeAPI contains public methods that are considered "unsafe" to expose in public RPC. 116 type UnsafeAPI struct { 117 CommonAPI 118 } 119 120 // NewUnsafeAPI creates a new UnsafeAPI definition 121 func NewUnsafeAPI(backend Backend) *UnsafeAPI { 122 return &UnsafeAPI{ 123 CommonAPI{backend: backend, unsafeTrace: true}, 124 } 125 } 126 127 // NewAPI creates a new API definition 128 func NewAPI(backend Backend) *API { 129 return &API{ 130 CommonAPI{backend: backend, unsafeTrace: false}, 131 } 132 } 133 134 type chainContext struct { 135 backend Backend 136 ctx context.Context 137 } 138 139 func (context *chainContext) Engine() consensus.Engine { 140 return context.backend.Engine() 141 } 142 143 func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header { 144 header, err := context.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) 145 if err != nil { 146 return nil 147 } 148 if header.Hash() == hash { 149 return header 150 } 151 header, err = context.backend.HeaderByHash(context.ctx, hash) 152 if err != nil { 153 return nil 154 } 155 return header 156 } 157 158 // chainContext constructs the context reader which is used by the evm for reading 159 // the necessary chain context. 160 func newChainContext(ctx context.Context, backend Backend) blockchain.ChainContext { 161 return &chainContext{backend: backend, ctx: ctx} 162 } 163 164 // blockByNumber is the wrapper of the chain access function offered by the backend. 165 // It will return an error if the block is not found. 166 func (api *CommonAPI) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { 167 return api.backend.BlockByNumber(ctx, number) 168 } 169 170 // blockByHash is the wrapper of the chain access function offered by the backend. 171 // It will return an error if the block is not found. 172 func (api *CommonAPI) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { 173 return api.backend.BlockByHash(ctx, hash) 174 } 175 176 // blockByNumberAndHash is the wrapper of the chain access function offered by 177 // the backend. It will return an error if the block is not found. 178 func (api *CommonAPI) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber, hash common.Hash) (*types.Block, error) { 179 block, err := api.blockByNumber(ctx, number) 180 if err != nil { 181 return nil, err 182 } 183 if block.Hash() == hash { 184 return block, nil 185 } 186 return api.blockByHash(ctx, hash) 187 } 188 189 // TraceConfig holds extra parameters to trace functions. 190 type TraceConfig struct { 191 *vm.LogConfig 192 Tracer *string 193 Timeout *string 194 LoggerTimeout *string 195 Reexec *uint64 196 } 197 198 // StdTraceConfig holds extra parameters to standard-json trace functions. 199 type StdTraceConfig struct { 200 *vm.LogConfig 201 Reexec *uint64 202 TxHash common.Hash 203 } 204 205 // txTraceResult is the result of a single transaction trace. 206 type txTraceResult struct { 207 TxHash common.Hash `json:"txHash,omitempty"` // transaction hash 208 Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer 209 Error string `json:"error,omitempty"` // Trace failure produced by the tracer 210 } 211 212 // blockTraceTask represents a single block trace task when an entire chain is 213 // being traced. 214 type blockTraceTask struct { 215 statedb *state.StateDB // Intermediate state prepped for tracing 216 block *types.Block // Block to trace the transactions from 217 rootref common.Hash // Trie root reference held for this task 218 results []*txTraceResult // Trace results procudes by the task 219 } 220 221 // blockTraceResult represets the results of tracing a single block when an entire 222 // chain is being traced. 223 type blockTraceResult struct { 224 Block hexutil.Uint64 `json:"block"` // Block number corresponding to this trace 225 Hash common.Hash `json:"hash"` // Block hash corresponding to this trace 226 Traces []*txTraceResult `json:"traces"` // Trace results produced by the task 227 } 228 229 // txTraceTask represents a single transaction trace task when an entire block 230 // is being traced. 231 type txTraceTask struct { 232 statedb *state.StateDB // Intermediate state prepped for tracing 233 index int // Transaction offset in the block 234 } 235 236 func checkRangeAndReturnBlock(api *CommonAPI, ctx context.Context, start, end rpc.BlockNumber) (*types.Block, *types.Block, error) { 237 // Fetch the block interval that we want to trace 238 from, err := api.blockByNumber(ctx, start) 239 if err != nil { 240 return nil, nil, err 241 } 242 to, err := api.blockByNumber(ctx, end) 243 if err != nil { 244 return nil, nil, err 245 } 246 247 // Trace the chain if we've found all our blocks 248 if from == nil { 249 return nil, nil, fmt.Errorf("starting block #%d not found", start) 250 } 251 if to == nil { 252 return nil, nil, fmt.Errorf("end block #%d not found", end) 253 } 254 if from.Number().Cmp(to.Number()) >= 0 { 255 return nil, nil, fmt.Errorf("end block #%d needs to come after start block #%d", end, start) 256 } 257 return from, to, nil 258 } 259 260 // TraceChain returns the structured logs created during the execution of EVM 261 // between two blocks (excluding start) and returns them as a JSON object. 262 func (api *UnsafeAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { 263 from, to, err := checkRangeAndReturnBlock(&api.CommonAPI, ctx, start, end) 264 if err != nil { 265 return nil, err 266 } 267 // Tracing a chain is a **long** operation, only do with subscriptions 268 notifier, supported := rpc.NotifierFromContext(ctx) 269 if !supported { 270 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 271 } 272 sub := notifier.CreateSubscription() 273 _, err = api.traceChain(from, to, config, notifier, sub) 274 return sub, err 275 } 276 277 // traceChain configures a new tracer according to the provided configuration, and 278 // executes all the transactions contained within. 279 // The traceChain operates in two modes: subscription mode and rpc mode 280 // - if notifier and sub is not nil, it works as a subscription mode and returns nothing 281 // - if those parameters are nil, it works as a rpc mode and returns the block trace results, so it can pass the result through rpc-call 282 func (api *CommonAPI) traceChain(start, end *types.Block, config *TraceConfig, notifier *rpc.Notifier, sub *rpc.Subscription) (map[uint64]*blockTraceResult, error) { 283 // Prepare all the states for tracing. Note this procedure can take very 284 // long time. Timeout mechanism is necessary. 285 reexec := defaultTraceReexec 286 if config != nil && config.Reexec != nil { 287 reexec = *config.Reexec 288 } 289 // Execute all the transaction contained within the chain concurrently for each block 290 blocks := int(end.NumberU64() - start.NumberU64()) 291 threads := runtime.NumCPU() 292 if threads > blocks { 293 threads = blocks 294 } 295 var ( 296 pend = new(sync.WaitGroup) 297 tasks = make(chan *blockTraceTask, threads) 298 results = make(chan *blockTraceTask, threads) 299 localctx = context.Background() 300 ) 301 for th := 0; th < threads; th++ { 302 pend.Add(1) 303 go func() { 304 defer pend.Done() 305 306 // Fetch and execute the next block trace tasks 307 for task := range tasks { 308 signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number()) 309 310 // Trace all the transactions contained within 311 for i, tx := range task.block.Transactions() { 312 msg, err := tx.AsMessageWithAccountKeyPicker(signer, task.statedb, task.block.NumberU64()) 313 if err != nil { 314 logger.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) 315 task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} 316 break 317 } 318 319 txCtx := blockchain.NewEVMTxContext(msg, task.block.Header()) 320 blockCtx := blockchain.NewEVMBlockContext(task.block.Header(), newChainContext(localctx, api.backend), nil) 321 322 res, err := api.traceTx(localctx, msg, blockCtx, txCtx, task.statedb, config) 323 if err != nil { 324 task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} 325 logger.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) 326 break 327 } 328 task.statedb.Finalise(true, true) 329 task.results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} 330 } 331 if notifier != nil { 332 // Stream the result back to the user or abort on teardown 333 select { 334 case results <- task: 335 case <-notifier.Closed(): 336 return 337 } 338 } else { 339 results <- task 340 } 341 } 342 }() 343 } 344 // Start a goroutine to feed all the blocks into the tracers 345 begin := time.Now() 346 347 go func() { 348 var ( 349 logged time.Time 350 number uint64 351 traced uint64 352 failed error 353 parent common.Hash 354 statedb *state.StateDB 355 ) 356 // Ensure everything is properly cleaned up on any exit path 357 defer func() { 358 close(tasks) 359 pend.Wait() 360 361 switch { 362 case failed != nil: 363 logger.Warn("Chain tracing failed", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin), "err", failed) 364 case number < end.NumberU64(): 365 logger.Warn("Chain tracing aborted", "start", start.NumberU64(), "end", end.NumberU64(), "abort", number, "transactions", traced, "elapsed", time.Since(begin)) 366 default: 367 logger.Info("Chain tracing finished", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin)) 368 } 369 close(results) 370 }() 371 var preferDisk bool 372 // Feed all the blocks both into the tracer, as well as fast process concurrently 373 for number = start.NumberU64(); number < end.NumberU64(); number++ { 374 if notifier != nil { 375 // Stop tracing if interruption was requested 376 select { 377 case <-notifier.Closed(): 378 return 379 default: 380 } 381 } 382 // Print progress logs if long enough time elapsed 383 if time.Since(logged) > log.StatsReportLimit { 384 logged = time.Now() 385 logger.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin)) 386 } 387 // Retrieve the parent state to trace on top 388 block, err := api.blockByNumber(localctx, rpc.BlockNumber(number)) 389 if err != nil { 390 failed = err 391 break 392 } 393 // Prepare the statedb for tracing. Don't use the live database for 394 // tracing to avoid persisting state junks into the database. 395 statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false, preferDisk) 396 if err != nil { 397 failed = err 398 break 399 } 400 if trieDb := statedb.Database().TrieDB(); trieDb != nil { 401 // Hold the reference for tracer, will be released at the final stage 402 trieDb.ReferenceRoot(block.Root()) 403 404 // Release the parent state because it's already held by the tracer 405 if !common.EmptyHash(parent) { 406 trieDb.Dereference(parent) 407 } 408 // Prefer disk if the trie db memory grows too much 409 s1, s2, s3 := trieDb.Size() 410 if !preferDisk && (s1+s2+s3) > defaultTracechainMemLimit { 411 logger.Info("Switching to prefer-disk mode for tracing", "size", s1+s2+s3) 412 preferDisk = true 413 } 414 } 415 parent = block.Root() 416 417 next, err := api.blockByNumber(localctx, rpc.BlockNumber(number+1)) 418 if err != nil { 419 failed = err 420 break 421 } 422 // Send the block over to the concurrent tracers (if not in the fast-forward phase) 423 txs := next.Transactions() 424 if notifier != nil { 425 select { 426 case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: next, rootref: block.Root(), results: make([]*txTraceResult, len(txs))}: 427 case <-notifier.Closed(): 428 return 429 } 430 } else { 431 tasks <- &blockTraceTask{statedb: statedb.Copy(), block: next, rootref: block.Root(), results: make([]*txTraceResult, len(txs))} 432 } 433 traced += uint64(len(txs)) 434 } 435 }() 436 437 waitForResult := func() map[uint64]*blockTraceResult { 438 // Keep reading the trace results and stream the to the user 439 var ( 440 done = make(map[uint64]*blockTraceResult) 441 next = start.NumberU64() + 1 442 ) 443 for res := range results { 444 // Queue up next received result 445 result := &blockTraceResult{ 446 Block: hexutil.Uint64(res.block.NumberU64()), 447 Hash: res.block.Hash(), 448 Traces: res.results, 449 } 450 done[uint64(result.Block)] = result 451 452 // Dereference any parent tries held in memory by this task 453 if res.statedb.Database().TrieDB() != nil { 454 res.statedb.Database().TrieDB().Dereference(res.rootref) 455 } 456 if notifier != nil { 457 // Stream completed traces to the user, aborting on the first error 458 for result, ok := done[next]; ok; result, ok = done[next] { 459 if len(result.Traces) > 0 || next == end.NumberU64() { 460 notifier.Notify(sub.ID, result) 461 } 462 delete(done, next) 463 next++ 464 } 465 } else { 466 if len(done) == blocks { 467 return done 468 } 469 } 470 } 471 return nil 472 } 473 474 if notifier != nil { 475 go waitForResult() 476 return nil, nil 477 } 478 479 return waitForResult(), nil 480 } 481 482 // TraceBlockByNumber returns the structured logs created during the execution of 483 // EVM and returns them as a JSON object. 484 func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { 485 block, err := api.blockByNumber(ctx, number) 486 if err != nil { 487 return nil, err 488 } 489 return api.traceBlock(ctx, block, config) 490 } 491 492 // TraceBlockByNumberRange returns the ranged blocks tracing results 493 // TODO-tracer: limit the result by the size of the return 494 func (api *UnsafeAPI) TraceBlockByNumberRange(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (map[uint64]*blockTraceResult, error) { 495 // When the block range is [start,end], the actual tracing block would be [start+1,end] 496 // this is the reason why we change the block range to [start-1, end] so that we can trace [start,end] blocks 497 from, to, err := checkRangeAndReturnBlock(&api.CommonAPI, ctx, start-1, end) 498 if err != nil { 499 return nil, err 500 } 501 return api.traceChain(from, to, config, nil, nil) 502 } 503 504 // TraceBlockByHash returns the structured logs created during the execution of 505 // EVM and returns them as a JSON object. 506 func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { 507 block, err := api.blockByHash(ctx, hash) 508 if err != nil { 509 return nil, err 510 } 511 return api.traceBlock(ctx, block, config) 512 } 513 514 // TraceBlock returns the structured logs created during the execution of EVM 515 // and returns them as a JSON object. 516 func (api *CommonAPI) TraceBlock(ctx context.Context, blob hexutil.Bytes, config *TraceConfig) ([]*txTraceResult, error) { 517 block := new(types.Block) 518 if err := rlp.Decode(bytes.NewReader(blob), block); err != nil { 519 return nil, fmt.Errorf("could not decode block: %v", err) 520 } 521 return api.traceBlock(ctx, block, config) 522 } 523 524 // TraceBlockFromFile returns the structured logs created during the execution of 525 // EVM and returns them as a JSON object. 526 func (api *UnsafeAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { 527 blob, err := os.ReadFile(file) 528 if err != nil { 529 return nil, fmt.Errorf("could not read file: %v", err) 530 } 531 return api.TraceBlock(ctx, common.Hex2Bytes(string(blob)), config) 532 } 533 534 // TraceBadBlock returns the structured logs created during the execution of 535 // EVM against a block pulled from the pool of bad ones and returns them as a JSON 536 // object. 537 func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { 538 blocks, err := api.backend.ChainDB().ReadAllBadBlocks() 539 if err != nil { 540 return nil, err 541 } 542 for _, block := range blocks { 543 if block.Hash() == hash { 544 return api.traceBlock(ctx, block, config) 545 } 546 } 547 return nil, fmt.Errorf("bad block %#x not found", hash) 548 } 549 550 // StandardTraceBlockToFile dumps the structured logs created during the 551 // execution of EVM to the local file system and returns a list of files 552 // to the caller. 553 func (api *UnsafeAPI) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { 554 block, err := api.blockByHash(ctx, hash) 555 if err != nil { 556 return nil, fmt.Errorf("block %#x not found", hash) 557 } 558 return api.standardTraceBlockToFile(ctx, block, config) 559 } 560 561 // StandardTraceBadBlockToFile dumps the structured logs created during the 562 // execution of EVM against a block pulled from the pool of bad ones to the 563 // local file system and returns a list of files to the caller. 564 func (api *UnsafeAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { 565 blocks, err := api.backend.ChainDB().ReadAllBadBlocks() 566 if err != nil { 567 return nil, err 568 } 569 for _, block := range blocks { 570 if block.Hash() == hash { 571 return api.standardTraceBlockToFile(ctx, block, config) 572 } 573 } 574 return nil, fmt.Errorf("bad block %#x not found", hash) 575 } 576 577 // traceBlock configures a new tracer according to the provided configuration, and 578 // executes all the transactions contained within. The return value will be one item 579 // per transaction, dependent on the requestd tracer. 580 func (api *CommonAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { 581 if !api.unsafeTrace { 582 if atomic.LoadInt32(&heavyAPIRequestCount) >= HeavyAPIRequestLimit { 583 return nil, fmt.Errorf("heavy debug api requests exceed the limit: %d", int64(HeavyAPIRequestLimit)) 584 } 585 atomic.AddInt32(&heavyAPIRequestCount, 1) 586 defer atomic.AddInt32(&heavyAPIRequestCount, -1) 587 } 588 if block.NumberU64() == 0 { 589 return nil, errors.New("genesis is not traceable") 590 } 591 // Create the parent state database 592 parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) 593 if err != nil { 594 return nil, err 595 } 596 reexec := defaultTraceReexec 597 if config != nil && config.Reexec != nil { 598 reexec = *config.Reexec 599 } 600 601 statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) 602 if err != nil { 603 return nil, err 604 } 605 // Execute all the transaction contained within the block concurrently 606 var ( 607 signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) 608 txs = block.Transactions() 609 results = make([]*txTraceResult, len(txs)) 610 611 pend = new(sync.WaitGroup) 612 jobs = make(chan *txTraceTask, len(txs)) 613 ) 614 threads := runtime.NumCPU() 615 if threads > len(txs) { 616 threads = len(txs) 617 } 618 for th := 0; th < threads; th++ { 619 pend.Add(1) 620 go func() { 621 defer pend.Done() 622 623 // Fetch and execute the next transaction trace tasks 624 for task := range jobs { 625 msg, err := txs[task.index].AsMessageWithAccountKeyPicker(signer, task.statedb, block.NumberU64()) 626 if err != nil { 627 logger.Warn("Tracing failed", "tx idx", task.index, "block", block.NumberU64(), "err", err) 628 results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} 629 continue 630 } 631 632 txCtx := blockchain.NewEVMTxContext(msg, block.Header()) 633 blockCtx := blockchain.NewEVMBlockContext(block.Header(), newChainContext(ctx, api.backend), nil) 634 res, err := api.traceTx(ctx, msg, blockCtx, txCtx, task.statedb, config) 635 if err != nil { 636 results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} 637 continue 638 } 639 results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Result: res} 640 } 641 }() 642 } 643 // Feed the transactions into the tracers and return 644 var failed error 645 for i, tx := range txs { 646 // Send the trace task over for execution 647 jobs <- &txTraceTask{statedb: statedb.Copy(), index: i} 648 649 // Generate the next state snapshot fast without tracing 650 msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, block.NumberU64()) 651 if err != nil { 652 logger.Warn("Tracing failed", "hash", tx.Hash(), "block", block.NumberU64(), "err", err) 653 failed = err 654 break 655 } 656 657 txCtx := blockchain.NewEVMTxContext(msg, block.Header()) 658 blockCtx := blockchain.NewEVMBlockContext(block.Header(), newChainContext(ctx, api.backend), nil) 659 vmenv := vm.NewEVM(blockCtx, txCtx, statedb, api.backend.ChainConfig(), &vm.Config{}) 660 if _, err = blockchain.ApplyMessage(vmenv, msg); err != nil { 661 failed = err 662 break 663 } 664 // Finalize the state so any modifications are written to the trie 665 statedb.Finalise(true, true) 666 } 667 close(jobs) 668 pend.Wait() 669 670 // If execution failed in between, abort 671 if failed != nil { 672 return nil, failed 673 } 674 return results, nil 675 } 676 677 // standardTraceBlockToFile configures a new tracer which uses standard JSON output, 678 // and traces either a full block or an individual transaction. The return value will 679 // be one filename per transaction traced. 680 func (api *CommonAPI) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { 681 // If we're tracing a single transaction, make sure it's present 682 if config != nil && !common.EmptyHash(config.TxHash) { 683 if !containsTx(block, config.TxHash) { 684 return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash) 685 } 686 } 687 if block.NumberU64() == 0 { 688 return nil, errors.New("genesis is not traceable") 689 } 690 parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) 691 if err != nil { 692 return nil, err 693 } 694 reexec := defaultTraceReexec 695 if config != nil && config.Reexec != nil { 696 reexec = *config.Reexec 697 } 698 statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) 699 if err != nil { 700 return nil, err 701 } 702 // Retrieve the tracing configurations, or use default values 703 var ( 704 logConfig vm.LogConfig 705 txHash common.Hash 706 ) 707 if config != nil { 708 if config.LogConfig != nil { 709 logConfig = *config.LogConfig 710 } 711 txHash = config.TxHash 712 } 713 logConfig.Debug = true 714 715 // Execute transaction, either tracing all or just the requested one 716 var ( 717 signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) 718 dumps []string 719 ) 720 for i, tx := range block.Transactions() { 721 // Prepare the transaction for un-traced execution 722 msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, block.NumberU64()) 723 if err != nil { 724 logger.Warn("Tracing failed", "hash", tx.Hash(), "block", block.NumberU64(), "err", err) 725 return nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) 726 } 727 728 var ( 729 txCtx = blockchain.NewEVMTxContext(msg, block.Header()) 730 blockCtx = blockchain.NewEVMBlockContext(block.Header(), newChainContext(ctx, api.backend), nil) 731 732 vmConf vm.Config 733 dump *os.File 734 ) 735 736 // If the transaction needs tracing, swap out the configs 737 if tx.Hash() == txHash || common.EmptyHash(txHash) { 738 // Generate a unique temporary file to dump it into 739 prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) 740 741 dump, err = os.CreateTemp(os.TempDir(), prefix) 742 if err != nil { 743 return nil, err 744 } 745 dumps = append(dumps, dump.Name()) 746 747 // Swap out the noop logger to the standard tracer 748 vmConf = vm.Config{ 749 Debug: true, 750 Tracer: vm.NewJSONLogger(&logConfig, bufio.NewWriter(dump)), 751 EnablePreimageRecording: true, 752 } 753 } 754 // Execute the transaction and flush any traces to disk 755 vmenv := vm.NewEVM(blockCtx, txCtx, statedb, api.backend.ChainConfig(), &vmConf) 756 _, err = blockchain.ApplyMessage(vmenv, msg) 757 758 if dump != nil { 759 dump.Close() 760 logger.Info("Wrote standard trace", "file", dump.Name()) 761 } 762 if err != nil { 763 return dumps, err 764 } 765 // Finalize the state so any modifications are written to the trie 766 statedb.Finalise(true, true) 767 768 // If we've traced the transaction we were looking for, abort 769 if tx.Hash() == txHash { 770 break 771 } 772 } 773 return dumps, nil 774 } 775 776 // containsTx reports whether the transaction with a certain hash 777 // is contained within the specified block. 778 func containsTx(block *types.Block, hash common.Hash) bool { 779 for _, tx := range block.Transactions() { 780 if tx.Hash() == hash { 781 return true 782 } 783 } 784 return false 785 } 786 787 // TraceTransaction returns the structured logs created during the execution of EVM 788 // and returns them as a JSON object. 789 func (api *CommonAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { 790 if !api.unsafeTrace { 791 if atomic.LoadInt32(&heavyAPIRequestCount) >= HeavyAPIRequestLimit { 792 return nil, fmt.Errorf("heavy debug api requests exceed the limit: %d", int64(HeavyAPIRequestLimit)) 793 } 794 atomic.AddInt32(&heavyAPIRequestCount, 1) 795 defer atomic.AddInt32(&heavyAPIRequestCount, -1) 796 } 797 // Retrieve the transaction and assemble its EVM context 798 tx, blockHash, blockNumber, index := api.backend.GetTxAndLookupInfo(hash) 799 if tx == nil { 800 return nil, fmt.Errorf("transaction %#x not found", hash) 801 } 802 // It shouldn't happen in practice. 803 if blockNumber == 0 { 804 return nil, errors.New("genesis is not traceable") 805 } 806 reexec := defaultTraceReexec 807 if config != nil && config.Reexec != nil { 808 reexec = *config.Reexec 809 } 810 block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash) 811 if err != nil { 812 return nil, err 813 } 814 msg, blockCtx, txCtx, statedb, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) 815 if err != nil { 816 return nil, err 817 } 818 // Trace the transaction and return 819 return api.traceTx(ctx, msg, blockCtx, txCtx, statedb, config) 820 } 821 822 // TraceCall lets you trace a given klay_call. It collects the structured logs 823 // created during the execution of EVM if the given transaction was added on 824 // top of the provided block and returns them as a JSON object. 825 func (api *CommonAPI) TraceCall(ctx context.Context, args klaytnapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) { 826 if !api.unsafeTrace { 827 if atomic.LoadInt32(&heavyAPIRequestCount) >= HeavyAPIRequestLimit { 828 return nil, fmt.Errorf("heavy debug api requests exceed the limit: %d", int64(HeavyAPIRequestLimit)) 829 } 830 atomic.AddInt32(&heavyAPIRequestCount, 1) 831 defer atomic.AddInt32(&heavyAPIRequestCount, -1) 832 } 833 // Try to retrieve the specified block 834 var ( 835 err error 836 block *types.Block 837 ) 838 if hash, ok := blockNrOrHash.Hash(); ok { 839 block, err = api.blockByHash(ctx, hash) 840 } else if number, ok := blockNrOrHash.Number(); ok { 841 block, err = api.blockByNumber(ctx, number) 842 } else { 843 return nil, errors.New("invalid arguments; neither block nor hash specified") 844 } 845 if err != nil { 846 return nil, err 847 } 848 // try to recompute the state 849 reexec := defaultTraceReexec 850 if config != nil && config.Reexec != nil { 851 reexec = *config.Reexec 852 } 853 statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false) 854 if err != nil { 855 return nil, err 856 } 857 858 // Execute the trace 859 intrinsicGas, err := types.IntrinsicGas(args.InputData(), nil, args.To == nil, api.backend.ChainConfig().Rules(block.Number())) 860 if err != nil { 861 return nil, err 862 } 863 basefee := new(big.Int).SetUint64(params.ZeroBaseFee) 864 if block.Header().BaseFee != nil { 865 basefee = block.Header().BaseFee 866 } 867 gasCap := uint64(0) 868 if rpcGasCap := api.backend.RPCGasCap(); rpcGasCap != nil { 869 gasCap = rpcGasCap.Uint64() 870 } 871 msg, err := args.ToMessage(gasCap, basefee, intrinsicGas) 872 if err != nil { 873 return nil, err 874 } 875 876 // Add gas fee to sender for estimating gasLimit/computing cost or calling a function by insufficient balance sender. 877 statedb.AddBalance(msg.ValidatedSender(), new(big.Int).Mul(new(big.Int).SetUint64(msg.Gas()), basefee)) 878 879 txCtx := blockchain.NewEVMTxContext(msg, block.Header()) 880 blockCtx := blockchain.NewEVMBlockContext(block.Header(), newChainContext(ctx, api.backend), nil) 881 882 return api.traceTx(ctx, msg, blockCtx, txCtx, statedb, config) 883 } 884 885 // traceTx configures a new tracer according to the provided configuration, and 886 // executes the given message in the provided environment. The return value will 887 // be tracer dependent. 888 func (api *CommonAPI) traceTx(ctx context.Context, message blockchain.Message, blockCtx vm.BlockContext, txCtx vm.TxContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { 889 // Assemble the structured logger or the JavaScript tracer 890 var ( 891 tracer vm.Tracer 892 err error 893 ) 894 switch { 895 case config != nil && config.Tracer != nil: 896 // Define a meaningful timeout of a single transaction trace 897 timeout := defaultTraceTimeout 898 if config.Timeout != nil { 899 if timeout, err = time.ParseDuration(*config.Timeout); err != nil { 900 return nil, err 901 } 902 } 903 904 if *config.Tracer == fastCallTracer { 905 tracer = vm.NewInternalTxTracer() 906 } else { 907 // Construct the JavaScript tracer to execute with 908 if tracer, err = New(*config.Tracer, new(Context), api.unsafeTrace); err != nil { 909 return nil, err 910 } 911 } 912 // Handle timeouts and RPC cancellations 913 deadlineCtx, cancel := context.WithTimeout(ctx, timeout) 914 go func() { 915 <-deadlineCtx.Done() 916 if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) { 917 switch t := tracer.(type) { 918 case *Tracer: 919 t.Stop(errors.New("execution timeout")) 920 case *vm.InternalTxTracer: 921 t.Stop(errors.New("execution timeout")) 922 default: 923 logger.Warn("unknown tracer type", "type", reflect.TypeOf(t).String()) 924 } 925 } 926 }() 927 defer cancel() 928 929 case config == nil: 930 tracer = vm.NewStructLogger(nil) 931 932 default: 933 tracer = vm.NewStructLogger(config.LogConfig) 934 } 935 // Run the transaction with tracing enabled. 936 vmenv := vm.NewEVM(blockCtx, txCtx, statedb, api.backend.ChainConfig(), &vm.Config{Debug: true, Tracer: tracer}) 937 938 ret, err := blockchain.ApplyMessage(vmenv, message) 939 if err != nil { 940 return nil, fmt.Errorf("tracing failed: %v", err) 941 } 942 // Depending on the tracer type, format and return the output 943 switch tracer := tracer.(type) { 944 case *vm.StructLogger: 945 loggerTimeout := defaultLoggerTimeout 946 if config != nil && config.LoggerTimeout != nil { 947 if loggerTimeout, err = time.ParseDuration(*config.LoggerTimeout); err != nil { 948 return nil, err 949 } 950 } 951 if logs, err := klaytnapi.FormatLogs(loggerTimeout, tracer.StructLogs()); err == nil { 952 return &klaytnapi.ExecutionResult{ 953 Gas: ret.UsedGas, 954 Failed: ret.Failed(), 955 ReturnValue: fmt.Sprintf("%x", ret.Return()), 956 StructLogs: logs, 957 }, nil 958 } else { 959 return nil, err 960 } 961 962 case *Tracer: 963 return tracer.GetResult() 964 case *vm.InternalTxTracer: 965 return tracer.GetResult() 966 967 default: 968 panic(fmt.Sprintf("bad tracer type %T", tracer)) 969 } 970 }