Skip to content

fix(syncer): refetch latest da height instead of da height +1#3201

Draft
julienrbrt wants to merge 7 commits intomainfrom
julien/cache
Draft

fix(syncer): refetch latest da height instead of da height +1#3201
julienrbrt wants to merge 7 commits intomainfrom
julien/cache

Conversation

@julienrbrt
Copy link
Member

@julienrbrt julienrbrt commented Mar 24, 2026

Overview

If the latest da height contained more blocks, and one of those blocks got executed, without p2p the node will be stuck as it won't refetch the da heigth. We should re fetch daHeight-1 on start.

In our case, the cache was as well further, which is fine, but because we don't hold blocks in cache, we don't need this at all.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 24, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32c071a5-a754-4930-ac0f-95a69e8e1253

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch julien/cache

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Contributor

claude bot commented Mar 24, 2026

Claude finished @julienrbrt's task in 1m 54s —— View job


Code Review

  • Gather context and read changed files
  • Review syncer.go changes
  • Review CHANGELOG.md
  • Post final review

Summary

The fix correctly identifies that on restart without P2P, the syncer may get stuck because it advanced past a DA height that still had unprocessed blocks. Re-fetching from state.DAHeight - 1 instead of state.DAHeight is the right intent. The CHANGELOG entry is accurate.

However, there is a critical bug in the implementation that silently breaks the main case.


Critical Bug: min(state.DAHeight-1, 0) with uint64 always returns 0

File: block/internal/syncing/syncer.go, line 359

daHeight := max(s.genesis.DAStartHeight, min(state.DAHeight-1, 0))

state.DAHeight is uint64 (see types/state.go:36). For unsigned integers, min(x, 0) == 0 for any non-negative value of x. So this expression always evaluates to:

daHeight := max(s.genesis.DAStartHeight, 0) // == s.genesis.DAStartHeight

Concretely:

state.DAHeight state.DAHeight-1 (uint64) min(..., 0) Final daHeight
0 maxUint64 (underflow) 0 DAStartHeight
1 0 0 DAStartHeight
5 4 0 ← wrong DAStartHeight ← wrong
100 99 0 ← wrong DAStartHeight ← wrong

The syncer will always restart from genesis DA height rather than state.DAHeight - 1. This means re-scanning all DA blocks from the beginning on every restart once the chain has progressed — it defeats the purpose of the fix entirely and could introduce significant startup latency on mature chains.

The underflow guard was likely intended to protect against state.DAHeight == 0, but min is the wrong function here. The correct way to compute state.DAHeight - 1 with underflow protection for uint64 is:

daHeight := max(s.genesis.DAStartHeight, max(state.DAHeight, 1)-1)

This correctly gives:

  • state.DAHeight = 0max(0, 1)-1 = 0DAStartHeight
  • state.DAHeight = 1max(1, 1)-1 = 0DAStartHeight
  • state.DAHeight = 5max(5, 1)-1 = 4max(DAStartHeight, 4)
  • state.DAHeight = 10099max(DAStartHeight, 99)

Fix this →


Minor Observation: P2P condition logic

The condition s.headerStore != nil && s.headerStore.Height() > state.LastBlockHeight is a reasonable heuristic for detecting "P2P is ahead of execution", but it's worth noting that headerStore.Height() may return a stale value if the node is restarting after a crash mid-sync. The comment accurately documents the intent, so this is acceptable as-is, but worth keeping in mind for future edge cases.


CHANGELOG

The entry is well-formed and accurate. ✓

@github-actions
Copy link
Contributor

github-actions bot commented Mar 24, 2026

The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedMar 25, 2026, 11:10 AM

@julienrbrt julienrbrt marked this pull request as draft March 24, 2026 18:46
if s.headerStore != nil && s.headerStore.Height() > state.LastBlockHeight {
daHeight = max(daHeight, s.cache.DaHeight())
}
s.daRetrieverHeight.Store(daHeight)
Copy link
Contributor

Choose a reason for hiding this comment

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

makes sense

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants