Fix FlatDB persistence edge cases from the signed/unsigned type unification#12172
Conversation
|
Claude finished @wurdum's task in 3m 15s —— View job PR Review: FlatDB persistence edge cases from the signed→unsigned unification
Both fixes are well-reasoned, minimal, and correctly target the FindingsMedium — second fix (
Recommendation: relax the assert to a debug log and add a regression test driving Low — What I verified (no issues)
VerdictSolid, scoped fixes with correct reasoning. The one blocking item is the missing regression test for the |
# Conflicts: # src/Nethermind/Nethermind.State.Flat.Test/CompactionScheduleTests.cs
Resolves conflicts from master #12172 (FlatDB persistence edge cases from the signed/unsigned type unification), which touched the same files the long-finality work refactored: - CompactionSchedule / ICompactionSchedule: adopt master's NextFullCompactionAfter(in StateId) signature and PreGenesis->block-0 anchoring; keep the branch's force-persist-backstop rationale comment and the extra persisted-snapshot boundary predicates. - PersistenceManager.DetermineSnapshotAction: port master's two fixes onto the branch's two-phase persist/convert logic — replace the latest-below-persisted Debug.Assert with a warn-and-continue, and use SaturatingSub so a deep reorg can't underflow the in-memory depth. Update the surviving NextFullCompactionAfter callsite to pass the StateId. - PersistenceManagerTests: keep the branch's DetermineSnapshotAction tuple API and test names; port master's two regression tests, adapted to that API — genesis-first persist from a fresh DB, and the no-underflow guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix FlatDB persistence edge cases from the signed→unsigned type unification
Changes
Two follow-up fixes to
Nethermind.State.Flatpersistence, both regressions introduced by thelong → ulong/PreGenesissentinel migration. The root cause in each is thatStateId.PreGenesis.BlockNumberis nowulong.MaxValue, which collides with unsigned arithmetic that used to be safe when the sentinel was-1.Anchor the compaction schedule at genesis when nothing is persisted.
ICompactionSchedule.NextFullCompactionAfternow takesin StateId frominstead ofulong from, andCompactionSchedulemapsPreGenesis → block 0internally (removing theif (from == ulong.MaxValue) return ulong.MaxValue;short-circuit).PersistenceManagerpasses theStateId(in currentPersistedState) at both call sites (DetermineSnapshotToPersist,FlushToPersistence).currentPersistedState == PreGenesis), feeding the sentinel'sulong.MaxValueblock number into the schedule madenextCompactedBoundary == ulong.MaxValue, which is> finalizedBlockNumberfor any real chain. That forced the first persist down the unfinalized/head-ancestor branch, delaying it from depthMinReorgDepth + CompactSizeuntil depth exceededMaxReorgDepth, and emitting a misleading "Very long unfinalized state" warning on a healthy node. Anchoring at genesis lets the branch be chosen by real finalization status. Self-correcting and one-time per database lifetime, but noisy and memory-heavy at cold start.Saturate
inMemoryStateDepthto avoid aulongunderflow.PersistenceManager.DetermineSnapshotToPersistcomputes the non-PreGenesisdepth withlastSnapshotNumber.SaturatingSub(currentPersistedState.BlockNumber)instead of a rawulongsubtraction.nullanyway. Saturating the subtraction floors the depth at0so the existing guard returnsnullearly and silently, matching the old signed behavior.Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Documentation
Requires documentation update
Requires explanation in Release Notes
Remarks
Both fixes are scoped to FlatDB cold-start / persistence-edge behavior; neither changes consensus-visible behavior, on-disk format, or steady-state operation.