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
20 changes: 17 additions & 3 deletions execution/stagedsync/exec3_parallel.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,9 +595,11 @@ func (pe *parallelExecutor) execImpl(ctx context.Context, execStage *StageState,
}
// Fallback for exit paths that publish no cause: a single-block
// fork-validation batch exits via execLoopExitCheck (no cause), and
// real shutdown cancels with context.Canceled. A fully-applied
// requested range is a clean end; otherwise there is more work.
if lastBlockResult.BlockNum >= pe.maxBlockNum {
// real shutdown cancels with context.Canceled. A fully-applied range
// — or an empty loop that executed nothing because the range was
// already applied (async background commit advanced progress) — is a
// clean end; otherwise there is more work.
if applyLoopCloseIsClean(lastBlockResult.BlockNum, pe.maxBlockNum, len(txResultBlocks)) {
return nil
}
return &ErrLoopExhausted{From: startBlockNum, To: lastBlockResult.BlockNum, Reason: "block batch is full"}
Expand Down Expand Up @@ -1478,6 +1480,18 @@ func execLoopShouldExit(blockResult *blockResult, sizeEst, batchLimit, maxBlockN
return execLoopContinue
}

// applyLoopCloseIsClean reports whether an apply-loop close with no published
// stop cause is a clean end rather than a partial batch to resume. It is clean
// when the requested range was fully applied (lastBlockNum >= maxBlockNum) or
// when the loop executed nothing at all (no tx-results and no blockResult) —
// the range was already applied before this call, so there is no pending work.
func applyLoopCloseIsClean(lastBlockNum, maxBlockNum uint64, txResultCount int) bool {
if lastBlockNum >= maxBlockNum {
return true
}
return txResultCount == 0 && lastBlockNum == 0
}

// closeApplyChannels closes the apply-loop-bound channels in the order
// the calculator and apply loop require: commitResults FIRST so the
// calculator drains and closes rootResults, then applyResults so the
Expand Down
32 changes: 32 additions & 0 deletions execution/stagedsync/exec3_parallel_robustness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,38 @@ func TestExecLoopShouldExitPriority(t *testing.T) {
}
}

// TestApplyLoopCloseIsClean pins the no-stop-cause apply-loop close
// classification. The load-bearing case is the empty loop
// (txResultCount==0, lastBlockNum==0): under background commit the async
// commit can advance execution progress to the validation target before a
// single-block fork-validation step runs, so the exec loop executes nothing
// and produces no blockResult. Treating that as pending work returns a
// spurious ErrLoopExhausted, which the stage loop reports as
// "unexpected state step has more work".
func TestApplyLoopCloseIsClean(t *testing.T) {
cases := []struct {
name string
lastBlockNum uint64
maxBlockNum uint64
txResults int
want bool
}{
{name: "fully applied", lastBlockNum: 5, maxBlockNum: 5, txResults: 3, want: true},
{name: "past target", lastBlockNum: 6, maxBlockNum: 5, txResults: 3, want: true},
{name: "partial batch is not clean", lastBlockNum: 3, maxBlockNum: 5, txResults: 2, want: false},
{name: "empty loop, nothing executed", lastBlockNum: 0, maxBlockNum: 21, txResults: 0, want: true},
{name: "tx-results without blockResult is not clean", lastBlockNum: 0, maxBlockNum: 21, txResults: 4, want: false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := applyLoopCloseIsClean(tc.lastBlockNum, tc.maxBlockNum, tc.txResults)
if got != tc.want {
t.Fatalf("applyLoopCloseIsClean(%d,%d,%d) = %v, want %v", tc.lastBlockNum, tc.maxBlockNum, tc.txResults, got, tc.want)
}
})
}
}

// TestShouldMarkExhaustedAtBlock exercises the production
// shouldMarkExhaustedAtBlock helper directly. The helper is the gate
// that decides whether executeBlocks stamps a dispatched block with
Expand Down
2 changes: 1 addition & 1 deletion execution/stagedsync/exec3_serial.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func (se *serialExecutor) exec(ctx context.Context, execStage *StageState, u Unw
return nil, rwTx, err
}

if shouldGenerateChangesets {
if shouldGenerateChangesets && blockNum > 0 {
se.doms.SavePastChangesetAccumulator(b.Hash(), blockNum, changeSet)
}
se.doms.SetChangesetAccumulator(nil)
Expand Down
3 changes: 3 additions & 0 deletions rpc/jsonrpc/eth_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ func (api *APIImpl) getProof(ctx context.Context, roTx kv.TemporalTx, address co
if err != nil {
return nil, err
}
if header == nil {
return nil, fmt.Errorf("header not found for block %d", blockNrOrHash.BlockNumber.Uint64())
}

domains, err := execctx.NewSharedDomains(ctx, tx, log.New(), execctx.WithoutDeferredBranchUpdates(), execctx.WithSequentialCommitment())
if err != nil {
Expand Down
Loading