Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions evmrpc/historical_debug_trace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package evmrpc

import (
"context"
"testing"

sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
"github.com/stretchr/testify/require"
)

func TestIsHistoricalDebugTraceBlock(t *testing.T) {
tests := []struct {
name string
blockHeight int64
latestHeight int64
maxBlockLookback int64
want bool
}{
{
name: "older than configured lookback",
blockHeight: 8,
latestHeight: 10,
maxBlockLookback: 1,
want: true,
},
{
name: "equal to configured lookback",
blockHeight: 9,
latestHeight: 10,
maxBlockLookback: 1,
want: false,
},
{
name: "zero lookback treats previous block as historical",
blockHeight: 9,
latestHeight: 10,
maxBlockLookback: 0,
want: true,
},
{
name: "zero lookback allows latest block",
blockHeight: 10,
latestHeight: 10,
maxBlockLookback: 0,
want: false,
},
{
name: "negative lookback disables classification",
blockHeight: 1,
latestHeight: 10,
maxBlockLookback: -1,
want: false,
},
{
name: "future block",
blockHeight: 11,
latestHeight: 10,
maxBlockLookback: 0,
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, isHistoricalDebugTraceBlock(tt.blockHeight, tt.latestHeight, tt.maxBlockLookback))
})
}
}

func TestGuardHistoricalDebugTraceHeight(t *testing.T) {
latestCtx := sdk.Context{}.WithBlockHeight(10)
api := &DebugAPI{
ctxProvider: func(int64) sdk.Context { return latestCtx },
connectionType: ConnectionTypeHTTP,
maxBlockLookback: -1,
}

err := api.guardHistoricalDebugTraceHeight(context.Background(), "debug_traceBlockByNumber", 8)
require.NoError(t, err)

api.maxBlockLookback = 1
err = api.guardHistoricalDebugTraceHeight(context.Background(), "debug_traceBlockByNumber", 8)
require.Error(t, err)
require.Contains(t, err.Error(), "block number 8 is beyond max lookback of 1")

err = api.guardHistoricalDebugTraceHeight(context.Background(), "debug_traceBlockByNumber", 9)
require.NoError(t, err)

api.maxBlockLookback = 0
err = api.guardHistoricalDebugTraceHeight(context.Background(), "debug_traceBlockByNumber", 9)
require.Error(t, err)
require.Contains(t, err.Error(), "block number 9 is beyond max lookback of 0")
}
21 changes: 18 additions & 3 deletions evmrpc/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ var (
rpcTelemetryMeter = otel.Meter("evmrpc")

metrics = struct {
requestLatencySeconds metric.Float64Histogram
wsConnectionCount metric.Int64Counter
redirectedRequestCount metric.Int64Counter
requestLatencySeconds metric.Float64Histogram
wsConnectionCount metric.Int64Counter
redirectedRequestCount metric.Int64Counter
historicalDebugTraceAttemptCount metric.Int64Counter
}{
requestLatencySeconds: must(rpcTelemetryMeter.Float64Histogram(
"evmrpc_request_latency_seconds",
Expand All @@ -63,6 +64,11 @@ var (
metric.WithDescription("Number of EVM RPC requests forwarded to another validator"),
metric.WithUnit("{count}"),
)),
historicalDebugTraceAttemptCount: must(rpcTelemetryMeter.Int64Counter(
"evmrpc_historical_debug_trace_attempts_total",
metric.WithDescription("Number of debug_trace* requests targeting historical blocks"),
metric.WithUnit("{count}"),
)),
}
)

Expand Down Expand Up @@ -140,3 +146,12 @@ func recordRedirectedRequest(ctx context.Context, endpoint, connection string) {
),
)
}

func recordHistoricalDebugTraceAttempt(ctx context.Context, endpoint, connection string) {
metrics.historicalDebugTraceAttemptCount.Add(ctx, 1,
metric.WithAttributes(
attribute.String(endpointKey, endpoint),
attribute.String(connectionKey, connection),
),
)
}
4 changes: 4 additions & 0 deletions evmrpc/trace_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ func (api *DebugAPI) TraceTransactionProfile(ctx context.Context, hash common.Ha
recordMetricsWithError(ctx, "debug_traceTransactionProfile", api.connectionType, startTime, returnErr, recover())
}()

if returnErr = api.guardHistoricalDebugTraceByTxHash(ctx, "debug_traceTransactionProfile", hash); returnErr != nil {
return nil, returnErr
}

ctx, done, err := api.prepareTraceContext(ctx)
if err != nil {
return nil, err
Expand Down
98 changes: 93 additions & 5 deletions evmrpc/tracers.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,80 @@ func (api *DebugAPI) prepareTraceContext(ctx context.Context) (context.Context,
}, nil
}

func (api *DebugAPI) guardHistoricalDebugTraceByTxHash(ctx context.Context, endpoint string, hash common.Hash) error {
if api.keeper == nil {
return nil
}
receipt, err := api.keeper.GetReceipt(api.ctxProvider(LatestCtxHeight), hash)
if err != nil || receipt == nil {
return nil
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Did we mean to return err if error is non-nil?
  • if not, log the error in DEBUG level or something similar?

}
return api.guardHistoricalDebugTraceHeight(ctx, endpoint, int64(receipt.BlockNumber)) //nolint:gosec
}

func (api *DebugAPI) guardHistoricalDebugTraceByNumber(ctx context.Context, endpoint string, number rpc.BlockNumber) error {
height, err := api.resolveDebugTraceBlockNumber(ctx, number)
if err != nil {
return err
}
return api.guardHistoricalDebugTraceHeight(ctx, endpoint, height)
}

