Skip to content
Merged
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
19 changes: 19 additions & 0 deletions evmrpc/block_trace_profiled.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ func (api *DebugAPI) profiledTraceBlock(
}
threads := min(runtime.NumCPU(), tracedCount)
threads = min(threads, maxProfiledTraceWorkers)
// Both paths return nil, ctx.Err() on block-level timeout/cancellation.
// Partial results are only returned for per-tx trace failures or state-replay errors.
if threads <= 1 {
return api.profiledTraceBlockSequential(ctx, block, metadata, config, statedb, blockCtx, signer, blockHash, results)
}
Expand Down Expand Up @@ -146,11 +148,17 @@ func (api *DebugAPI) profiledTraceBlockSequential(

if len(metadata) == 0 {
for i, tx := range txs {
if err := ctx.Err(); err != nil {
return nil, err
}
traceOne(i, tx)
}
return results, nil
}
for _, md := range metadata {
if err := ctx.Err(); err != nil {
return nil, err
}
if md.ShouldIncludeInTraceResult {
i := md.IdxInEthBlock
traceOne(i, txs[i])
Expand Down Expand Up @@ -246,6 +254,10 @@ func (api *DebugAPI) profiledTraceBlockParallel(

if len(metadata) == 0 {
for i, tx := range txs {
if err := ctx.Err(); err != nil {
failed = err
break
}
if err := feedTraceTask(i); err != nil {
failed = err
break
Expand All @@ -257,6 +269,10 @@ func (api *DebugAPI) profiledTraceBlockParallel(
}
} else {
for _, md := range metadata {
if err := ctx.Err(); err != nil {
Comment thread
amir-deris marked this conversation as resolved.
failed = err
break
}
if md.ShouldIncludeInTraceResult {
i := md.IdxInEthBlock
if err := feedTraceTask(i); err != nil {
Expand All @@ -277,6 +293,9 @@ func (api *DebugAPI) profiledTraceBlockParallel(
pend.Wait()

if failed != nil {
if errors.Is(failed, context.DeadlineExceeded) || errors.Is(failed, context.Canceled) {
return nil, failed
}
// Fill error entries for txs that were never dispatched to workers,
// matching the sequential path's per-tx error semantics.
if len(metadata) == 0 {
Expand Down
56 changes: 56 additions & 0 deletions evmrpc/block_trace_profiled_export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package evmrpc_test

import (
"context"
"math/big"
"testing"

gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/tracersutils"
"github.com/ethereum/go-ethereum/trie"
"github.com/sei-protocol/sei-chain/evmrpc"
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
"github.com/sei-protocol/sei-chain/x/evm/state"
"github.com/stretchr/testify/require"
)

func TestProfiledTraceBlockParallelMetadataLoopRespectsContext(t *testing.T) {
t.Parallel()

header := &gethtypes.Header{Number: big.NewInt(1)}
block := gethtypes.NewBlock(header, &gethtypes.Body{}, nil, trie.NewStackTrie(nil))
blockHash := block.Hash()
signer := gethtypes.LatestSignerForChainID(big.NewInt(1))

ctx, cancel := context.WithCancel(t.Context())
defer cancel()

var secondRunnableCalls int
metadata := []tracersutils.TraceBlockMetadata{
{
TraceRunnable: func(vm.StateDB) { cancel() },
},
{
TraceRunnable: func(vm.StateDB) { secondRunnableCalls++ },
},
}

stateDB := state.NewDBImpl(Ctx, EVMKeeper, false)
api := evmrpc.NewDebugAPIForTest(evmrpc.NewTraceBackendForTest(EVMKeeper, func(int64) sdk.Context { return Ctx }))
got, err := evmrpc.ProfiledTraceBlockParallelForTest(
api,
ctx,
block,
metadata,
nil,
stateDB,
signer,
blockHash,
nil,
2,
)
require.ErrorIs(t, err, context.Canceled)
require.Nil(t, got)
require.Zero(t, secondRunnableCalls)
}
84 changes: 84 additions & 0 deletions evmrpc/block_trace_profiled_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package evmrpc

import (
"context"
"math/big"
"testing"

gethcommon "github.com/ethereum/go-ethereum/common"
gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/tracersutils"
"github.com/ethereum/go-ethereum/trie"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -66,3 +73,80 @@ func TestShouldUseProfiledBlockTrace(t *testing.T) {
})
}
}

func testProfiledTraceBlock(t *testing.T) (*gethtypes.Block, gethcommon.Hash, gethtypes.Signer) {
t.Helper()

header := &gethtypes.Header{Number: big.NewInt(1)}
block := gethtypes.NewBlock(header, &gethtypes.Body{}, nil, trie.NewStackTrie(nil))
blockHash := block.Hash()
signer := gethtypes.LatestSignerForChainID(big.NewInt(1))
return block, blockHash, signer
}

func TestProfiledTraceBlockSequentialMetadataLoopRespectsContext(t *testing.T) {
t.Parallel()

block, blockHash, signer := testProfiledTraceBlock(t)
ctx, cancel := context.WithCancel(t.Context())
defer cancel()

var secondRunnableCalls int
metadata := []tracersutils.TraceBlockMetadata{
{
// Expire the trace context mid-replay; iteration 2 must not run.
TraceRunnable: func(vm.StateDB) { cancel() },
},
{
TraceRunnable: func(vm.StateDB) { secondRunnableCalls++ },
},
}

api := &DebugAPI{}
got, err := api.profiledTraceBlockSequential(
ctx,
block,
metadata,
nil,
nil,
vm.BlockContext{},
signer,
blockHash,
make([]*tracers.TxTraceResult, 0),
)
require.ErrorIs(t, err, context.Canceled)
require.Nil(t, got)
require.Zero(t, secondRunnableCalls)
}

func TestProfiledTraceBlockSequentialEVMOnlyLoopRespectsContext(t *testing.T) {
t.Parallel()

block, blockHash, signer := testProfiledTraceBlock(t)
body := &gethtypes.Body{
Transactions: gethtypes.Transactions{
gethtypes.NewTx(&gethtypes.LegacyTx{}),
gethtypes.NewTx(&gethtypes.LegacyTx{}),
},
}
block = gethtypes.NewBlock(block.Header(), body, nil, trie.NewStackTrie(nil))

ctx, cancel := context.WithCancel(t.Context())
cancel()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] Cancelling before the call means this only exercises the guard on iteration 0 — it proves the loop never starts, not that it stops. The scenario the PR is fixing is cancellation mid-loop, which the metadata test above covers properly via cancel() inside the first TraceRunnable.

Also worth noting: the pre-fix failure mode here is a nil-interface panic in profiledTraceTx (statedb.GetNonce on the nil passed at line 144) rather than a clean assertion failure. Both points are addressed by giving iteration 0 something real to do and cancelling after it — e.g. a stub statedb — so the test fails on require.ErrorIs rather than on a panic.


api := &DebugAPI{}
results := make([]*tracers.TxTraceResult, len(body.Transactions))
got, err := api.profiledTraceBlockSequential(
ctx,
block,
nil,
nil,
nil,
vm.BlockContext{},
signer,
blockHash,
results,
)
require.ErrorIs(t, err, context.Canceled)
require.Nil(t, got)
}
30 changes: 30 additions & 0 deletions evmrpc/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import (
"context"
"sync"

gethcommon "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/tracersutils"
cosmoclient "github.com/sei-protocol/sei-chain/sei-cosmos/client"
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
"github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt"
Expand Down Expand Up @@ -109,3 +113,29 @@ func (f *LogFetcher) TryFilterLogsRangeForTest(
func MatchesCriteriaForTest(log *ethtypes.Log, crit filters.FilterCriteria) bool {
return MatchesCriteria(log, crit)
}

func ProfiledTraceBlockParallelForTest(
api *DebugAPI,
ctx context.Context,
block *ethtypes.Block,
metadata []tracersutils.TraceBlockMetadata,
config *tracers.TraceConfig,
statedb vm.StateDB,
signer ethtypes.Signer,
blockHash gethcommon.Hash,
results []*tracers.TxTraceResult,
threads int,
) ([]*tracers.TxTraceResult, error) {
return api.profiledTraceBlockParallel(ctx, block, metadata, config, statedb, signer, blockHash, results, threads)
}

func NewTraceBackendForTest(keeper *keeper.Keeper, ctxProvider func(int64) sdk.Context) *Backend {
return &Backend{
keeper: keeper,
ctxProvider: ctxProvider,
}
}

func NewDebugAPIForTest(backend *Backend) *DebugAPI {
return &DebugAPI{backend: backend}
}
Loading