stagedsync, execmodule: halt on invalid block during parallel initial sync - #19870
Merged
Conversation
… sync The parallel executor was missing error handling for ErrInvalidBlock, causing an infinite retry loop that pushed Caplin's backward target further back. This adds: - ReportBadHeaderPoS, badBlockHalt, and UnwindTo handling in parallel executor (parity with serial at exec3_serial.go:460-475) - Merge pe.wait() errors into execErr so both error paths converge - Early return on ErrInvalidBlock before step-frozen check to prevent error masking - Process halt in exec module and stage loop on ErrInvalidBlock All halt behavior is gated on dbg.Exec3Parallel to avoid changing serial flow behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6 tasks
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the parallel execution (ExecV3 / exec3_parallel) initial-sync error path so that rules.ErrInvalidBlock is handled deterministically (halt/unwind/report) instead of being retried indefinitely, and ensures invalid-block errors aren’t masked by later commitment/“step frozen” checks.
Changes:
- Stop the staged sync loop on
ErrInvalidBlockduring parallel initial sync to avoid infinite retry. - Add missing
ErrInvalidBlockhandling in the parallel executor (PoS bad-header report, optional halt, unwind). - Preserve the original
ErrInvalidBlockby returning it before the step-frozen commitment check.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
execution/stagedsync/stageloop/stageloop.go |
Stops the stage loop on ErrInvalidBlock during parallel initial cycle. |
execution/stagedsync/exec3_parallel.go |
Merges wait() errors into execErr and adds invalid-block-specific handling (report/halt/unwind). |
execution/stagedsync/exec3.go |
Early-return on ErrInvalidBlock to prevent masking by commitment “step frozen” logic. |
execution/execmodule/exec_module.go |
Halts the process on ErrInvalidBlock during startup frozen-block processing in parallel mode. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Comment on lines
+498
to
+499
| if err := u.UnwindTo(lastHeader.Number.Uint64()-1, BadBlock(lastHeader.Hash(), execErr), rwTx); err != nil { | ||
| return nil, rwTx, err |
Comment on lines
+503
to
+506
| // Exit the process so the operator can investigate. | ||
| if dbg.Exec3Parallel && errors.Is(err, rules.ErrInvalidBlock) { | ||
| e.logger.Error("Invalid block during parallel initial sync — halting process") | ||
| os.Exit(1) |
|
|
||
| if execErr != nil { | ||
| if !(errors.Is(execErr, context.Canceled) || errors.Is(execErr, &ErrLoopExhausted{})) { | ||
| pe.logger.Warn(fmt.Sprintf("[%s] Execution failed", pe.logPrefix), "err", execErr) |
Comment on lines
+478
to
+480
| if execErr == nil { | ||
| execErr = waitErr | ||
| } |
- Replace os.Exit(1) with stopNode callback for clean shutdown - Add underflow guard on unwind-to block number - Add block number/hash context to execution failure log - Combine execErr and waitErr with errors.Join instead of dropping Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
yperbasis
approved these changes
Mar 14, 2026
lupin012
pushed a commit
that referenced
this pull request
Mar 17, 2026
… sync (#19870) ## Summary - Add missing `ErrInvalidBlock` handling in parallel executor (parity with serial path at `exec3_serial.go:460-475`): `ReportBadHeaderPoS`, `badBlockHalt` check, and `UnwindTo` - Merge `pe.wait()` errors into `execErr` so executor goroutine errors and rw goroutine errors converge into a single handling path - Early return on `ErrInvalidBlock` before the step-frozen commitment check, preventing the original error from being masked - Halt process in exec module (`ProcessFrozenBlocks`) and stop stage loop on `ErrInvalidBlock` during initial sync **Important:** All halt behavior is gated on `dbg.Exec3Parallel` (`EXEC3_PARALLEL` env var) to avoid changing serial flow behavior. The serial path is unaffected. ## Context When the parallel executor hits an invalid block during initial sync (e.g. receiptHash mismatch), it previously entered an infinite retry loop — the stage loop caught the error, slept 500ms, and retried the same block. Meanwhile Caplin continued downloading, pushing its backward target further back. The process needed to be killed manually. ## Test plan - [x] `make lint` passes - [x] `make erigon integration` builds clean - [ ] Verify on mainnet parallel node: set `BAD_BLOCK_HALT=true EXEC3_PARALLEL=true`, inject bad block → process exits cleanly with error log - [ ] Verify serial path unchanged: same scenario without `EXEC3_PARALLEL` → existing retry behavior preserved 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Mark Holt <erigon@dev-bm-e3-ethmainnet-n4.erigon.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ErrInvalidBlockhandling in parallel executor (parity with serial path atexec3_serial.go:460-475):ReportBadHeaderPoS,badBlockHaltcheck, andUnwindTope.wait()errors intoexecErrso executor goroutine errors and rw goroutine errors converge into a single handling pathErrInvalidBlockbefore the step-frozen commitment check, preventing the original error from being maskedProcessFrozenBlocks) and stop stage loop onErrInvalidBlockduring initial syncImportant: All halt behavior is gated on
dbg.Exec3Parallel(EXEC3_PARALLELenv var) to avoid changing serial flow behavior. The serial path is unaffected.Context
When the parallel executor hits an invalid block during initial sync (e.g. receiptHash mismatch), it previously entered an infinite retry loop — the stage loop caught the error, slept 500ms, and retried the same block. Meanwhile Caplin continued downloading, pushing its backward target further back. The process needed to be killed manually.
Test plan
make lintpassesmake erigon integrationbuilds cleanBAD_BLOCK_HALT=true EXEC3_PARALLEL=true, inject bad block → process exits cleanly with error logEXEC3_PARALLEL→ existing retry behavior preserved🤖 Generated with Claude Code