func (api *DebugAPI) guardHistoricalDebugTraceByHash(ctx context.Context, endpoint string, hash common.Hash) error {
if api.backend == nil {
return nil
}
block, _, err := api.backend.BlockByHash(ctx, hash)
if err != nil || block == nil {
return nil
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto re returning error

}
return api.guardHistoricalDebugTraceHeight(ctx, endpoint, int64(block.NumberU64())) //nolint:gosec
}

func (api *DebugAPI) guardHistoricalDebugTraceByNumberOrHash(ctx context.Context, endpoint string, blockNrOrHash rpc.BlockNumberOrHash) error {
if number, ok := blockNrOrHash.Number(); ok {
return api.guardHistoricalDebugTraceByNumber(ctx, endpoint, number)
}
if hash, ok := blockNrOrHash.Hash(); ok {
return api.guardHistoricalDebugTraceByHash(ctx, endpoint, hash)
}
return api.guardHistoricalDebugTraceHeight(ctx, endpoint, api.ctxProvider(LatestCtxHeight).BlockHeight())
}

func (api *DebugAPI) resolveDebugTraceBlockNumber(ctx context.Context, number rpc.BlockNumber) (int64, error) {
switch number {
case rpc.SafeBlockNumber, rpc.FinalizedBlockNumber, rpc.LatestBlockNumber, rpc.PendingBlockNumber:
return api.ctxProvider(LatestCtxHeight).BlockHeight(), nil
case rpc.EarliestBlockNumber:
if api.tmClient == nil {
return 0, errors.New("tendermint client is not configured")
}
genesisRes, err := api.tmClient.Genesis(ctx)
if err != nil {
return 0, err
}
return genesisRes.Genesis.InitialHeight, nil
default:
return number.Int64(), nil
}
}

func (api *DebugAPI) guardHistoricalDebugTraceHeight(ctx context.Context, endpoint string, blockHeight int64) error {
latest := api.ctxProvider(LatestCtxHeight).BlockHeight()
if !isHistoricalDebugTraceBlock(blockHeight, latest, api.maxBlockLookback) {
return nil
}
recordHistoricalDebugTraceAttempt(ctx, endpoint, string(api.connectionType))
return fmt.Errorf("block number %d is beyond max lookback of %d", blockHeight, api.maxBlockLookback)
}

func isHistoricalDebugTraceBlock(blockHeight, latestHeight, maxBlockLookback int64) bool {
if maxBlockLookback < 0 || blockHeight < 0 || latestHeight < blockHeight {
return false
}
return blockHeight < latestHeight-maxBlockLookback
}
Comment thread
cursor[bot] marked this conversation as resolved.

type SeiDebugAPI struct {
*DebugAPI
}
Expand Down Expand Up @@ -187,6 +261,10 @@ func (api *DebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, con
recordMetricsWithError(ctx, "debug_traceTransaction", api.connectionType, startTime, returnErr, recover())
}()

if returnErr = api.guardHistoricalDebugTraceByTxHash(ctx, "debug_traceTransaction", hash); returnErr != nil {
return nil, returnErr
}

if cached, ok := api.tryTraceCache(hash, config); ok {
return cached, nil
}
Expand Down Expand Up @@ -540,17 +618,16 @@ func (api *DebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNum
recordMetricsWithError(ctx, "debug_traceBlockByNumber", api.connectionType, startTime, returnErr, recover())
}()

if returnErr = api.guardHistoricalDebugTraceByNumber(ctx, "debug_traceBlockByNumber", number); returnErr != nil {
return nil, returnErr
}

ctx, done, err := api.prepareTraceContext(ctx)
if err != nil {
return nil, err
}
defer done()

latest := api.ctxProvider(LatestCtxHeight).BlockHeight()
if api.maxBlockLookback >= 0 && number.Int64() < latest-api.maxBlockLookback {
return nil, fmt.Errorf("block number %d is beyond max lookback of %d", number.Int64(), api.maxBlockLookback)
}

if cached, ok := api.tryBlockTraceCacheByNumber(ctx, number, config); ok {
return cached, nil
}
Expand All @@ -569,6 +646,10 @@ func (api *DebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, con
recordMetricsWithError(ctx, "debug_traceBlockByHash", api.connectionType, startTime, returnErr, recover())
}()

if returnErr = api.guardHistoricalDebugTraceByHash(ctx, "debug_traceBlockByHash", hash); returnErr != nil {
return nil, returnErr
}

ctx, done, err := api.prepareTraceContext(ctx)
if err != nil {
return nil, err
Expand All @@ -593,6 +674,10 @@ func (api *DebugAPI) TraceCall(ctx context.Context, args export.TransactionArgs,
recordMetricsWithError(ctx, "debug_traceCall", api.connectionType, startTime, returnErr, recover())
}()

if returnErr = api.guardHistoricalDebugTraceByNumberOrHash(ctx, "debug_traceCall", blockNrOrHash); returnErr != nil {
return nil, returnErr
}

ctx, done, err := api.prepareTraceContext(ctx)
if err != nil {
return nil, err
Expand Down Expand Up @@ -649,6 +734,9 @@ func (api *DebugAPI) TraceStateAccess(ctx context.Context, hash common.Hash) (re
returnErr = fmt.Errorf("panic occurred: %v, could not trace tx state: %s", r, hash.Hex())
}
}()
if returnErr = api.guardHistoricalDebugTraceByTxHash(ctx, "debug_traceStateAccess", hash); returnErr != nil {
return nil, returnErr
}
tendermintTraces := &TendermintTraces{Traces: []TendermintTrace{}}
ctx = WithTendermintTraces(ctx, tendermintTraces)
receiptTraces := &ReceiptTraces{Traces: []RawResponseReceipt{}}
Expand Down
Loading