diff --git a/cmd/rpcdaemon/README.md b/cmd/rpcdaemon/README.md index 4629b79180b..fe5e786e7aa 100644 --- a/cmd/rpcdaemon/README.md +++ b/cmd/rpcdaemon/README.md @@ -338,6 +338,7 @@ The following table shows the current implementation status of Erigon's RPC daem | debug_traceTransaction | Yes | Streaming (can handle huge results) | | debug_traceCall | Yes | Streaming (can handle huge results) | | debug_traceCallMany | Yes | Erigon Method PR#4567. | +| debug_traceBatchByNumber | Yes | Streaming (can handle huge results) | | | | | | trace_call | Yes | | | trace_callMany | Yes | | diff --git a/docs/endpoints/endpoints.md b/docs/endpoints/endpoints.md index 4faadfb0971..5e3a947172d 100644 --- a/docs/endpoints/endpoints.md +++ b/docs/endpoints/endpoints.md @@ -46,6 +46,7 @@ If the endpoint is not in the list below, it means this specific endpoint is not - debug_traceCall - debug_traceTransaction - debug_traceTransactionCounters +- debug_traceBatchByNumber ## engine diff --git a/turbo/jsonrpc/debug_api.go b/turbo/jsonrpc/debug_api.go index 1f65b7d47c4..3f5eed08c7e 100644 --- a/turbo/jsonrpc/debug_api.go +++ b/turbo/jsonrpc/debug_api.go @@ -44,6 +44,7 @@ type PrivateDebugAPI interface { GetRawHeader(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutility.Bytes, error) GetRawBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutility.Bytes, error) TraceTransactionCounters(ctx context.Context, hash common.Hash, config *tracers.TraceConfig_ZkEvm, stream *jsoniter.Stream) error + TraceBatchByNumber(ctx context.Context, number rpc.BlockNumber, config *tracers.TraceConfig_ZkEvm, stream *jsoniter.Stream) error } // PrivateDebugAPIImpl is implementation of the PrivateDebugAPI interface based on remote Db access diff --git a/turbo/jsonrpc/tracing_block_zkevm.go b/turbo/jsonrpc/tracing_block_zkevm.go new file mode 100644 index 00000000000..c5e4558901b --- /dev/null +++ b/turbo/jsonrpc/tracing_block_zkevm.go @@ -0,0 +1,184 @@ +package jsonrpc + +import ( + "context" + "time" + + jsoniter "github.com/json-iterator/go" + "github.com/ledgerwatch/erigon-lib/chain" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/core" + "github.com/ledgerwatch/erigon/core/rawdb" + "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/core/vm/evmtypes" + "github.com/ledgerwatch/erigon/eth/tracers" + "github.com/ledgerwatch/erigon/rpc" + "github.com/ledgerwatch/erigon/turbo/services" + "github.com/ledgerwatch/erigon/turbo/transactions" + "github.com/ledgerwatch/erigon/zk/hermez_db" +) + +type blockTracer struct { + ctx context.Context + stream *jsoniter.Stream + engine consensus.EngineReader + tx kv.Tx + config *tracers.TraceConfig_ZkEvm + chainConfig *chain.Config + _blockReader services.FullBlockReader + historyV3 bool + evmCallTimeout time.Duration +} + +func (bt *blockTracer) TraceBlock(block *types.Block) error { + txEnv, err := transactions.ComputeTxEnv_ZkEvm(bt.ctx, bt.engine, block, bt.chainConfig, bt._blockReader, bt.tx, 0, bt.historyV3) + if err != nil { + bt.stream.WriteNil() + return err + } + bt.stream.WriteArrayStart() + + borTx := rawdb.ReadBorTransactionForBlock(bt.tx, block.NumberU64()) + txns := block.Transactions() + if borTx != nil && *bt.config.BorTraceEnabled { + txns = append(txns, borTx) + } + + txTracerEnv := txTracerEnv{ + block: block, + txEnv: txEnv, + cumulativeGas: uint64(0), + hermezReader: hermez_db.NewHermezDbReader(bt.tx), + chainConfig: bt.chainConfig, + engine: bt.engine, + } + + for idx, txn := range txns { + if err := bt.traceLastTxFlushed(txTracerEnv, txn, idx); err != nil { + return err + } + } + bt.stream.WriteArrayEnd() + bt.stream.Flush() + + return nil +} + +func (bt *blockTracer) traceLastTxFlushed(txTracerEnv txTracerEnv, txn types.Transaction, idx int) error { + if err := bt.traceTransactionUnflushed(txTracerEnv, txn, idx); err != nil { + return err + } + + if idx != len(txTracerEnv.block.Transactions())-1 { + bt.stream.WriteMore() + } + bt.stream.Flush() + return nil +} + +func (bt *blockTracer) traceTransactionUnflushed(txTracerEnv txTracerEnv, txn types.Transaction, idx int) error { + txHash := txn.Hash() + bt.stream.WriteObjectStart() + bt.stream.WriteObjectField("txHash") + bt.stream.WriteString(txHash.Hex()) + bt.stream.WriteMore() + bt.stream.WriteObjectField("result") + select { + default: + case <-bt.ctx.Done(): + bt.stream.WriteNil() + return bt.ctx.Err() + } + + txCtx, msg, err := txTracerEnv.GetTxExecuteContext(txn, idx) + if err != nil { + bt.stream.WriteNil() + return err + } + + if err = transactions.TraceTx( + bt.ctx, + msg, + txTracerEnv.txEnv.BlockContext, + txCtx, + txTracerEnv.txEnv.Ibs, + bt.config, + bt.chainConfig, + bt.stream, + bt.evmCallTimeout, + ); err == nil { + rules := bt.chainConfig.Rules(txTracerEnv.block.NumberU64(), txTracerEnv.block.Time()) + err = txTracerEnv.txEnv.Ibs.FinalizeTx(rules, state.NewNoopWriter()) + } + bt.stream.WriteObjectEnd() + + // if we have an error we want to output valid json for it before continuing after clearing down potential writes to the stream + if err != nil { + bt.handleError(err) + if err != nil { + return err + } + } + + return nil +} + +func (bt *blockTracer) handleError(err error) { + bt.stream.WriteMore() + bt.stream.WriteObjectStart() + rpc.HandleError(err, bt.stream) + bt.stream.WriteObjectEnd() +} + +type txTracerEnv struct { + hermezReader state.ReadOnlyHermezDb + chainConfig *chain.Config + engine consensus.EngineReader + block *types.Block + cumulativeGas uint64 + txEnv transactions.TxEnv +} + +func (tt *txTracerEnv) GetTxExecuteContext(txn types.Transaction, idx int) (evmtypes.TxContext, types.Message, error) { + txHash := txn.Hash() + evm, effectiveGasPricePercentage, err := core.PrepareForTxExecution( + tt.chainConfig, + &vm.Config{}, + &tt.txEnv.BlockContext, + tt.hermezReader, + tt.txEnv.Ibs, + tt.block, + &txHash, + idx, + ) + if err != nil { + return evmtypes.TxContext{}, types.Message{}, err + } + + msg, _, err := core.GetTxContext( + tt.chainConfig, + tt.engine, + tt.txEnv.Ibs, + tt.block.Header(), + txn, + evm, + effectiveGasPricePercentage, + ) + if err != nil { + return evmtypes.TxContext{}, types.Message{}, err + } + + txCtx := evmtypes.TxContext{ + TxHash: txHash, + Origin: msg.From(), + GasPrice: msg.GasPrice(), + Txn: txn, + CumulativeGasUsed: &tt.cumulativeGas, + BlockNum: tt.block.NumberU64(), + } + + return txCtx, msg, nil +} diff --git a/turbo/jsonrpc/tracing_zkevm.go b/turbo/jsonrpc/tracing_zkevm.go index c54a10fe173..659f9abd3f0 100644 --- a/turbo/jsonrpc/tracing_zkevm.go +++ b/turbo/jsonrpc/tracing_zkevm.go @@ -2,6 +2,7 @@ package jsonrpc import ( "context" + "errors" "fmt" "math/big" "time" @@ -81,87 +82,20 @@ func (api *PrivateDebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rp stream.WriteNil() return err } - engine := api.engine() - - txEnv, err := transactions.ComputeTxEnv_ZkEvm(ctx, engine, block, chainConfig, api._blockReader, tx, 0, api.historyV3(tx)) - if err != nil { - stream.WriteNil() - return err - } - blockCtx := txEnv.BlockContext - ibs := txEnv.Ibs - rules := chainConfig.Rules(block.NumberU64(), block.Time()) - stream.WriteArrayStart() - - borTx := rawdb.ReadBorTransactionForBlock(tx, block.NumberU64()) - txns := block.Transactions() - if borTx != nil && *config.BorTraceEnabled { - txns = append(txns, borTx) + blockTracer := &blockTracer{ + ctx: ctx, + stream: stream, + engine: api.engine(), + tx: tx, + config: config, + chainConfig: chainConfig, + _blockReader: api._blockReader, + historyV3: api.historyV3(tx), + evmCallTimeout: api.evmCallTimeout, } - cumulativeGas := uint64(0) - hermezReader := hermez_db.NewHermezDbReader(tx) - - for idx, txn := range txns { - stream.WriteObjectStart() - stream.WriteObjectField("txHash") - stream.WriteString(txn.Hash().Hex()) - stream.WriteMore() - stream.WriteObjectField("result") - select { - default: - case <-ctx.Done(): - stream.WriteNil() - return ctx.Err() - } - - txHash := txn.Hash() - evm, effectiveGasPricePercentage, err := core.PrepareForTxExecution(chainConfig, &vm.Config{}, &blockCtx, hermezReader, ibs, block, &txHash, idx) - if err != nil { - stream.WriteNil() - return err - } - - msg, _, err := core.GetTxContext(chainConfig, engine, ibs, block.Header(), txn, evm, effectiveGasPricePercentage) - if err != nil { - stream.WriteNil() - return err - } - - txCtx := evmtypes.TxContext{ - TxHash: txn.Hash(), - Origin: msg.From(), - GasPrice: msg.GasPrice(), - Txn: txn, - CumulativeGasUsed: &cumulativeGas, - BlockNum: block.NumberU64(), - } - - err = transactions.TraceTx(ctx, msg, blockCtx, txCtx, ibs, config, chainConfig, stream, api.evmCallTimeout) - if err == nil { - err = ibs.FinalizeTx(rules, state.NewNoopWriter()) - } - stream.WriteObjectEnd() - - // if we have an error we want to output valid json for it before continuing after clearing down potential writes to the stream - if err != nil { - stream.WriteMore() - stream.WriteObjectStart() - rpc.HandleError(err, stream) - stream.WriteObjectEnd() - if err != nil { - return err - } - } - if idx != len(txns)-1 { - stream.WriteMore() - } - stream.Flush() - } - stream.WriteArrayEnd() - stream.Flush() - return nil + return blockTracer.TraceBlock(block) } func (api *PrivateDebugAPIImpl) TraceCallMany(ctx context.Context, bundles []Bundle, simulateContext StateContext, config *tracers.TraceConfig_ZkEvm, stream *jsoniter.Stream) error { @@ -469,3 +403,80 @@ func (api *PrivateDebugAPIImpl) TraceTransactionCounters(ctx context.Context, ha // Trace the transaction and return return transactions.TraceTx(ctx, txEnv.Msg, txEnv.BlockContext, txEnv.TxContext, txEnv.Ibs, config, chainConfig, stream, api.evmCallTimeout) } + +func (api *PrivateDebugAPIImpl) TraceBatchByNumber(ctx context.Context, batchNum rpc.BlockNumber, config *tracers.TraceConfig_ZkEvm, stream *jsoniter.Stream) error { + tx, err := api.db.BeginRo(ctx) + if err != nil { + stream.WriteNil() + return err + } + defer tx.Rollback() + + reader := hermez_db.NewHermezDbReader(tx) + badBatch, err := reader.GetInvalidBatch(batchNum.Uint64()) + if err != nil { + stream.WriteNil() + return err + } + + if badBatch { + stream.WriteNil() + return errors.New("batch is invalid") + } + + blockNumbers, err := reader.GetL2BlockNosByBatch(batchNum.Uint64()) + if err != nil { + stream.WriteNil() + return fmt.Errorf("failed to get block numbers for batch %d: %w", batchNum, err) + } + if len(blockNumbers) == 0 { + return fmt.Errorf("no blocks found for batch %d", batchNum) + } + + // if we've pruned this history away for this block then just return early + // to save any red herring errors + if err = api.BaseAPI.checkPruneHistory(tx, blockNumbers[0]); err != nil { + stream.WriteNil() + return err + } + + if config == nil { + config = &tracers.TraceConfig_ZkEvm{} + } + + if config.BorTraceEnabled == nil { + config.BorTraceEnabled = newBoolPtr(false) + } + + chainConfig, err := api.chainConfig(ctx, tx) + if err != nil { + stream.WriteNil() + return err + } + blockTracer := &blockTracer{ + ctx: ctx, + stream: stream, + engine: api.engine(), + tx: tx, + config: config, + chainConfig: chainConfig, + _blockReader: api._blockReader, + historyV3: api.historyV3(tx), + evmCallTimeout: api.evmCallTimeout, + } + + for _, blockNum := range blockNumbers { + block, err := api.blockByNumberWithSenders(ctx, tx, blockNum) + if err != nil { + stream.WriteNil() + return nil + } + + if err := blockTracer.TraceBlock(block); err != nil { + stream.WriteNil() + return err + } + } + + return nil +}