github.com/klaytn/klaytn@v1.10.2/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 "io/ioutil" 30 "math/big" 31 "os" 32 "reflect" 33 "runtime" 34 "sync" 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 // Backend interface provides the common API services with access to necessary functions. 79 type Backend interface { 80 HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) 81 HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) 82 BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) 83 BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) 84 GetTxAndLookupInfo(txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) 85 RPCGasCap() *big.Int 86 ChainConfig() *params.ChainConfig 87 ChainDB() database.DBManager 88 Engine() consensus.Engine 89 // StateAtBlock returns the state corresponding to the stateroot of the block. 90 // N.B: For executing transactions on block N, the required stateRoot is block N-1, 91 // so this method should be called with the parent. 92 StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (*state.StateDB, error) 93 StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (blockchain.Message, vm.Context, *state.StateDB, error) 94 } 95 96 // API is the collection of tracing APIs exposed over the private debugging endpoint. 97 type API struct { 98 backend Backend 99 unsafeTrace bool 100 } 101 102 // NewAPIUnsafeDisabled creates a new API definition for the tracing methods of the CN service, 103 // only allowing predefined tracers. 104 func NewAPIUnsafeDisabled(backend Backend) *API { 105 return &API{backend: backend, unsafeTrace: false} 106 } 107 108 // NewAPI creates a new API definition for the tracing methods of the CN service, 109 // allowing both predefined tracers and Javascript snippet based tracing. 110 func NewAPI(backend Backend) *API { 111 return &API{backend: backend, unsafeTrace: true} 112 } 113 114 type chainContext struct { 115 backend Backend 116 ctx context.Context 117 } 118 119 func (context *chainContext) Engine() consensus.Engine { 120 return context.backend.Engine() 121 } 122 123 func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header { 124 header, err := context.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) 125 if err != nil { 126 return nil 127 } 128 if header.Hash() == hash { 129 return header 130 } 131 header, err = context.backend.HeaderByHash(context.ctx, hash) 132 if err != nil { 133 return nil 134 } 135 return header 136 } 137 138 // chainContext constructs the context reader which is used by the evm for reading 139 // the necessary chain context. 140 func newChainContext(ctx context.Context, backend Backend) blockchain.ChainContext { 141 return &chainContext{backend: backend, ctx: ctx} 142 } 143 144 // blockByNumber is the wrapper of the chain access function offered by the backend. 145 // It will return an error if the block is not found. 146 func (api *API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { 147 return api.backend.BlockByNumber(ctx, number) 148 } 149 150 // blockByHash is the wrapper of the chain access function offered by the backend. 151 // It will return an error if the block is not found. 152 func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { 153 return api.backend.BlockByHash(ctx, hash) 154 } 155 156 // blockByNumberAndHash is the wrapper of the chain access function offered by 157 // the backend. It will return an error if the block is not found. 158 func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber, hash common.Hash) (*types.Block, error) { 159 block, err := api.blockByNumber(ctx, number) 160 if err != nil { 161 return nil, err 162 } 163 if block.Hash() == hash { 164 return block, nil 165 } 166 return api.blockByHash(ctx, hash) 167 } 168 169 // TraceConfig holds extra parameters to trace functions. 170 type TraceConfig struct { 171 *vm.LogConfig 172 Tracer *string 173 Timeout *string 174 LoggerTimeout *string 175 Reexec *uint64 176 } 177 178 // StdTraceConfig holds extra parameters to standard-json trace functions. 179 type StdTraceConfig struct { 180 *vm.LogConfig 181 Reexec *uint64 182 TxHash common.Hash 183 } 184 185 // txTraceResult is the result of a single transaction trace. 186 type txTraceResult struct { 187 TxHash common.Hash `json:"txHash,omitempty"` // transaction hash 188 Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer 189 Error string `json:"error,omitempty"` // Trace failure produced by the tracer 190 } 191 192 // blockTraceTask represents a single block trace task when an entire chain is 193 // being traced. 194 type blockTraceTask struct { 195 statedb *state.StateDB // Intermediate state prepped for tracing 196 block *types.Block // Block to trace the transactions from 197 rootref common.Hash // Trie root reference held for this task 198 results []*txTraceResult // Trace results procudes by the task 199 } 200 201 // blockTraceResult represets the results of tracing a single block when an entire 202 // chain is being traced. 203 type blockTraceResult struct { 204 Block hexutil.Uint64 `json:"block"` // Block number corresponding to this trace 205 Hash common.Hash `json:"hash"` // Block hash corresponding to this trace 206 Traces []*txTraceResult `json:"traces"` // Trace results produced by the task 207 } 208 209 // txTraceTask represents a single transaction trace task when an entire block 210 // is being traced. 211 type txTraceTask struct { 212 statedb *state.StateDB // Intermediate state prepped for tracing 213 index int // Transaction offset in the block 214 } 215 216 func checkRangeAndReturnBlock(api *API, ctx context.Context, start, end rpc.BlockNumber) (*types.Block, *types.Block, error) { 217 // Fetch the block interval that we want to trace 218 from, err := api.blockByNumber(ctx, start) 219 if err != nil { 220 return nil, nil, err 221 } 222 to, err := api.blockByNumber(ctx, end) 223 if err != nil { 224 return nil, nil, err 225 } 226 227 // Trace the chain if we've found all our blocks 228 if from == nil { 229 return nil, nil, fmt.Errorf("starting block #%d not found", start) 230 } 231 if to == nil { 232 return nil, nil, fmt.Errorf("end block #%d not found", end) 233 } 234 if from.Number().Cmp(to.Number()) >= 0 { 235 return nil, nil, fmt.Errorf("end block #%d needs to come after start block #%d", end, start) 236 } 237 return from, to, nil 238 } 239 240 // TraceChain returns the structured logs created during the execution of EVM 241 // between two blocks (excluding start) and returns them as a JSON object. 242 func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { 243 from, to, err := checkRangeAndReturnBlock(api, ctx, start, end) 244 if err != nil { 245 return nil, err 246 } 247 // Tracing a chain is a **long** operation, only do with subscriptions 248 notifier, supported := rpc.NotifierFromContext(ctx) 249 if !supported { 250 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 251 } 252 var sub *rpc.Subscription 253 sub = notifier.CreateSubscription() 254 _, err = api.traceChain(from, to, config, notifier, sub) 255 return sub, err 256 } 257 258 // traceChain configures a new tracer according to the provided configuration, and 259 // executes all the transactions contained within. 260 // The traceChain operates in two modes: subscription mode and rpc mode 261 // - if notifier and sub is not nil, it works as a subscription mode and returns nothing 262 // - 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 263 func (api *API) traceChain(start, end *types.Block, config *TraceConfig, notifier *rpc.Notifier, sub *rpc.Subscription) (map[uint64]*blockTraceResult, error) { 264 // Prepare all the states for tracing. Note this procedure can take very 265 // long time. Timeout mechanism is necessary. 266 reexec := defaultTraceReexec 267 if config != nil && config.Reexec != nil { 268 reexec = *config.Reexec 269 } 270 // Execute all the transaction contained within the chain concurrently for each block 271 blocks := int(end.NumberU64() - start.NumberU64()) 272 threads := runtime.NumCPU() 273 if threads > blocks { 274 threads = blocks 275 } 276 var ( 277 pend = new(sync.WaitGroup) 278 tasks = make(chan *blockTraceTask, threads) 279 results = make(chan *blockTraceTask, threads) 280 localctx = context.Background() 281 ) 282 for th := 0; th < threads; th++ { 283 pend.Add(1) 284 go func() { 285 defer pend.Done() 286 287 // Fetch and execute the next block trace tasks 288 for task := range tasks { 289 signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number()) 290 291 // Trace all the transactions contained within 292 for i, tx := range task.block.Transactions() { 293 msg, err := tx.AsMessageWithAccountKeyPicker(signer, task.statedb, task.block.NumberU64()) 294 if err != nil { 295 logger.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) 296 task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} 297 break 298 } 299 300 vmctx := blockchain.NewEVMContext(msg, task.block.Header(), newChainContext(localctx, api.backend), nil) 301 302 res, err := api.traceTx(localctx, msg, vmctx, task.statedb, config) 303 if err != nil { 304 task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} 305 logger.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) 306 break 307 } 308 task.statedb.Finalise(true, true) 309 task.results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} 310 } 311 if notifier != nil { 312 // Stream the result back to the user or abort on teardown 313 select { 314 case results <- task: 315 case <-notifier.Closed(): 316 return 317 } 318 } else { 319 results <- task 320 } 321 } 322 }() 323 } 324 // Start a goroutine to feed all the blocks into the tracers 325 begin := time.Now() 326 327 go func() { 328 var ( 329 logged time.Time 330 number uint64 331 traced uint64 332 failed error 333 parent common.Hash 334 statedb *state.StateDB 335 ) 336 // Ensure everything is properly cleaned up on any exit path 337 defer func() { 338 close(tasks) 339 pend.Wait() 340 341 switch { 342 case failed != nil: 343 logger.Warn("Chain tracing failed", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin), "err", failed) 344 case number < end.NumberU64(): 345 logger.Warn("Chain tracing aborted", "start", start.NumberU64(), "end", end.NumberU64(), "abort", number, "transactions", traced, "elapsed", time.Since(begin)) 346 default: 347 logger.Info("Chain tracing finished", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin)) 348 } 349 close(results) 350 }() 351 var preferDisk bool 352 // Feed all the blocks both into the tracer, as well as fast process concurrently 353 for number = start.NumberU64(); number < end.NumberU64(); number++ { 354 if notifier != nil { 355 // Stop tracing if interruption was requested 356 select { 357 case <-notifier.Closed(): 358 return 359 default: 360 } 361 } 362 // Print progress logs if long enough time elapsed 363 if time.Since(logged) > log.StatsReportLimit { 364 logged = time.Now() 365 logger.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin)) 366 } 367 // Retrieve the parent state to trace on top 368 block, err := api.blockByNumber(localctx, rpc.BlockNumber(number)) 369 if err != nil { 370 failed = err 371 break 372 } 373 // Prepare the statedb for tracing. Don't use the live database for 374 // tracing to avoid persisting state junks into the database. 375 statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false, preferDisk) 376 if err != nil { 377 failed = err 378 break 379 } 380 if trieDb := statedb.Database().TrieDB(); trieDb != nil { 381 // Hold the reference for tracer, will be released at the final stage 382 trieDb.Reference(block.Root(), common.Hash{}) 383 384 // Release the parent state because it's already held by the tracer 385 if !common.EmptyHash(parent) { 386 trieDb.Dereference(parent) 387 } 388 // Prefer disk if the trie db memory grows too much 389 s1, s2, s3 := trieDb.Size() 390 if !preferDisk && (s1+s2+s3) > defaultTracechainMemLimit { 391 logger.Info("Switching to prefer-disk mode for tracing", "size", s1+s2+s3) 392 preferDisk = true 393 } 394 } 395 parent = block.Root() 396 397 next, err := api.blockByNumber(localctx, rpc.BlockNumber(number+1)) 398 if err != nil { 399 failed = err 400 break 401 } 402 // Send the block over to the concurrent tracers (if not in the fast-forward phase) 403 txs := next.Transactions() 404 if notifier != nil { 405 select { 406 case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: next, rootref: block.Root(), results: make([]*txTraceResult, len(txs))}: 407 case <-notifier.Closed(): 408 return 409 } 410 } else { 411 tasks <- &blockTraceTask{statedb: statedb.Copy(), block: next, rootref: block.Root(), results: make([]*txTraceResult, len(txs))} 412 } 413 traced += uint64(len(txs)) 414 } 415 }() 416 417 waitForResult := func() map[uint64]*blockTraceResult { 418 // Keep reading the trace results and stream the to the user 419 var ( 420 done = make(map[uint64]*blockTraceResult) 421 next = start.NumberU64() + 1 422 ) 423 for res := range results { 424 // Queue up next received result 425 result := &blockTraceResult{ 426 Block: hexutil.Uint64(res.block.NumberU64()), 427 Hash: res.block.Hash(), 428 Traces: res.results, 429 } 430 done[uint64(result.Block)] = result 431 432 // Dereference any parent tries held in memory by this task 433 if res.statedb.Database().TrieDB() != nil { 434 res.statedb.Database().TrieDB().Dereference(res.rootref) 435 } 436 if notifier != nil { 437 // Stream completed traces to the user, aborting on the first error 438 for result, ok := done[next]; ok; result, ok = done[next] { 439 if len(result.Traces) > 0 || next == end.NumberU64() { 440 notifier.Notify(sub.ID, result) 441 } 442 delete(done, next) 443 next++ 444 } 445 } else { 446 if len(done) == blocks { 447 return done 448 } 449 } 450 } 451 return nil 452 } 453 454 if notifier != nil { 455 go waitForResult() 456 return nil, nil 457 } 458 459 return waitForResult(), nil 460 } 461 462 // TraceBlockByNumber returns the structured logs created during the execution of 463 // EVM and returns them as a JSON object. 464 func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { 465 block, err := api.blockByNumber(ctx, number) 466 if err != nil { 467 return nil, err 468 } 469 return api.traceBlock(ctx, block, config) 470 } 471 472 // TraceBlockByNumberRange returns the ranged blocks tracing results 473 // TODO-tracer: limit the result by the size of the return 474 func (api *API) TraceBlockByNumberRange(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (map[uint64]*blockTraceResult, error) { 475 // When the block range is [start,end], the actual tracing block would be [start+1,end] 476 // this is the reason why we change the block range to [start-1, end] so that we can trace [start,end] blocks 477 from, to, err := checkRangeAndReturnBlock(api, ctx, start-1, end) 478 if err != nil { 479 return nil, err 480 } 481 return api.traceChain(from, to, config, nil, nil) 482 } 483 484 // TraceBlockByHash returns the structured logs created during the execution of 485 // EVM and returns them as a JSON object. 486 func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { 487 block, err := api.blockByHash(ctx, hash) 488 if err != nil { 489 return nil, err 490 } 491 return api.traceBlock(ctx, block, config) 492 } 493 494 // TraceBlock returns the structured logs created during the execution of EVM 495 // and returns them as a JSON object. 496 func (api *API) TraceBlock(ctx context.Context, blob hexutil.Bytes, config *TraceConfig) ([]*txTraceResult, error) { 497 block := new(types.Block) 498 if err := rlp.Decode(bytes.NewReader(blob), block); err != nil { 499 return nil, fmt.Errorf("could not decode block: %v", err) 500 } 501 return api.traceBlock(ctx, block, config) 502 } 503 504 // TraceBlockFromFile returns the structured logs created during the execution of 505 // EVM and returns them as a JSON object. 506 func (api *API) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { 507 if !api.unsafeTrace { 508 return nil, errors.New("TraceBlockFromFile is disabled") 509 } 510 blob, err := ioutil.ReadFile(file) 511 if err != nil { 512 return nil, fmt.Errorf("could not read file: %v", err) 513 } 514 return api.TraceBlock(ctx, common.Hex2Bytes(string(blob)), config) 515 } 516 517 // TraceBadBlock returns the structured logs created during the execution of 518 // EVM against a block pulled from the pool of bad ones and returns them as a JSON 519 // object. 520 func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { 521 blocks, err := api.backend.ChainDB().ReadAllBadBlocks() 522 if err != nil { 523 return nil, err 524 } 525 for _, block := range blocks { 526 if block.Hash() == hash { 527 return api.traceBlock(ctx, block, config) 528 } 529 } 530 return nil, fmt.Errorf("bad block %#x not found", hash) 531 } 532 533 // StandardTraceBlockToFile dumps the structured logs created during the 534 // execution of EVM to the local file system and returns a list of files 535 // to the caller. 536 func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { 537 if !api.unsafeTrace { 538 return nil, errors.New("StandardTraceBlockToFile is disabled") 539 } 540 block, err := api.blockByHash(ctx, hash) 541 if err != nil { 542 return nil, fmt.Errorf("block %#x not found", hash) 543 } 544 return api.standardTraceBlockToFile(ctx, block, config) 545 } 546 547 // StandardTraceBadBlockToFile dumps the structured logs created during the 548 // execution of EVM against a block pulled from the pool of bad ones to the 549 // local file system and returns a list of files to the caller. 550 func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { 551 if !api.unsafeTrace { 552 return nil, errors.New("StandardTraceBadBlockToFile is disabled") 553 } 554 blocks, err := api.backend.ChainDB().ReadAllBadBlocks() 555 if err != nil { 556 return nil, err 557 } 558 for _, block := range blocks { 559 if block.Hash() == hash { 560 return api.standardTraceBlockToFile(ctx, block, config) 561 } 562 } 563 return nil, fmt.Errorf("bad block %#x not found", hash) 564 } 565 566 // traceBlock configures a new tracer according to the provided configuration, and 567 // executes all the transactions contained within. The return value will be one item 568 // per transaction, dependent on the requestd tracer. 569 func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { 570 if block.NumberU64() == 0 { 571 return nil, errors.New("genesis is not traceable") 572 } 573 // Create the parent state database 574 parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) 575 if err != nil { 576 return nil, err 577 } 578 reexec := defaultTraceReexec 579 if config != nil && config.Reexec != nil { 580 reexec = *config.Reexec 581 } 582 583 statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) 584 if err != nil { 585 return nil, err 586 } 587 // Execute all the transaction contained within the block concurrently 588 var ( 589 signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) 590 txs = block.Transactions() 591 results = make([]*txTraceResult, len(txs)) 592 593 pend = new(sync.WaitGroup) 594 jobs = make(chan *txTraceTask, len(txs)) 595 ) 596 threads := runtime.NumCPU() 597 if threads > len(txs) { 598 threads = len(txs) 599 } 600 for th := 0; th < threads; th++ { 601 pend.Add(1) 602 go func() { 603 defer pend.Done() 604 605 // Fetch and execute the next transaction trace tasks 606 for task := range jobs { 607 msg, err := txs[task.index].AsMessageWithAccountKeyPicker(signer, task.statedb, block.NumberU64()) 608 if err != nil { 609 logger.Warn("Tracing failed", "tx idx", task.index, "block", block.NumberU64(), "err", err) 610 results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} 611 continue 612 } 613 614 vmctx := blockchain.NewEVMContext(msg, block.Header(), newChainContext(ctx, api.backend), nil) 615 res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) 616 if err != nil { 617 results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} 618 continue 619 } 620 results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Result: res} 621 } 622 }() 623 } 624 // Feed the transactions into the tracers and return 625 var failed error 626 for i, tx := range txs { 627 // Send the trace task over for execution 628 jobs <- &txTraceTask{statedb: statedb.Copy(), index: i} 629 630 // Generate the next state snapshot fast without tracing 631 msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, block.NumberU64()) 632 if err != nil { 633 logger.Warn("Tracing failed", "hash", tx.Hash(), "block", block.NumberU64(), "err", err) 634 failed = err 635 break 636 } 637 638 vmctx := blockchain.NewEVMContext(msg, block.Header(), newChainContext(ctx, api.backend), nil) 639 vmenv := vm.NewEVM(vmctx, statedb, api.backend.ChainConfig(), &vm.Config{UseOpcodeComputationCost: true}) 640 if _, _, kerr := blockchain.ApplyMessage(vmenv, msg); kerr.ErrTxInvalid != nil { 641 failed = kerr.ErrTxInvalid 642 break 643 } 644 // Finalize the state so any modifications are written to the trie 645 statedb.Finalise(true, true) 646 } 647 close(jobs) 648 pend.Wait() 649 650 // If execution failed in between, abort 651 if failed != nil { 652 return nil, failed 653 } 654 return results, nil 655 } 656 657 // standardTraceBlockToFile configures a new tracer which uses standard JSON output, 658 // and traces either a full block or an individual transaction. The return value will 659 // be one filename per transaction traced. 660 func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { 661 // If we're tracing a single transaction, make sure it's present 662 if config != nil && !common.EmptyHash(config.TxHash) { 663 if !containsTx(block, config.TxHash) { 664 return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash) 665 } 666 } 667 if block.NumberU64() == 0 { 668 return nil, errors.New("genesis is not traceable") 669 } 670 parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) 671 if err != nil { 672 return nil, err 673 } 674 reexec := defaultTraceReexec 675 if config != nil && config.Reexec != nil { 676 reexec = *config.Reexec 677 } 678 statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) 679 if err != nil { 680 return nil, err 681 } 682 // Retrieve the tracing configurations, or use default values 683 var ( 684 logConfig vm.LogConfig 685 txHash common.Hash 686 ) 687 if config != nil { 688 if config.LogConfig != nil { 689 logConfig = *config.LogConfig 690 } 691 txHash = config.TxHash 692 } 693 logConfig.Debug = true 694 695 // Execute transaction, either tracing all or just the requested one 696 var ( 697 signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) 698 dumps []string 699 ) 700 for i, tx := range block.Transactions() { 701 // Prepare the transaction for un-traced execution 702 msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, block.NumberU64()) 703 if err != nil { 704 logger.Warn("Tracing failed", "hash", tx.Hash(), "block", block.NumberU64(), "err", err) 705 return nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) 706 } 707 708 var ( 709 vmctx = blockchain.NewEVMContext(msg, block.Header(), newChainContext(ctx, api.backend), nil) 710 711 vmConf vm.Config 712 dump *os.File 713 ) 714 715 // If the transaction needs tracing, swap out the configs 716 if tx.Hash() == txHash || common.EmptyHash(txHash) { 717 // Generate a unique temporary file to dump it into 718 prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) 719 720 dump, err = ioutil.TempFile(os.TempDir(), prefix) 721 if err != nil { 722 return nil, err 723 } 724 dumps = append(dumps, dump.Name()) 725 726 // Swap out the noop logger to the standard tracer 727 vmConf = vm.Config{ 728 Debug: true, 729 Tracer: vm.NewJSONLogger(&logConfig, bufio.NewWriter(dump)), 730 EnablePreimageRecording: true, 731 UseOpcodeComputationCost: true, 732 } 733 } 734 // Execute the transaction and flush any traces to disk 735 vmenv := vm.NewEVM(vmctx, statedb, api.backend.ChainConfig(), &vmConf) 736 _, _, kerr := blockchain.ApplyMessage(vmenv, msg) 737 738 if dump != nil { 739 dump.Close() 740 logger.Info("Wrote standard trace", "file", dump.Name()) 741 } 742 if kerr.ErrTxInvalid != nil { 743 return dumps, kerr.ErrTxInvalid 744 } 745 // Finalize the state so any modifications are written to the trie 746 statedb.Finalise(true, true) 747 748 // If we've traced the transaction we were looking for, abort 749 if tx.Hash() == txHash { 750 break 751 } 752 } 753 return dumps, nil 754 } 755 756 // containsTx reports whether the transaction with a certain hash 757 // is contained within the specified block. 758 func containsTx(block *types.Block, hash common.Hash) bool { 759 for _, tx := range block.Transactions() { 760 if tx.Hash() == hash { 761 return true 762 } 763 } 764 return false 765 } 766 767 // TraceTransaction returns the structured logs created during the execution of EVM 768 // and returns them as a JSON object. 769 func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { 770 // Retrieve the transaction and assemble its EVM context 771 tx, blockHash, blockNumber, index := api.backend.GetTxAndLookupInfo(hash) 772 if tx == nil { 773 return nil, fmt.Errorf("transaction %#x not found", hash) 774 } 775 // It shouldn't happen in practice. 776 if blockNumber == 0 { 777 return nil, errors.New("genesis is not traceable") 778 } 779 reexec := defaultTraceReexec 780 if config != nil && config.Reexec != nil { 781 reexec = *config.Reexec 782 } 783 block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash) 784 if err != nil { 785 return nil, err 786 } 787 msg, vmctx, statedb, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) 788 if err != nil { 789 return nil, err 790 } 791 // Trace the transaction and return 792 return api.traceTx(ctx, msg, vmctx, statedb, config) 793 } 794 795 // traceTx configures a new tracer according to the provided configuration, and 796 // executes the given message in the provided environment. The return value will 797 // be tracer dependent. 798 func (api *API) traceTx(ctx context.Context, message blockchain.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { 799 // Assemble the structured logger or the JavaScript tracer 800 var ( 801 tracer vm.Tracer 802 err error 803 ) 804 switch { 805 case config != nil && config.Tracer != nil: 806 // Define a meaningful timeout of a single transaction trace 807 timeout := defaultTraceTimeout 808 if config.Timeout != nil { 809 if timeout, err = time.ParseDuration(*config.Timeout); err != nil { 810 return nil, err 811 } 812 } 813 814 if *config.Tracer == fastCallTracer { 815 tracer = vm.NewInternalTxTracer() 816 } else { 817 // Construct the JavaScript tracer to execute with 818 if tracer, err = New(*config.Tracer, api.unsafeTrace); err != nil { 819 return nil, err 820 } 821 } 822 // Handle timeouts and RPC cancellations 823 deadlineCtx, cancel := context.WithTimeout(ctx, timeout) 824 go func() { 825 <-deadlineCtx.Done() 826 if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) { 827 switch t := tracer.(type) { 828 case *Tracer: 829 t.Stop(errors.New("execution timeout")) 830 case *vm.InternalTxTracer: 831 t.Stop(errors.New("execution timeout")) 832 default: 833 logger.Warn("unknown tracer type", "type", reflect.TypeOf(t).String()) 834 } 835 } 836 }() 837 defer cancel() 838 839 case config == nil: 840 tracer = vm.NewStructLogger(nil) 841 842 default: 843 tracer = vm.NewStructLogger(config.LogConfig) 844 } 845 // Run the transaction with tracing enabled. 846 vmenv := vm.NewEVM(vmctx, statedb, api.backend.ChainConfig(), &vm.Config{Debug: true, Tracer: tracer, UseOpcodeComputationCost: true}) 847 848 ret, gas, kerr := blockchain.ApplyMessage(vmenv, message) 849 if kerr.ErrTxInvalid != nil { 850 return nil, fmt.Errorf("tracing failed: %v", kerr.ErrTxInvalid) 851 } 852 // Depending on the tracer type, format and return the output 853 switch tracer := tracer.(type) { 854 case *vm.StructLogger: 855 loggerTimeout := defaultLoggerTimeout 856 if config != nil && config.LoggerTimeout != nil { 857 if loggerTimeout, err = time.ParseDuration(*config.LoggerTimeout); err != nil { 858 return nil, err 859 } 860 } 861 if logs, err := klaytnapi.FormatLogs(loggerTimeout, tracer.StructLogs()); err == nil { 862 return &klaytnapi.ExecutionResult{ 863 Gas: gas, 864 Failed: kerr.Status != types.ReceiptStatusSuccessful, 865 ReturnValue: fmt.Sprintf("%x", ret), 866 StructLogs: logs, 867 }, nil 868 } else { 869 return nil, err 870 } 871 872 case *Tracer: 873 return tracer.GetResult() 874 case *vm.InternalTxTracer: 875 return tracer.GetResult() 876 877 default: 878 panic(fmt.Sprintf("bad tracer type %T", tracer)) 879 } 880 }