github.com/neatio-net/neatio@v1.7.3-0.20231114194659-f4d7a2226baa/neatptc/api_tracer.go (about) 1 package neatptc 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "runtime" 10 "sync" 11 "time" 12 13 "github.com/neatio-net/neatio/chain/core" 14 "github.com/neatio-net/neatio/chain/core/rawdb" 15 "github.com/neatio-net/neatio/chain/core/state" 16 "github.com/neatio-net/neatio/chain/core/types" 17 "github.com/neatio-net/neatio/chain/core/vm" 18 "github.com/neatio-net/neatio/chain/log" 19 "github.com/neatio-net/neatio/chain/trie" 20 "github.com/neatio-net/neatio/internal/neatapi" 21 "github.com/neatio-net/neatio/neatptc/tracers" 22 "github.com/neatio-net/neatio/network/rpc" 23 "github.com/neatio-net/neatio/utilities/common" 24 "github.com/neatio-net/neatio/utilities/common/hexutil" 25 "github.com/neatio-net/neatio/utilities/rlp" 26 ) 27 28 const ( 29 defaultTraceTimeout = 5 * time.Second 30 31 defaultTraceReexec = uint64(128) 32 ) 33 34 type TraceConfig struct { 35 *vm.LogConfig 36 Tracer *string 37 Timeout *string 38 Reexec *uint64 39 } 40 41 type StdTraceConfig struct { 42 *vm.LogConfig 43 Reexec *uint64 44 TxHash common.Hash 45 } 46 47 type txTraceResult struct { 48 Result interface{} `json:"result,omitempty"` 49 Error string `json:"error,omitempty"` 50 } 51 52 type blockTraceTask struct { 53 statedb *state.StateDB 54 block *types.Block 55 rootref common.Hash 56 results []*txTraceResult 57 } 58 59 type blockTraceResult struct { 60 Block hexutil.Uint64 `json:"block"` 61 Hash common.Hash `json:"hash"` 62 Traces []*txTraceResult `json:"traces"` 63 } 64 65 type txTraceTask struct { 66 statedb *state.StateDB 67 index int 68 } 69 70 func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { 71 72 var from, to *types.Block 73 74 switch start { 75 case rpc.PendingBlockNumber: 76 from = api.eth.miner.PendingBlock() 77 case rpc.LatestBlockNumber: 78 from = api.eth.blockchain.CurrentBlock() 79 default: 80 from = api.eth.blockchain.GetBlockByNumber(uint64(start)) 81 } 82 switch end { 83 case rpc.PendingBlockNumber: 84 to = api.eth.miner.PendingBlock() 85 case rpc.LatestBlockNumber: 86 to = api.eth.blockchain.CurrentBlock() 87 default: 88 to = api.eth.blockchain.GetBlockByNumber(uint64(end)) 89 } 90 91 if from == nil { 92 return nil, fmt.Errorf("starting block #%d not found", start) 93 } 94 if to == nil { 95 return nil, fmt.Errorf("end block #%d not found", end) 96 } 97 if from.Number().Cmp(to.Number()) >= 0 { 98 return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start) 99 } 100 return api.traceChain(ctx, from, to, config) 101 } 102 103 func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) { 104 105 notifier, supported := rpc.NotifierFromContext(ctx) 106 if !supported { 107 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 108 } 109 sub := notifier.CreateSubscription() 110 111 origin := start.NumberU64() 112 database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) 113 114 if number := start.NumberU64(); number > 0 { 115 start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) 116 if start == nil { 117 return nil, fmt.Errorf("parent block #%d not found", number-1) 118 } 119 } 120 statedb, err := state.New(start.Root(), database) 121 if err != nil { 122 123 reexec := defaultTraceReexec 124 if config != nil && config.Reexec != nil { 125 reexec = *config.Reexec 126 } 127 128 for i := uint64(0); i < reexec; i++ { 129 start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) 130 if start == nil { 131 break 132 } 133 if statedb, err = state.New(start.Root(), database); err == nil { 134 break 135 } 136 } 137 138 if err != nil { 139 switch err.(type) { 140 case *trie.MissingNodeError: 141 return nil, errors.New("required historical state unavailable") 142 default: 143 return nil, err 144 } 145 } 146 } 147 148 blocks := int(end.NumberU64() - origin) 149 150 threads := runtime.NumCPU() 151 if threads > blocks { 152 threads = blocks 153 } 154 var ( 155 pend = new(sync.WaitGroup) 156 tasks = make(chan *blockTraceTask, threads) 157 results = make(chan *blockTraceTask, threads) 158 ) 159 for th := 0; th < threads; th++ { 160 pend.Add(1) 161 go func() { 162 defer pend.Done() 163 164 for task := range tasks { 165 signer := types.MakeSigner(api.eth.blockchain.Config(), task.block.Number()) 166 167 for i, tx := range task.block.Transactions() { 168 msg, _ := tx.AsMessage(signer) 169 vmctx := core.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil) 170 171 res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) 172 if err != nil { 173 task.results[i] = &txTraceResult{Error: err.Error()} 174 log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) 175 break 176 } 177 178 task.statedb.Finalise(api.eth.blockchain.Config().IsEIP158(task.block.Number())) 179 task.results[i] = &txTraceResult{Result: res} 180 } 181 182 select { 183 case results <- task: 184 case <-notifier.Closed(): 185 return 186 } 187 } 188 }() 189 } 190 191 begin := time.Now() 192 193 go func() { 194 var ( 195 logged time.Time 196 number uint64 197 traced uint64 198 failed error 199 proot common.Hash 200 ) 201 202 defer func() { 203 close(tasks) 204 pend.Wait() 205 206 switch { 207 case failed != nil: 208 log.Warn("Chain tracing failed", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin), "err", failed) 209 case number < end.NumberU64(): 210 log.Warn("Chain tracing aborted", "start", start.NumberU64(), "end", end.NumberU64(), "abort", number, "transactions", traced, "elapsed", time.Since(begin)) 211 default: 212 log.Info("Chain tracing finished", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin)) 213 } 214 close(results) 215 }() 216 217 for number = start.NumberU64() + 1; number <= end.NumberU64(); number++ { 218 219 select { 220 case <-notifier.Closed(): 221 return 222 default: 223 } 224 225 if time.Since(logged) > 8*time.Second { 226 if number > origin { 227 nodes, imgs := database.TrieDB().Size() 228 log.Info("Tracing chain segment", "start", origin, "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin), "memory", nodes+imgs) 229 } else { 230 log.Info("Preparing state for chain trace", "block", number, "start", origin, "elapsed", time.Since(begin)) 231 } 232 logged = time.Now() 233 } 234 235 block := api.eth.blockchain.GetBlockByNumber(number) 236 if block == nil { 237 failed = fmt.Errorf("block #%d not found", number) 238 break 239 } 240 241 if number > origin { 242 txs := block.Transactions() 243 244 select { 245 case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, rootref: proot, results: make([]*txTraceResult, len(txs))}: 246 case <-notifier.Closed(): 247 return 248 } 249 traced += uint64(len(txs)) 250 } 251 252 _, _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{}) 253 if err != nil { 254 failed = err 255 break 256 } 257 258 root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number())) 259 if err != nil { 260 failed = err 261 break 262 } 263 if err := statedb.Reset(root); err != nil { 264 failed = err 265 break 266 } 267 268 database.TrieDB().Reference(root, common.Hash{}) 269 if number >= origin { 270 database.TrieDB().Reference(root, common.Hash{}) 271 } 272 273 if proot != (common.Hash{}) { 274 database.TrieDB().Dereference(proot) 275 } 276 proot = root 277 278 } 279 }() 280 281 go func() { 282 var ( 283 done = make(map[uint64]*blockTraceResult) 284 next = origin + 1 285 ) 286 for res := range results { 287 288 result := &blockTraceResult{ 289 Block: hexutil.Uint64(res.block.NumberU64()), 290 Hash: res.block.Hash(), 291 Traces: res.results, 292 } 293 done[uint64(result.Block)] = result 294 295 database.TrieDB().Dereference(res.rootref) 296 297 for result, ok := done[next]; ok; result, ok = done[next] { 298 if len(result.Traces) > 0 || next == end.NumberU64() { 299 notifier.Notify(sub.ID, result) 300 } 301 delete(done, next) 302 next++ 303 } 304 } 305 }() 306 return sub, nil 307 } 308 309 func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { 310 311 var block *types.Block 312 313 switch number { 314 case rpc.PendingBlockNumber: 315 block = api.eth.miner.PendingBlock() 316 case rpc.LatestBlockNumber: 317 block = api.eth.blockchain.CurrentBlock() 318 default: 319 block = api.eth.blockchain.GetBlockByNumber(uint64(number)) 320 } 321 322 if block == nil { 323 return nil, fmt.Errorf("block #%d not found", number) 324 } 325 return api.traceBlock(ctx, block, config) 326 } 327 328 func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { 329 block := api.eth.blockchain.GetBlockByHash(hash) 330 if block == nil { 331 return nil, fmt.Errorf("block #%x not found", hash) 332 } 333 return api.traceBlock(ctx, block, config) 334 } 335 336 func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) { 337 block := new(types.Block) 338 if err := rlp.Decode(bytes.NewReader(blob), block); err != nil { 339 return nil, fmt.Errorf("could not decode block: %v", err) 340 } 341 return api.traceBlock(ctx, block, config) 342 } 343 344 func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { 345 blob, err := ioutil.ReadFile(file) 346 if err != nil { 347 return nil, fmt.Errorf("could not read file: %v", err) 348 } 349 return api.TraceBlock(ctx, blob, config) 350 } 351 352 func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { 353 354 if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil { 355 return nil, err 356 } 357 parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) 358 if parent == nil { 359 return nil, fmt.Errorf("parent #%x not found", block.ParentHash()) 360 } 361 reexec := defaultTraceReexec 362 if config != nil && config.Reexec != nil { 363 reexec = *config.Reexec 364 } 365 statedb, err := api.computeStateDB(parent, reexec) 366 if err != nil { 367 return nil, err 368 } 369 370 var ( 371 signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) 372 373 txs = block.Transactions() 374 results = make([]*txTraceResult, len(txs)) 375 376 pend = new(sync.WaitGroup) 377 jobs = make(chan *txTraceTask, len(txs)) 378 ) 379 threads := runtime.NumCPU() 380 if threads > len(txs) { 381 threads = len(txs) 382 } 383 for th := 0; th < threads; th++ { 384 pend.Add(1) 385 go func() { 386 defer pend.Done() 387 388 for task := range jobs { 389 msg, _ := txs[task.index].AsMessage(signer) 390 vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) 391 392 res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) 393 if err != nil { 394 results[task.index] = &txTraceResult{Error: err.Error()} 395 continue 396 } 397 results[task.index] = &txTraceResult{Result: res} 398 } 399 }() 400 } 401 402 var failed error 403 for i, tx := range txs { 404 405 jobs <- &txTraceTask{statedb: statedb.Copy(), index: i} 406 407 msg, _ := tx.AsMessage(signer) 408 vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) 409 410 vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{}) 411 if _, _, err := core.ApplyMessageEx(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { 412 failed = err 413 break 414 } 415 416 statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) 417 } 418 close(jobs) 419 pend.Wait() 420 421 if failed != nil { 422 return nil, failed 423 } 424 return results, nil 425 } 426 427 func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, error) { 428 429 statedb, err := api.eth.blockchain.StateAt(block.Root()) 430 if err == nil { 431 return statedb, nil 432 } 433 434 origin := block.NumberU64() 435 database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) 436 437 for i := uint64(0); i < reexec; i++ { 438 block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) 439 if block == nil { 440 break 441 } 442 if statedb, err = state.New(block.Root(), database); err == nil { 443 break 444 } 445 } 446 if err != nil { 447 switch err.(type) { 448 case *trie.MissingNodeError: 449 return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) 450 default: 451 return nil, err 452 } 453 } 454 455 var ( 456 start = time.Now() 457 logged time.Time 458 proot common.Hash 459 ) 460 for block.NumberU64() < origin { 461 462 if time.Since(logged) > 8*time.Second { 463 log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start)) 464 logged = time.Now() 465 } 466 467 if block = api.eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil { 468 return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) 469 } 470 _, _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{}) 471 if err != nil { 472 return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) 473 } 474 475 root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number())) 476 if err != nil { 477 return nil, err 478 } 479 if err := statedb.Reset(root); err != nil { 480 return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) 481 } 482 database.TrieDB().Reference(root, common.Hash{}) 483 if proot != (common.Hash{}) { 484 database.TrieDB().Dereference(proot) 485 } 486 proot = root 487 } 488 nodes, imgs := database.TrieDB().Size() 489 log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) 490 return statedb, nil 491 } 492 493 func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { 494 495 tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash) 496 if tx == nil { 497 return nil, fmt.Errorf("transaction #%x not found", hash) 498 } 499 reexec := defaultTraceReexec 500 if config != nil && config.Reexec != nil { 501 reexec = *config.Reexec 502 } 503 msg, vmctx, statedb, err := api.computeTxEnv(blockHash, int(index), reexec) 504 if err != nil { 505 return nil, err 506 } 507 508 return api.traceTx(ctx, msg, vmctx, statedb, config) 509 } 510 511 func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { 512 513 var ( 514 tracer vm.Tracer 515 err error 516 ) 517 switch { 518 case config != nil && config.Tracer != nil: 519 520 timeout := defaultTraceTimeout 521 if config.Timeout != nil { 522 if timeout, err = time.ParseDuration(*config.Timeout); err != nil { 523 return nil, err 524 } 525 } 526 527 if tracer, err = tracers.New(*config.Tracer); err != nil { 528 return nil, err 529 } 530 531 deadlineCtx, cancel := context.WithTimeout(ctx, timeout) 532 go func() { 533 <-deadlineCtx.Done() 534 tracer.(*tracers.Tracer).Stop(errors.New("execution timeout")) 535 }() 536 defer cancel() 537 538 case config == nil: 539 tracer = vm.NewStructLogger(nil) 540 541 default: 542 tracer = vm.NewStructLogger(config.LogConfig) 543 } 544 545 vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) 546 547 result, _, err := core.ApplyMessageEx(vmenv, message, new(core.GasPool).AddGas(message.Gas())) 548 if err != nil { 549 return nil, fmt.Errorf("tracing failed: %v", err) 550 } 551 552 switch tracer := tracer.(type) { 553 case *vm.StructLogger: 554 return &neatapi.ExecutionResult{ 555 Gas: result.UsedGas, 556 Failed: result.Failed(), 557 ReturnValue: fmt.Sprintf("%x", result.Revert()), 558 StructLogs: neatapi.FormatLogs(tracer.StructLogs()), 559 }, nil 560 561 case *tracers.Tracer: 562 return tracer.GetResult() 563 564 default: 565 panic(fmt.Sprintf("bad tracer type %T", tracer)) 566 } 567 } 568 569 func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) { 570 571 block := api.eth.blockchain.GetBlockByHash(blockHash) 572 if block == nil { 573 return nil, vm.Context{}, nil, fmt.Errorf("block %#x not found", blockHash) 574 } 575 parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) 576 if parent == nil { 577 return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) 578 } 579 statedb, err := api.computeStateDB(parent, reexec) 580 if err != nil { 581 return nil, vm.Context{}, nil, err 582 } 583 584 if txIndex == 0 && len(block.Transactions()) == 0 { 585 return nil, vm.Context{}, statedb, nil 586 } 587 588 signer := types.MakeSigner(api.eth.blockchain.Config(), block.Number()) 589 590 for idx, tx := range block.Transactions() { 591 592 msg, _ := tx.AsMessage(signer) 593 context := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) 594 if idx == txIndex { 595 return msg, context, statedb, nil 596 } 597 598 vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{}) 599 if _, _, err := core.ApplyMessageEx(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { 600 return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) 601 } 602 603 statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) 604 } 605 return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, blockHash) 606 }