You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
chain_index::finalized_height(chain_height: u32) -> types::Height computes chain_height.saturating_sub(NON_FINALIZED_DEPTH). The name suggests
"zaino's authoritative finalized tip," but the value is a chain-tip-
derived lower bound, not the actual finalized-DB tip.
The two agree in steady state — each iter advances fs.db_height to track
this value. They diverge after a reorg that shortens the best-known-
chain tip below the prior finalization boundary: zaino's finalized DB
is monotonic (once a block is added, it stays), so fs.db_height can
sit abovechain_height - NON_FINALIZED_DEPTH.
We do assume reorgs cannot pass NON_FINALIZED_DEPTH blocks deep
(Zcash protocol bound via coinbase maturity), so every block zaino has
finalized was safe by protocol at the time of finalization. The
divergence is purely about the chain-derived lower bound falling
below the (monotonic) actual tip after the chain shortens — not about
"reorg-unsafe finalization."
Scenario
Best-known-chain tip = 200; zaino has finalized to fs.db_height = 100. finalized_height(200) = 100. Function value == fs tip. Consistent.
Validator reorgs ~50 blocks (within the protocol bound). New
best-known-chain tip = 180.
finalized_height(180) = 80. But fs.db_height is still 100 —
those finalized blocks at heights 1..=100 weren't touched by the
reorg (it only reached heights 151..=200).
Why current callers tolerate it
fs.sync_to_height(finalized_height(chain_height), &source) iterates (db_height + 1)..=target. If target < db_height, the range is empty
→ no-op. Correct outcome by accident; the name suggests the call
conveys "the actual finalized tip is now this value," but really it's
"advance the DB toward this value if we haven't already passed it."
ChainIndexSnapshot::StillSyncingFinalizedState { validator_finalized_height }
is used for read-routing — queries at heights ≤ this value are passed
through to the finalized DB. Under-reporting (returning 80 when fs is
at 100) means heights 81..=100 get routed to the non-existent NFS
instead of the finalized DB. Probably manifests as "no result" rather
than wrong data, but the snapshot is misleading about what zaino has
actually finalized.
Proposed rename
Rename to finalized_height_floor(chain_tip). The _floor suffix cues
the reader that the actual finalized tip is max(fs.db_height, finalized_height_floor(chain_tip)) and that fs.db_height() is the
right call when you want "what zaino has actually finalized."
Whichever name lands, also: the ChainIndexSnapshot::StillSyncingFinalizedState { validator_finalized_height }
field at chain_index.rs:1290 currently stores this lower-bound value
under a name that suggests "what the validator has actually finalized."
That's the read-routing under-reporting site. Worth re-naming and/or
sourcing it from fs.db_height for the read-routing use.
Related
zebra_state::MAX_BLOCK_REORG_HEIGHT = MIN_TRANSPARENT_COINBASE_MATURITY - 1 = 99
— Zebra's protocol-derived constant; zaino currently uses 100 (one
extra block of safety margin), already noted in the doc on NON_FINALIZED_DEPTH.
Subtlety
chain_index::finalized_height(chain_height: u32) -> types::Heightcomputeschain_height.saturating_sub(NON_FINALIZED_DEPTH). The name suggests"zaino's authoritative finalized tip," but the value is a chain-tip-
derived lower bound, not the actual finalized-DB tip.
The two agree in steady state — each iter advances
fs.db_heightto trackthis value. They diverge after a reorg that shortens the best-known-
chain tip below the prior finalization boundary: zaino's finalized DB
is monotonic (once a block is added, it stays), so
fs.db_heightcansit above
chain_height - NON_FINALIZED_DEPTH.We do assume reorgs cannot pass
NON_FINALIZED_DEPTHblocks deep(Zcash protocol bound via coinbase maturity), so every block zaino has
finalized was safe by protocol at the time of finalization. The
divergence is purely about the chain-derived lower bound falling
below the (monotonic) actual tip after the chain shortens — not about
"reorg-unsafe finalization."
Scenario
fs.db_height = 100.finalized_height(200) = 100. Function value == fs tip. Consistent.best-known-chain tip = 180.
finalized_height(180) = 80. Butfs.db_heightis still 100 —those finalized blocks at heights 1..=100 weren't touched by the
reorg (it only reached heights 151..=200).
Why current callers tolerate it
fs.sync_to_height(finalized_height(chain_height), &source)iterates(db_height + 1)..=target. Iftarget < db_height, the range is empty→ no-op. Correct outcome by accident; the name suggests the call
conveys "the actual finalized tip is now this value," but really it's
"advance the DB toward this value if we haven't already passed it."
ChainIndexSnapshot::StillSyncingFinalizedState { validator_finalized_height }is used for read-routing — queries at heights ≤ this value are passed
through to the finalized DB. Under-reporting (returning 80 when fs is
at 100) means heights 81..=100 get routed to the non-existent NFS
instead of the finalized DB. Probably manifests as "no result" rather
than wrong data, but the snapshot is misleading about what zaino has
actually finalized.
Proposed rename
Rename to
finalized_height_floor(chain_tip). The_floorsuffix cuesthe reader that the actual finalized tip is
max(fs.db_height, finalized_height_floor(chain_tip))and thatfs.db_height()is theright call when you want "what zaino has actually finalized."
Whichever name lands, also: the
ChainIndexSnapshot::StillSyncingFinalizedState { validator_finalized_height }field at
chain_index.rs:1290currently stores this lower-bound valueunder a name that suggests "what the validator has actually finalized."
That's the read-routing under-reporting site. Worth re-naming and/or
sourcing it from
fs.db_heightfor the read-routing use.Related
zebra_state::MAX_BLOCK_REORG_HEIGHT = MIN_TRANSPARENT_COINBASE_MATURITY - 1 = 99— Zebra's protocol-derived constant; zaino currently uses
100(oneextra block of safety margin), already noted in the doc on
NON_FINALIZED_DEPTH.chain_heightsnapshot — the function is called once per iter and thevalue is treated as immutable; this PR didn't change the semantic,
just named it).