Skip to content

Commit

Permalink
feat: add debug_traceBatchByNumber (#1537)
Browse files Browse the repository at this point in the history
* feat: add debug_traceBatchByNumber

* chore: generate endpoints doc
  • Loading branch information
V-Staykov authored Dec 4, 2024
1 parent 6635b2f commit f4f6201
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 78 deletions.
1 change: 1 addition & 0 deletions cmd/rpcdaemon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | |
Expand Down
1 change: 1 addition & 0 deletions docs/endpoints/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ If the endpoint is not in the list below, it means this specific endpoint is not
- debug_getRawBlock
- debug_getRawHeader
- debug_storageRangeAt
- debug_traceBatchByNumber
- debug_traceBlockByHash
- debug_traceBlockByNumber
- debug_traceCall
Expand Down
1 change: 1 addition & 0 deletions turbo/jsonrpc/debug_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
184 changes: 184 additions & 0 deletions turbo/jsonrpc/tracing_block_zkevm.go
Original file line number Diff line number Diff line change
@@ -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
}
167 changes: 89 additions & 78 deletions turbo/jsonrpc/tracing_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package jsonrpc

import (
"context"
"errors"
"fmt"
"math/big"
"time"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

0 comments on commit f4f6201

Please sign in to comment.