github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/ethapi/tx_tracer.go (about) 1 // Copyright 2015 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 ethapi 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "runtime" 25 "sync" 26 "time" 27 28 "github.com/unicornultrafoundation/go-u2u/common" 29 "github.com/unicornultrafoundation/go-u2u/common/hexutil" 30 "github.com/unicornultrafoundation/go-u2u/core/state" 31 "github.com/unicornultrafoundation/go-u2u/core/types" 32 "github.com/unicornultrafoundation/go-u2u/core/vm" 33 "github.com/unicornultrafoundation/go-u2u/evmcore" 34 "github.com/unicornultrafoundation/go-u2u/evmcore/txtracer" 35 "github.com/unicornultrafoundation/go-u2u/log" 36 "github.com/unicornultrafoundation/go-u2u/params" 37 "github.com/unicornultrafoundation/go-u2u/rpc" 38 "github.com/unicornultrafoundation/go-u2u/u2u" 39 "github.com/unicornultrafoundation/go-u2u/u2u/contracts/sfc" 40 "github.com/unicornultrafoundation/go-u2u/utils/signers/gsignercache" 41 ) 42 43 // PublicTxTraceAPI provides an API to access transaction tracing. 44 // It offers only methods that operate on public data that is freely available to anyone. 45 type PublicTxTraceAPI struct { 46 b Backend 47 } 48 49 // NewPublicTxTraceAPI creates a new transaction trace API. 50 func NewPublicTxTraceAPI(b Backend) *PublicTxTraceAPI { 51 return &PublicTxTraceAPI{b} 52 } 53 54 // Trace transaction and return processed result 55 func (s *PublicTxTraceAPI) traceTx( 56 ctx context.Context, blockCtx vm.BlockContext, msg types.Message, 57 state *state.StateDB, block *evmcore.EvmBlock, tx *types.Transaction, index uint64, 58 status uint64, chainConfig *params.ChainConfig) (*[]txtracer.ActionTrace, error) { 59 60 // Providing default config 61 // In case of trace transaction node, this config is changed 62 cfg := u2u.DefaultVMConfig 63 cfg.Debug = true 64 txTracer := txtracer.NewTraceStructLogger(nil) 65 cfg.Tracer = txTracer 66 cfg.NoBaseFee = true 67 68 // Setup context so it may be cancelled the call has completed 69 // or, in case of unmetered gas, setup a context with a timeout. 70 var timeout time.Duration = 5 * time.Second 71 if s.b.RPCTimeout() > 0 { 72 timeout = s.b.RPCTimeout() 73 } 74 var cancel context.CancelFunc 75 ctx, cancel = context.WithTimeout(ctx, timeout) 76 77 // Make sure the context is cancelled when the call has completed 78 // this makes sure resources are cleaned up. 79 defer cancel() 80 txTracer.SetTx(tx.Hash()) 81 txTracer.SetFrom(msg.From()) 82 txTracer.SetTo(msg.To()) 83 txTracer.SetValue(*msg.Value()) 84 txTracer.SetBlockHash(block.Hash) 85 txTracer.SetBlockNumber(block.Number) 86 txTracer.SetTxIndex(uint(index)) 87 txTracer.SetGasUsed(tx.Gas()) 88 89 var txContext = evmcore.NewEVMTxContext(msg) 90 vmenv := vm.NewEVM(blockCtx, txContext, state, chainConfig, cfg) 91 92 // Wait for the context to be done and cancel the evm. Even if the 93 // EVM has finished, cancelling may be done (repeatedly) 94 go func() { 95 <-ctx.Done() 96 vmenv.Cancel() 97 }() 98 99 // Setup the gas pool and stateDB 100 gp := new(evmcore.GasPool).AddGas(msg.Gas()) 101 state.Prepare(tx.Hash(), int(index)) 102 result, err := evmcore.ApplyMessage(vmenv, msg, gp) 103 104 if result != nil { 105 txTracer.SetGasUsed(result.UsedGas) 106 } 107 // Process traces if any 108 txTracer.ProcessTx() 109 traceActions := txTracer.GetTraceActions() 110 state.Finalise(true) 111 if err != nil { 112 errTrace := txtracer.GetErrorTraceFromMsg(&msg, block.Hash, *block.Number, tx.Hash(), index, err) 113 at := make([]txtracer.ActionTrace, 0) 114 at = append(at, *errTrace) 115 if status == 1 { 116 return nil, fmt.Errorf("invalid transaction replay state at %s", tx.Hash().String()) 117 } 118 return &at, nil 119 } 120 // If the timer caused an abort, return an appropriate error message 121 if vmenv.Cancelled() { 122 log.Info("EVM was canceled due to timeout when replaying transaction ", "txHash", tx.Hash().String()) 123 return nil, fmt.Errorf("timeout when replaying tx") 124 } 125 126 if result != nil && result.Err != nil { 127 if len(*traceActions) == 0 { 128 log.Error("error in result when replaying transaction:", "txHash", tx.Hash().String(), " err", result.Err.Error()) 129 errTrace := txtracer.GetErrorTraceFromMsg(&msg, block.Hash, *block.Number, tx.Hash(), index, result.Err) 130 at := make([]txtracer.ActionTrace, 0) 131 at = append(at, *errTrace) 132 return &at, nil 133 } 134 if status == 1 { 135 return nil, fmt.Errorf("invalid transaction replay state at %s", tx.Hash().String()) 136 } 137 return traceActions, nil 138 } 139 140 if status == 0 { 141 return nil, fmt.Errorf("invalid transaction replay state at %s", tx.Hash().String()) 142 } 143 return traceActions, nil 144 } 145 146 // Gets all transaction from specified block and process them 147 func (s *PublicTxTraceAPI) traceBlock(ctx context.Context, block *evmcore.EvmBlock, txHash *common.Hash, traceIndex *[]hexutil.Uint) (*[]txtracer.ActionTrace, error) { 148 var ( 149 blockNumber int64 150 parentBlockNr rpc.BlockNumber 151 ) 152 153 if block != nil && block.NumberU64() > 0 { 154 blockNumber = block.Number.Int64() 155 parentBlockNr = rpc.BlockNumber(blockNumber - 1) 156 } else { 157 return nil, fmt.Errorf("invalid block for tracing") 158 } 159 160 // Check if node is synced 161 if s.b.CurrentBlock().Number.Int64() < blockNumber { 162 return nil, fmt.Errorf("invalid block %v for tracing, current block is %v", blockNumber, s.b.CurrentBlock()) 163 } 164 165 callTrace := txtracer.CallTrace{ 166 Actions: make([]txtracer.ActionTrace, 0), 167 } 168 169 signer := gsignercache.Wrap(types.MakeSigner(s.b.ChainConfig(), block.Number)) 170 blockCtx := s.b.GetBlockContext(block.Header()) 171 172 allTxOK := true 173 // loop thru all transactions in the block get them from DB 174 for _, tx := range block.Transactions { 175 // Get transaction trace from backend persistent store 176 // Otherwise replay transaction and save trace to db 177 traces, err := s.b.TxTraceByHash(ctx, tx.Hash()) 178 if err == nil { 179 if txHash == nil || *txHash == tx.Hash() { 180 callTrace.AddTraces(traces, traceIndex) 181 if txHash != nil { 182 break 183 } 184 } 185 } else { 186 allTxOK = false 187 break 188 } 189 } 190 191 if !allTxOK { 192 193 // get state from block parent, to be able to recreate correct nonces 194 state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHash{BlockNumber: &parentBlockNr}) 195 if err != nil { 196 return nil, fmt.Errorf("cannot get state for block %v, error: %v", block.NumberU64(), err.Error()) 197 } 198 receipts, err := s.b.GetReceiptsByNumber(ctx, rpc.BlockNumber(blockNumber)) 199 if err != nil { 200 log.Debug("Cannot get receipts for block", "block", blockNumber, "err", err.Error()) 201 return nil, fmt.Errorf("cannot get receipts for block %v, error: %v", block.NumberU64(), err.Error()) 202 } 203 204 callTrace = txtracer.CallTrace{ 205 Actions: make([]txtracer.ActionTrace, 0), 206 } 207 208 // loop thru all transactions in the block and process them 209 for i, tx := range block.Transactions { 210 if txHash == nil || *txHash == tx.Hash() { 211 212 log.Info("Replaying transaction", "txHash", tx.Hash().String()) 213 // get full transaction info 214 tx, _, index, err := s.b.GetTransaction(ctx, tx.Hash()) 215 if err != nil { 216 log.Error("cannot replay tranasction", "txHash", tx.Hash().String(), "err", err.Error()) 217 return nil, fmt.Errorf("cannot replay tranasction %s, error %s", tx.Hash().String(), err) 218 } 219 msg, err := evmcore.TxAsMessage(tx, signer, block.BaseFee) 220 if err != nil { 221 log.Error("cannot get message from transaction", "txHash", tx.Hash().String(), "err", err.Error()) 222 return nil, fmt.Errorf("cannot get message from transaction %s, error %s", tx.Hash().String(), err) 223 } 224 from := msg.From() 225 if tx.To() != nil && *tx.To() == sfc.ContractAddress { 226 errTrace := txtracer.GetErrorTrace(block.Hash, *block.Number, &from, tx.To(), tx.Hash(), index, errors.New("sfc tx")) 227 at := make([]txtracer.ActionTrace, 0) 228 at = append(at, *errTrace) 229 callTrace.AddTrace(errTrace) 230 jsonTraceBytes, _ := json.Marshal(&at) 231 s.b.TxTraceSave(ctx, tx.Hash(), jsonTraceBytes) 232 } else { 233 txTraces, err := s.traceTx(ctx, blockCtx, msg, state, block, tx, index, receipts[i].Status, s.b.ChainConfig()) 234 if err != nil { 235 log.Debug("Cannot get transaction trace for transaction", "txHash", tx.Hash().String(), "err", err.Error()) 236 callTrace.AddTrace(txtracer.GetErrorTraceFromMsg(&msg, block.Hash, *block.Number, tx.Hash(), index, err)) 237 } else { 238 callTrace.AddTraces(txTraces, traceIndex) 239 240 // Save trace result into persistent key-value store 241 jsonTraceBytes, _ := json.Marshal(txTraces) 242 s.b.TxTraceSave(ctx, tx.Hash(), jsonTraceBytes) 243 } 244 } 245 if txHash != nil { 246 break 247 } 248 } else if txHash != nil { 249 log.Info("Replaying transaction without trace", "txHash", tx.Hash().String()) 250 // Generate the next state snapshot fast without tracing 251 msg, err := evmcore.TxAsMessage(tx, signer, block.BaseFee) 252 if err != nil { 253 return nil, fmt.Errorf("cannot replay tranasction %s, error %s", tx.Hash().String(), err) 254 } 255 state.Prepare(tx.Hash(), i) 256 vmConfig := u2u.DefaultVMConfig 257 vmConfig.NoBaseFee = true 258 vmConfig.Debug = false 259 vmConfig.Tracer = nil 260 vmenv := vm.NewEVM(blockCtx, evmcore.NewEVMTxContext(msg), state, s.b.ChainConfig(), vmConfig) 261 res, err := evmcore.ApplyMessage(vmenv, msg, new(evmcore.GasPool).AddGas(msg.Gas())) 262 failed := false 263 if err != nil { 264 failed = true 265 log.Error("Cannot replay transaction", "txHash", tx.Hash().String(), "err", err.Error()) 266 } 267 if res != nil && res.Err != nil { 268 failed = true 269 log.Debug("Error replaying transaction", "txHash", tx.Hash().String(), "err", res.Err.Error()) 270 } 271 // Finalize the state so any modifications are written to the trie 272 state.Finalise(true) 273 if (failed && receipts[i].Status == 1) || (!failed && receipts[i].Status == 0) { 274 return nil, fmt.Errorf("invalid transaction replay state at %s", tx.Hash().String()) 275 } 276 } 277 } 278 } 279 280 // In case of empty result create empty trace for empty block 281 if len(callTrace.Actions) == 0 { 282 if traceIndex != nil || txHash != nil { 283 return nil, nil 284 } else { 285 emptyTrace := txtracer.CallTrace{ 286 Actions: make([]txtracer.ActionTrace, 0), 287 } 288 blockTrace := txtracer.NewActionTrace(block.Hash, *block.Number, common.Hash{}, 0, "empty") 289 txAction := txtracer.NewAddressAction(&common.Address{}, 0, []byte{}, nil, hexutil.Big{}, nil) 290 blockTrace.Action = *txAction 291 blockTrace.Error = "Empty block" 292 emptyTrace.AddTrace(blockTrace) 293 return &emptyTrace.Actions, nil 294 } 295 } 296 297 return &callTrace.Actions, nil 298 } 299 300 /* trace_block function returns transaction traces in givven block 301 * When blockNr is -1 the chain head is returned. 302 * When blockNr is -2 the pending chain head is returned. 303 * When fullTx is true all transactions in the block are returned, otherwise 304 * only the transaction hash is returned. 305 */ 306 func (s *PublicTxTraceAPI) Block(ctx context.Context, numberOrHash rpc.BlockNumberOrHash) (*[]txtracer.ActionTrace, error) { 307 308 blockNr, _ := numberOrHash.Number() 309 310 defer func(start time.Time) { 311 log.Info("Executing trace_block call finished", "blockNr", blockNr.Int64(), "runtime", time.Since(start)) 312 }(time.Now()) 313 314 block, err := s.b.BlockByNumber(ctx, blockNr) 315 if err != nil { 316 log.Debug("Cannot get block from db", "blockNr", blockNr) 317 return nil, err 318 } 319 320 return s.traceBlock(ctx, block, nil, nil) 321 } 322 323 // Transaction trace_transaction function returns transaction traces 324 func (s *PublicTxTraceAPI) Transaction(ctx context.Context, hash common.Hash) (*[]txtracer.ActionTrace, error) { 325 defer func(start time.Time) { 326 log.Info("Executing trace_transaction call finished", "txHash", hash.String(), "runtime", time.Since(start)) 327 }(time.Now()) 328 return s.traceTxHash(ctx, hash, nil) 329 } 330 331 // Get trace_get function returns transaction traces on specified index position of the traces 332 // If index is nil, then just root trace is returned 333 func (s *PublicTxTraceAPI) Get(ctx context.Context, hash common.Hash, traceIndex []hexutil.Uint) (*[]txtracer.ActionTrace, error) { 334 defer func(start time.Time) { 335 log.Info("Executing trace_get call finished", "txHash", hash.String(), "index", traceIndex, "runtime", time.Since(start)) 336 }(time.Now()) 337 return s.traceTxHash(ctx, hash, &traceIndex) 338 } 339 340 // traceTxHash looks for a block of this transaction hash and trace it 341 func (s *PublicTxTraceAPI) traceTxHash(ctx context.Context, hash common.Hash, traceIndex *[]hexutil.Uint) (*[]txtracer.ActionTrace, error) { 342 _, blockNumber, _, _ := s.b.GetTransaction(ctx, hash) 343 blkNr := rpc.BlockNumber(blockNumber) 344 block, err := s.b.BlockByNumber(ctx, blkNr) 345 if err != nil { 346 log.Debug("Cannot get block from db", "blockNr", blkNr) 347 return nil, err 348 } 349 callTrace := txtracer.CallTrace{ 350 Actions: make([]txtracer.ActionTrace, 0), 351 } 352 353 // Get transaction trace from backend persistent store 354 // Otherwise replay transaction and save trace to db 355 traces, err := s.b.TxTraceByHash(ctx, hash) 356 if err == nil && len(*traces) > 0 { 357 callTrace.AddTraces(traces, traceIndex) 358 } 359 if len(callTrace.Actions) != 0 { 360 return &callTrace.Actions, nil 361 } 362 363 return s.traceBlock(ctx, block, &hash, traceIndex) 364 } 365 366 // FilterArgs represents the arguments for specifiing trace targets 367 type FilterArgs struct { 368 FromAddress *[]common.Address `json:"fromAddress"` 369 ToAddress *[]common.Address `json:"toAddress"` 370 FromBlock *rpc.BlockNumberOrHash `json:"fromBlock"` 371 ToBlock *rpc.BlockNumberOrHash `json:"toBlock"` 372 After uint `json:"after"` 373 Count uint `json:"count"` 374 } 375 376 // Filter is function for trace_filter rpc call 377 func (s *PublicTxTraceAPI) Filter(ctx context.Context, args FilterArgs) (*[]txtracer.ActionTrace, error) { 378 // add log after execution 379 defer func(start time.Time) { 380 381 var data []interface{} 382 if args.FromBlock != nil { 383 data = append(data, "fromBlock", args.FromBlock.BlockNumber.Int64()) 384 } 385 if args.ToBlock != nil { 386 data = append(data, "toBlock", args.ToBlock.BlockNumber.Int64()) 387 } 388 if args.FromAddress != nil { 389 adresses := make([]string, 0) 390 for _, addr := range *args.FromAddress { 391 adresses = append(adresses, addr.String()) 392 } 393 data = append(data, "fromAddr", adresses) 394 } 395 if args.ToAddress != nil { 396 adresses := make([]string, 0) 397 for _, addr := range *args.ToAddress { 398 adresses = append(adresses, addr.String()) 399 } 400 data = append(data, "toAddr", adresses) 401 } 402 data = append(data, "time", time.Since(start)) 403 log.Info("Executing trace_filter call finished", data...) 404 }(time.Now()) 405 406 // process arguments 407 var ( 408 fromBlock, toBlock rpc.BlockNumber 409 mainErr error 410 ) 411 if args.FromBlock != nil { 412 fromBlock = *args.FromBlock.BlockNumber 413 } 414 if args.ToBlock != nil { 415 toBlock = *args.ToBlock.BlockNumber 416 if toBlock == rpc.LatestBlockNumber || toBlock == rpc.PendingBlockNumber { 417 toBlock = rpc.BlockNumber(s.b.CurrentBlock().NumberU64()) 418 } 419 } else { 420 toBlock = rpc.BlockNumber(s.b.CurrentBlock().NumberU64()) 421 } 422 423 // counter of processed traces 424 var traceAdded, traceCount uint 425 var fromAddresses, toAddresses map[common.Address]struct{} 426 if args.FromAddress != nil { 427 fromAddresses = make(map[common.Address]struct{}) 428 for _, addr := range *args.FromAddress { 429 fromAddresses[addr] = struct{}{} 430 } 431 } 432 if args.ToAddress != nil { 433 toAddresses = make(map[common.Address]struct{}) 434 for _, addr := range *args.ToAddress { 435 toAddresses[addr] = struct{}{} 436 } 437 } 438 439 // check for context timeout 440 contextDone := false 441 go func() { 442 <-ctx.Done() 443 contextDone = true 444 }() 445 446 // struct for collecting result traces 447 callTrace := txtracer.CallTrace{ 448 Actions: make([]txtracer.ActionTrace, 0), 449 } 450 451 // count of traces doesn't matter so use parallel workers 452 if args.Count == 0 { 453 workerCount := runtime.NumCPU() / 2 454 blocks := make(chan rpc.BlockNumber, 10000) 455 results := make(chan txtracer.ActionTrace, 100000) 456 457 // create workers and their sync group 458 var wg sync.WaitGroup 459 for w := 0; w < workerCount; w++ { 460 wg.Add(1) 461 wId := w 462 go func() { 463 defer wg.Done() 464 worker(wId, s, ctx, blocks, results, fromAddresses, toAddresses) 465 }() 466 } 467 468 // add all blocks in specified range for processing 469 for i := fromBlock; i <= toBlock; i++ { 470 blocks <- i 471 } 472 close(blocks) 473 474 var wgResult sync.WaitGroup 475 wgResult.Add(1) 476 go func() { 477 defer wgResult.Done() 478 // collect results 479 for trace := range results { 480 callTrace.AddTrace(&trace) 481 } 482 }() 483 484 // wait for proccessing all blocks 485 wg.Wait() 486 close(results) 487 488 wgResult.Wait() 489 } else { 490 blocks: 491 // go thru all blocks in specified range 492 for i := fromBlock; i <= toBlock; i++ { 493 block, err := s.b.BlockByNumber(ctx, i) 494 if err != nil { 495 mainErr = err 496 break 497 } 498 499 // when block has any transaction, then process it 500 if block != nil && block.Transactions.Len() > 0 { 501 traces, err := s.traceBlock(ctx, block, nil, nil) 502 if err != nil { 503 mainErr = err 504 break 505 } 506 507 // loop thru all traces from the block 508 // and check 509 for _, trace := range *traces { 510 511 if args.Count == 0 || traceAdded < args.Count { 512 addTrace := true 513 514 if args.FromAddress != nil || args.ToAddress != nil { 515 if args.FromAddress != nil { 516 if trace.Action.From == nil { 517 addTrace = false 518 } else { 519 if _, ok := fromAddresses[*trace.Action.From]; !ok { 520 addTrace = false 521 } 522 } 523 } 524 if args.ToAddress != nil { 525 if trace.Action.To == nil { 526 addTrace = false 527 } else if _, ok := toAddresses[*trace.Action.To]; !ok { 528 addTrace = false 529 } 530 } 531 } 532 if addTrace { 533 if traceCount >= args.After { 534 callTrace.AddTrace(&trace) 535 traceAdded++ 536 } 537 traceCount++ 538 } 539 } else { 540 // already reached desired count of traces in batch 541 break blocks 542 } 543 } 544 } 545 if contextDone { 546 break 547 } 548 } 549 } 550 551 //when timeout occured or another error 552 if contextDone || mainErr != nil { 553 if mainErr != nil { 554 return nil, mainErr 555 } 556 return nil, fmt.Errorf("timeout when scanning blocks") 557 } 558 559 return &callTrace.Actions, nil 560 } 561 562 func worker(id int, 563 s *PublicTxTraceAPI, 564 ctx context.Context, 565 blocks <-chan rpc.BlockNumber, 566 results chan<- txtracer.ActionTrace, 567 fromAddresses map[common.Address]struct{}, 568 toAddresses map[common.Address]struct{}) { 569 570 for i := range blocks { 571 block, err := s.b.BlockByNumber(ctx, i) 572 if err != nil { 573 break 574 } 575 576 // when block has any transaction, then process it 577 if block != nil && block.Transactions.Len() > 0 { 578 traces, err := s.traceBlock(ctx, block, nil, nil) 579 if err != nil { 580 break 581 } 582 for _, trace := range *traces { 583 addTrace := true 584 585 if len(fromAddresses) > 0 { 586 587 if trace.Action.From == nil { 588 addTrace = false 589 } else { 590 if _, ok := fromAddresses[*trace.Action.From]; !ok { 591 addTrace = false 592 } 593 } 594 } 595 if len(toAddresses) > 0 { 596 if trace.Action.To == nil { 597 addTrace = false 598 } else if _, ok := toAddresses[*trace.Action.To]; !ok { 599 addTrace = false 600 } 601 } 602 if addTrace { 603 results <- trace 604 } 605 } 606 } 607 } 608 }