diff --git a/turbo/jsonrpc/trace_adhoc.go b/turbo/jsonrpc/trace_adhoc.go index cd52559d15a..a4ebbde56bc 100644 --- a/turbo/jsonrpc/trace_adhoc.go +++ b/turbo/jsonrpc/trace_adhoc.go @@ -22,6 +22,8 @@ import ( "encoding/json" "errors" "fmt" + "github.com/erigontech/erigon-lib/kv/rawdbv3" + "github.com/erigontech/erigon/turbo/snapshotsync/freezeblocks" "math" "strings" @@ -817,7 +819,7 @@ func (api *TraceAPIImpl) ReplayTransaction(ctx context.Context, txHash libcommon } var isBorStateSyncTxn bool - blockNum, _, ok, err := api.txnLookup(ctx, tx, txHash) + blockNum, txNum, ok, err := api.txnLookup(ctx, tx, txHash) if err != nil { return nil, err } @@ -843,30 +845,31 @@ func (api *TraceAPIImpl) ReplayTransaction(ctx context.Context, txHash libcommon isBorStateSyncTxn = true } - block, err := api.blockByNumberWithSenders(ctx, tx, blockNum) + header, err := api.headerByRPCNumber(ctx, rpc.BlockNumber(blockNum), tx) if err != nil { return nil, err } - if block == nil { - return nil, nil + + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + + txNumMin, err := txNumsReader.Min(tx, blockNum) + if err != nil { + return nil, err } - var txnIndex int - for idx := 0; idx < block.Transactions().Len() && !isBorStateSyncTxn; idx++ { - txn := block.Transactions()[idx] - if txn.Hash() == txHash { - txnIndex = idx - break - } + if txNumMin+2 > txNum { + return nil, fmt.Errorf("uint underflow txnums error txNum: %d, txNumMin: %d, blockNum: %d", txNum, txNumMin, blockNum) } + var txnIndex = int(txNum - txNumMin - 2) + if isBorStateSyncTxn { - txnIndex = block.Transactions().Len() + txnIndex = -1 } - signer := types.MakeSigner(chainConfig, blockNum, block.Time()) + signer := types.MakeSigner(chainConfig, blockNum, header.Time) // Returns an array of trace arrays, one trace array for each transaction - traces, _, err := api.callManyTransactions(ctx, tx, block, traceTypes, txnIndex, *gasBailOut, signer, chainConfig, traceConfig) + trace, _, err := api.callTransaction(ctx, tx, header, traceTypes, txnIndex, *gasBailOut, signer, chainConfig, traceConfig) if err != nil { return nil, err } @@ -886,25 +889,18 @@ func (api *TraceAPIImpl) ReplayTransaction(ctx context.Context, txHash libcommon } result := &TraceCallResult{} - for txno, trace := range traces { - // We're only looking for a specific transaction - if txno == txnIndex { - result.Output = trace.Output - if traceTypeTrace { - result.Trace = trace.Trace - } - if traceTypeStateDiff { - result.StateDiff = trace.StateDiff - } - if traceTypeVmTrace { - result.VmTrace = trace.VmTrace - } - - return trace, nil - } + result.Output = trace.Output + if traceTypeTrace { + result.Trace = trace.Trace + } + if traceTypeStateDiff { + result.StateDiff = trace.StateDiff + } + if traceTypeVmTrace { + result.VmTrace = trace.VmTrace } - return result, nil + return trace, nil } func (api *TraceAPIImpl) ReplayBlockTransactions(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, traceTypes []string, gasBailOut *bool, traceConfig *config.TraceConfig) ([]*TraceCallResult, error) { @@ -950,7 +946,7 @@ func (api *TraceAPIImpl) ReplayBlockTransactions(ctx context.Context, blockNrOrH signer := types.MakeSigner(chainConfig, blockNumber, block.Time()) // Returns an array of trace arrays, one trace array for each transaction - traces, _, err := api.callManyTransactions(ctx, tx, block, traceTypes, -1 /* all txn indices */, *gasBailOut, signer, chainConfig, traceConfig) + traces, _, err := api.callBlock(ctx, tx, block, traceTypes, *gasBailOut, signer, chainConfig, traceConfig) if err != nil { return nil, err } @@ -1212,14 +1208,14 @@ func (api *TraceAPIImpl) CallMany(ctx context.Context, calls json.RawMessage, pa cachedWriter := state.NewCachedWriter(noop, stateCache) ibs := state.New(cachedReader) - return api.doCallMany(ctx, dbtx, stateReader, stateCache, cachedWriter, ibs, - msgs, callParams, parentNrOrHash, nil, true /* gasBailout */, -1 /* all txn indices */, traceConfig) + return api.doCallBlock(ctx, dbtx, stateReader, stateCache, cachedWriter, ibs, + msgs, callParams, parentNrOrHash, nil, true /* gasBailout */, traceConfig) } -func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, stateReader state.StateReader, +func (api *TraceAPIImpl) doCallBlock(ctx context.Context, dbtx kv.Tx, stateReader state.StateReader, stateCache *shards.StateCache, cachedWriter state.StateWriter, ibs *state.IntraBlockState, msgs []types.Message, callParams []TraceCallParam, - parentNrOrHash *rpc.BlockNumberOrHash, header *types.Header, gasBailout bool, txIndexNeeded int, + parentNrOrHash *rpc.BlockNumberOrHash, header *types.Header, gasBailout bool, traceConfig *config.TraceConfig, ) ([]*TraceCallResult, error) { chainConfig, err := api.chainConfig(ctx, dbtx) @@ -1299,7 +1295,7 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, stateReader traceResult := &TraceCallResult{Trace: []*ParityTrace{}, TransactionHash: args.txHash} vmConfig := vm.Config{} - if (traceTypeTrace && (txIndexNeeded == -1 || txIndex == txIndexNeeded)) || traceTypeVmTrace { + if traceTypeTrace || traceTypeVmTrace { var ot OeTracer ot.config, err = parseOeTracerConfig(traceConfig) if err != nil { @@ -1308,7 +1304,7 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, stateReader ot.compat = api.compatibility ot.r = traceResult ot.idx = []string{fmt.Sprintf("%d-", txIndex)} - if traceTypeTrace && (txIndexNeeded == -1 || txIndex == txIndexNeeded) { + if traceTypeTrace { ot.traceAddr = []int{} } if traceTypeVmTrace { @@ -1389,7 +1385,11 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, stateReader return nil, err } } - sd.CompareStates(initialIbs, ibs) + if sd != nil { + if err = sd.CompareStates(initialIbs, ibs); err != nil { + return nil, err + } + } if err = ibs.CommitBlock(chainRules, cachedWriter); err != nil { return nil, err } @@ -1407,16 +1407,209 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, stateReader traceResult.Trace = []*ParityTrace{} } results = append(results, traceResult) - // When txIndexNeeded is not -1, we are tracing specific transaction in the block and not the entire block, so we stop after we've traced - // the required transaction - if txIndexNeeded != -1 && txIndex == txIndexNeeded { - break - } } return results, nil } +func (api *TraceAPIImpl) doCall(ctx context.Context, dbtx kv.Tx, stateReader state.StateReader, + stateCache *shards.StateCache, cachedWriter state.StateWriter, ibs *state.IntraBlockState, + msg types.Message, callParam TraceCallParam, + parentNrOrHash *rpc.BlockNumberOrHash, header *types.Header, gasBailout bool, txIndex int, + traceConfig *config.TraceConfig, +) (*TraceCallResult, error) { + chainConfig, err := api.chainConfig(ctx, dbtx) + if err != nil { + return nil, err + } + engine := api.engine() + + if parentNrOrHash == nil { + var num = rpc.LatestBlockNumber + parentNrOrHash = &rpc.BlockNumberOrHash{BlockNumber: &num} + } + blockNumber, hash, _, err := rpchelper.GetBlockNumber(ctx, *parentNrOrHash, dbtx, api._blockReader, api.filters) + if err != nil { + return nil, err + } + noop := state.NewNoopWriter() + + parentHeader, err := api.headerByRPCNumber(ctx, rpc.BlockNumber(blockNumber), dbtx) + if err != nil { + return nil, err + } + if parentHeader == nil { + return nil, fmt.Errorf("parent header %d(%x) not found", blockNumber, hash) + } + + // Setup context so it may be cancelled the call has completed + // or, in case of unmetered gas, setup a context with a timeout. + var cancel context.CancelFunc + if api.evmCallTimeout > 0 { + ctx, cancel = context.WithTimeout(ctx, api.evmCallTimeout) + } else { + ctx, cancel = context.WithCancel(ctx) + } + + // Make sure the context is cancelled when the call has completed + // this makes sure resources are cleaned up. + defer cancel() + + useParent := false + if header == nil { + header = parentHeader + useParent = true + } + + var baseTxNum uint64 + historicalStateReader, isHistoricalStateReader := stateReader.(state.HistoricalStateReader) + if isHistoricalStateReader { + baseTxNum = historicalStateReader.GetTxNum() + } + + blockCtx := transactions.NewEVMBlockContext(engine, header, parentNrOrHash.RequireCanonical, dbtx, api._blockReader, chainConfig) + + if isHistoricalStateReader { + historicalStateReader.SetTxNum(baseTxNum + uint64(txIndex)) + } + if err := libcommon.Stopped(ctx.Done()); err != nil { + return nil, err + } + + var traceTypeTrace, traceTypeStateDiff, traceTypeVmTrace bool + args := callParam + for _, traceType := range args.traceTypes { + switch traceType { + case TraceTypeTrace: + traceTypeTrace = true + case TraceTypeStateDiff: + traceTypeStateDiff = true + case TraceTypeVmTrace: + traceTypeVmTrace = true + default: + return nil, fmt.Errorf("unrecognized trace type: %s", traceType) + } + } + + traceResult := &TraceCallResult{Trace: []*ParityTrace{}, TransactionHash: args.txHash} + vmConfig := vm.Config{} + if traceTypeTrace || traceTypeVmTrace { + var ot OeTracer + ot.config, err = parseOeTracerConfig(traceConfig) + if err != nil { + return nil, err + } + ot.compat = api.compatibility + ot.r = traceResult + ot.idx = []string{fmt.Sprintf("%d-", txIndex)} + if traceTypeTrace { + ot.traceAddr = []int{} + } + if traceTypeVmTrace { + traceResult.VmTrace = &VmTrace{Ops: []*VmTraceOp{}} + } + vmConfig.Debug = true + vmConfig.Tracer = &ot + } + + if useParent { + blockCtx.GasLimit = math.MaxUint64 + blockCtx.MaxGasLimit = true + } + + // Clone the state cache before applying the changes for diff after transaction execution, clone is discarded + var cloneReader state.StateReader + var sd *StateDiff + if traceTypeStateDiff { + cloneCache := stateCache.Clone() + cloneReader = state.NewCachedReader(stateReader, cloneCache) + //cloneReader = stateReader + if isHistoricalStateReader { + historicalStateReader.SetTxNum(baseTxNum + uint64(txIndex)) + } + sdMap := make(map[libcommon.Address]*StateDiffAccount) + traceResult.StateDiff = sdMap + sd = &StateDiff{sdMap: sdMap} + } + + ibs.Reset() + var finalizeTxStateWriter state.StateWriter + if sd != nil { + finalizeTxStateWriter = sd + } else { + finalizeTxStateWriter = noop + } + + var txFinalized bool + var execResult *evmtypes.ExecutionResult + if args.isBorStateSyncTxn { + txFinalized = true + var stateSyncEvents []*types.Message + stateSyncEvents, err = api.stateSyncEvents(ctx, dbtx, header.Hash(), blockNumber, chainConfig) + if err != nil { + return nil, err + } + + execResult, err = tracer.TraceBorStateSyncTxnTraceAPI( + ctx, + &vmConfig, + chainConfig, + ibs, + finalizeTxStateWriter, + blockCtx, + header.Hash(), + header.Number.Uint64(), + header.Time, + stateSyncEvents, + ) + } else { + ibs.SetTxContext(txIndex) + txCtx := core.NewEVMTxContext(msg) + evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vmConfig) + gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) + + execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, gasBailout /*gasBailout*/) + } + if err != nil { + return nil, fmt.Errorf("first run for txIndex %d error: %w", txIndex, err) + } + + chainRules := chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Time) + traceResult.Output = libcommon.CopyBytes(execResult.ReturnData) + if traceTypeStateDiff { + initialIbs := state.New(cloneReader) + if !txFinalized { + if err = ibs.FinalizeTx(chainRules, sd); err != nil { + return nil, err + } + } + + if sd != nil { + if err = sd.CompareStates(initialIbs, ibs); err != nil { + return nil, err + } + } + + if err = ibs.CommitBlock(chainRules, cachedWriter); err != nil { + return nil, err + } + } else { + if !txFinalized { + if err = ibs.FinalizeTx(chainRules, noop); err != nil { + return nil, err + } + } + if err = ibs.CommitBlock(chainRules, cachedWriter); err != nil { + return nil, err + } + } + if !traceTypeTrace { + traceResult.Trace = []*ParityTrace{} + } + + return traceResult, nil +} + // RawTransaction implements trace_rawTransaction. func (api *TraceAPIImpl) RawTransaction(ctx context.Context, txHash libcommon.Hash, traceTypes []string) ([]interface{}, error) { var stub []interface{} diff --git a/turbo/jsonrpc/trace_filtering.go b/turbo/jsonrpc/trace_filtering.go index 1c0b410e1c3..b01728d5b55 100644 --- a/turbo/jsonrpc/trace_filtering.go +++ b/turbo/jsonrpc/trace_filtering.go @@ -67,7 +67,7 @@ func (api *TraceAPIImpl) Transaction(ctx context.Context, txHash common.Hash, ga } var isBorStateSyncTxn bool - blockNumber, _, ok, err := api.txnLookup(ctx, tx, txHash) + blockNumber, txNum, ok, err := api.txnLookup(ctx, tx, txHash) if err != nil { return nil, err } @@ -92,55 +92,49 @@ func (api *TraceAPIImpl) Transaction(ctx context.Context, txHash common.Hash, ga isBorStateSyncTxn = true } - block, err := api.blockByNumberWithSenders(ctx, tx, blockNumber) + header, err := api.headerByRPCNumber(ctx, rpc.BlockNumber(blockNumber), tx) if err != nil { return nil, err } - if block == nil { + if header == nil { return nil, nil } - var txIndex int + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + + txNumMin, err := txNumsReader.Min(tx, blockNumber) + if err != nil { + return nil, err + } + + if txNumMin+2 > txNum { + return nil, fmt.Errorf("uint underflow txnums error txNum: %d, txNumMin: %d, blockNum: %d", txNum, txNumMin, blockNumber) + } + + var txIndex = int(txNum - txNumMin - 2) + if isBorStateSyncTxn { - txIndex = block.Transactions().Len() - } else { - var found bool - for idx := 0; idx < block.Transactions().Len(); idx++ { - txn := block.Transactions()[idx] - if txn.Hash() == txHash { - txIndex = idx - found = true - break - } - } - if !found { - return nil, fmt.Errorf("txn with hash %x belongs to currentely non-canonical block %d. only canonical blocks can be traced", txHash, block.NumberU64()) - } + txIndex = -1 } bn := hexutil.Uint64(blockNumber) - hash := block.Hash() - signer := types.MakeSigner(chainConfig, blockNumber, block.Time()) + hash := header.Hash() + signer := types.MakeSigner(chainConfig, blockNumber, header.Time) // Returns an array of trace arrays, one trace array for each transaction - traces, _, err := api.callManyTransactions(ctx, tx, block, []string{TraceTypeTrace}, txIndex, *gasBailOut, signer, chainConfig, traceConfig) + trace, _, err := api.callTransaction(ctx, tx, header, []string{TraceTypeTrace}, txIndex, *gasBailOut, signer, chainConfig, traceConfig) if err != nil { return nil, err } - out := make([]ParityTrace, 0, len(traces)) + out := make([]ParityTrace, 0, len(trace.Trace)) blockno := uint64(bn) - for txno, trace := range traces { - // We're only looking for a specific transaction - if txno == txIndex { - for _, pt := range trace.Trace { - pt.BlockHash = &hash - pt.BlockNumber = &blockno - pt.TransactionHash = trace.TransactionHash - txpos := uint64(txno) - pt.TransactionPosition = &txpos - out = append(out, *pt) - } - } + for _, pt := range trace.Trace { + pt.BlockHash = &hash + pt.BlockNumber = &blockno + pt.TransactionHash = trace.TransactionHash + txpos := uint64(txIndex) + pt.TransactionPosition = &txpos + out = append(out, *pt) } return out, err @@ -215,7 +209,7 @@ func (api *TraceAPIImpl) Block(ctx context.Context, blockNr rpc.BlockNumber, gas return nil, err } signer := types.MakeSigner(cfg, blockNum, block.Time()) - traces, syscall, err := api.callManyTransactions(ctx, tx, block, []string{TraceTypeTrace}, -1 /* all txn indices */, *gasBailOut /* gasBailOut */, signer, cfg, traceConfig) + traces, syscall, err := api.callBlock(ctx, tx, block, []string{TraceTypeTrace}, *gasBailOut /* gasBailOut */, signer, cfg, traceConfig) if err != nil { return nil, err } @@ -711,12 +705,11 @@ func filterTrace(pt *ParityTrace, fromAddresses map[common.Address]struct{}, toA } } -func (api *TraceAPIImpl) callManyTransactions( +func (api *TraceAPIImpl) callBlock( ctx context.Context, dbtx kv.TemporalTx, block *types.Block, traceTypes []string, - txIndex int, gasBailOut bool, signer *types.Signer, cfg *chain.Config, @@ -822,8 +815,8 @@ func (api *TraceAPIImpl) callManyTransactions( msgs[i] = msg } - traces, cmErr := api.doCallMany(ctx, dbtx, stateReader, stateCache, cachedWriter, ibs, msgs, callParams, - &parentNrOrHash, header, gasBailOut /* gasBailout */, txIndex, traceConfig) + traces, cmErr := api.doCallBlock(ctx, dbtx, stateReader, stateCache, cachedWriter, ibs, msgs, callParams, + &parentNrOrHash, header, gasBailOut /* gasBailout */, traceConfig) if cmErr != nil { return nil, nil, cmErr @@ -836,6 +829,125 @@ func (api *TraceAPIImpl) callManyTransactions( return traces, syscall, nil } +func (api *TraceAPIImpl) callTransaction( + ctx context.Context, + dbtx kv.TemporalTx, + header *types.Header, + traceTypes []string, + txIndex int, + gasBailOut bool, + signer *types.Signer, + cfg *chain.Config, + traceConfig *config.TraceConfig, +) (*TraceCallResult, consensus.SystemCall, error) { + blockNumber := header.Number.Uint64() + pNo := blockNumber + if pNo > 0 { + pNo -= 1 + } + + parentNo := rpc.BlockNumber(pNo) + rules := cfg.Rules(blockNumber, header.Time) + var txn types.Transaction + var borStateSyncTxnHash common.Hash + if cfg.Bor != nil { + // check if this header has state sync txn + blockHash := header.Hash() + borStateSyncTxnHash = bortypes.ComputeBorTxHash(blockNumber, blockHash) + + var ok bool + var err error + + if api.useBridgeReader { + _, ok, err = api.bridgeReader.EventTxnLookup(ctx, borStateSyncTxnHash) + + } else { + _, ok, err = api._blockReader.EventLookup(ctx, dbtx, borStateSyncTxnHash) + } + if err != nil { + return nil, nil, err + } + if ok { + txn = bortypes.NewBorTransaction() + } + } else { + var err error + txn, err = api._txnReader.TxnByIdxInBlock(ctx, dbtx, blockNumber, txIndex) + if err != nil { + return nil, nil, err + } + } + + parentHash := header.ParentHash + parentNrOrHash := rpc.BlockNumberOrHash{ + BlockNumber: &parentNo, + BlockHash: &parentHash, + RequireCanonical: true, + } + + stateReader, err := rpchelper.CreateStateReader(ctx, dbtx, api._blockReader, parentNrOrHash, 0, api.filters, api.stateCache, cfg.ChainName) + if err != nil { + return nil, nil, err + } + stateCache := shards.NewStateCache( + 32, 0 /* no limit */) // this cache living only during current RPC call, but required to store state writes + cachedReader := state.NewCachedReader(stateReader, stateCache) + noop := state.NewNoopWriter() + cachedWriter := state.NewCachedWriter(noop, stateCache) + ibs := state.New(cachedReader) + + engine := api.engine() + consensusHeaderReader := consensuschain.NewReader(cfg, dbtx, nil, nil) + logger := log.New("trace_filtering") + err = core.InitializeBlockExecution(engine.(consensus.Engine), consensusHeaderReader, header, cfg, ibs, nil, logger, nil) + if err != nil { + return nil, nil, err + } + if err = ibs.CommitBlock(rules, cachedWriter); err != nil { + return nil, nil, err + } + + var txnHash common.Hash + var msg types.Message + if cfg.Bor != nil { + txnHash = borStateSyncTxnHash + // we use an empty message for bor state sync txn since it gets handled differently + } else { + txnHash = txn.Hash() + msg, err = txn.AsMessage(*signer, header.BaseFee, rules) + if err != nil { + return nil, nil, fmt.Errorf("convert txn into msg: %w", err) + } + + // gnosis might have a fee free account here + if msg.FeeCap().IsZero() && engine != nil { + syscall := func(contract common.Address, data []byte) ([]byte, error) { + return core.SysCallContract(contract, data, cfg, ibs, header, engine, true /* constCall */) + } + msg.SetIsFree(engine.IsServiceTransaction(msg.From(), syscall)) + } + } + + callParam := TraceCallParam{ + txHash: &txnHash, + traceTypes: traceTypes, + isBorStateSyncTxn: cfg.Bor != nil, + } + + trace, cmErr := api.doCall(ctx, dbtx, stateReader, stateCache, cachedWriter, ibs, msg, callParam, + &parentNrOrHash, header, gasBailOut /* gasBailout */, txIndex, traceConfig) + + if cmErr != nil { + return nil, nil, cmErr + } + + syscall := func(contract common.Address, data []byte) ([]byte, error) { + return core.SysCallContract(contract, data, cfg, ibs, header, engine, false /* constCall */) + } + + return trace, syscall, nil +} + // TraceFilterRequest represents the arguments for trace_filter type TraceFilterRequest struct { FromBlock *hexutil.Uint64 `json:"fromBlock"`