From 5fe8ff3686321d96eb5da9ce349fed46a4f46ccf Mon Sep 17 00:00:00 2001 From: MoonBoi9001 Date: Thu, 6 Aug 2026 21:35:38 +0300 Subject: [PATCH 1/3] feat(unwind): log reorg paths that bypass the on-disk unwind Four branches can switch the canonical chain without running the on-disk unwind, and none of them said so in the log, so a reorg that leaves stale index data behind is invisible in a node's journal. Each branch now logs when it skips, making the silent cases visible and countable. --- execution/execmodule/forkchoice.go | 8 ++++++++ execution/stagedsync/stage_execute.go | 1 + execution/stagedsync/sync.go | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/execution/execmodule/forkchoice.go b/execution/execmodule/forkchoice.go index 93b61132df9..96b4826e99b 100644 --- a/execution/execmodule/forkchoice.go +++ b/execution/execmodule/forkchoice.go @@ -290,6 +290,14 @@ func (e *ExecModule) unwindIfNeeded( err = fmt.Errorf("updateForkChoice: %w", err) return nil, err } + } else { + execProgress, err := stages.GetStageProgress(tx, stages.Execution) + if err != nil { + return nil, err + } + if execProgress > unwindTarget { + e.logger.Info("updateForkChoice: unwind skipped with executed state above reorg point", "unwindTarget", unwindTarget, "lastCanonicalBlock", lastCanonicalBlock, "execProgress", execProgress) + } } // SD.Unwind (inside RunUnwind) tx-aware-invalidates the BranchCache by // the unwound txNum, so no whole-cache clear is needed here. diff --git a/execution/stagedsync/stage_execute.go b/execution/stagedsync/stage_execute.go index 4fa4990f57f..0a7bdc94e8d 100644 --- a/execution/stagedsync/stage_execute.go +++ b/execution/stagedsync/stage_execute.go @@ -407,6 +407,7 @@ func UnwindExecutionStage(u *UnwindState, s *StageState, doms *execctx.SharedDom // Do not `ResetPendingUpdates()` here. Unlike the disk-unwind path below (which discards then // rebuilds commitment state via unwindExec3 + SeekCommitment), this early return only rewinds the in-RAM overlay + logger.Info(fmt.Sprintf("[%s] Unwind Execution: RAM-only, disk state untouched", u.LogPrefix()), "unwindPoint", u.UnwindPoint, "progress", s.BlockNumber) _, err = unwindDomsToBlock(ctx, rwTx, cfg.blockReader, doms, s.BlockNumber, nil) return err } diff --git a/execution/stagedsync/sync.go b/execution/stagedsync/sync.go index 640d62d1174..7af26e13f08 100644 --- a/execution/stagedsync/sync.go +++ b/execution/stagedsync/sync.go @@ -140,6 +140,7 @@ func (s *Sync) UnwindTo(unwindPoint uint64, reason UnwindReason, tx kv.Tx) error // Ignore in the case that snapshots are ahead of commitment, it will be resolved later. // This can be a problem if snapshots include a wrong chain so it is ok to ignore it. if errors.Is(err, commitmentdb.ErrBehindCommitment) { + s.logger.Info("UnwindTo: unwind request dropped, target behind commitment", "requested", unwindPoint, "err", reason.Err()) return nil } if err != nil { @@ -538,6 +539,11 @@ func (s *Sync) unwindStage(initialCycle bool, stage *Stage, sd *execctx.SharedDo unwind.Reason = s.unwindReason if stageState.BlockNumber <= unwind.UnwindPoint { + if stageState.BlockNumber == unwind.UnwindPoint { + s.logger.Info("unwind skipped, stage exactly at unwind point", "stage", stage.ID, "unwindPoint", unwind.UnwindPoint) + } else { + s.logger.Debug("unwind skipped, stage below unwind point", "stage", stage.ID, "progress", stageState.BlockNumber, "unwindPoint", unwind.UnwindPoint) + } return nil } From 5cb7be90dc3829dd912c7bb110acbdd99cd3352d Mon Sep 17 00:00:00 2001 From: MoonBoi9001 Date: Fri, 7 Aug 2026 15:32:40 +0300 Subject: [PATCH 2/3] fix(unwind): log the real error when dropping an unwind request The dropped-request log recorded the unwind reason's error, which is often nil (fork-choice unwinds carry none), hiding why the request was dropped. Record the commitment-check error that actually caused the drop instead. --- execution/stagedsync/sync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/stagedsync/sync.go b/execution/stagedsync/sync.go index 7af26e13f08..91a92181028 100644 --- a/execution/stagedsync/sync.go +++ b/execution/stagedsync/sync.go @@ -140,7 +140,7 @@ func (s *Sync) UnwindTo(unwindPoint uint64, reason UnwindReason, tx kv.Tx) error // Ignore in the case that snapshots are ahead of commitment, it will be resolved later. // This can be a problem if snapshots include a wrong chain so it is ok to ignore it. if errors.Is(err, commitmentdb.ErrBehindCommitment) { - s.logger.Info("UnwindTo: unwind request dropped, target behind commitment", "requested", unwindPoint, "err", reason.Err()) + s.logger.Info("UnwindTo: unwind request dropped, target behind commitment", "requested", unwindPoint, "err", err) return nil } if err != nil { From 8f97caf8125f335754dde85dbdc737f1ec18e4a6 Mon Sep 17 00:00:00 2001 From: MoonBoi9001 Date: Fri, 7 Aug 2026 15:32:53 +0300 Subject: [PATCH 3/3] fix(forkchoice): keep updateForkChoice context on progress error The other error returns on the unwind path wrap their errors with the function name; the new stage-progress read returned its error bare, making failures harder to attribute. --- execution/execmodule/forkchoice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/execmodule/forkchoice.go b/execution/execmodule/forkchoice.go index 96b4826e99b..b0151c244ab 100644 --- a/execution/execmodule/forkchoice.go +++ b/execution/execmodule/forkchoice.go @@ -293,7 +293,7 @@ func (e *ExecModule) unwindIfNeeded( } else { execProgress, err := stages.GetStageProgress(tx, stages.Execution) if err != nil { - return nil, err + return nil, fmt.Errorf("updateForkChoice: %w", err) } if execProgress > unwindTarget { e.logger.Info("updateForkChoice: unwind skipped with executed state above reorg point", "unwindTarget", unwindTarget, "lastCanonicalBlock", lastCanonicalBlock, "execProgress", execProgress)