Skip to content

mpt: max version should return INVALID_BLOCK_NUM when rewind below its valid range - #2400

Open
Chen-Yifan wants to merge 1 commit into
mainfrom
vicky/rewind-secondary
Open

mpt: max version should return INVALID_BLOCK_NUM when rewind below its valid range#2400
Chen-Yifan wants to merge 1 commit into
mainfrom
vicky/rewind-secondary

Conversation

@Chen-Yifan

@Chen-Yifan Chen-Yifan commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Problem

Rewinding to a version before a ring's first valid root (e.g. a secondary
timeline activated after the rewind target) left next_version at
target + 1. The ring then reported a max_version() whose slot held no
root: get_latest_version() named a version that could not be loaded, and
readers of the latest root got null.

Change

  • rewind_to_version on the root offsets ring now resets the ring to the
    empty state when the target predates its entire valid range, so
    max_version() reports INVALID_BLOCK_NUM, identical to a freshly
    activated secondary. The next commit reseeds it through the existing
    fast forward path.
  • Rewinding into a gap between valid roots is refused with an assert.
    Together these restore the invariant that a non-empty ring always has a
    valid root at max_version(), and the sec_root != INVALID_OFFSET
    assert in UpdateAux::rewind_to_version stays as a corruption tripwire.
  • Refactor: reset_all(version) is split into reset_all() (empty state)
    and advance_next_version(version) (write cursor positioning used by
    fast_forward_next_version). No behavior change.

Copilot AI review requested due to automatic review settings July 2, 2026 15:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR relaxes UpdateAux::rewind_to_version() by removing an assertion that treated a secondary timeline root offset as always valid. This aligns the function’s behavior with existing handling in post_root_fast_offset_() (which already treats INVALID_OFFSET as “no cutoff candidate” via std::nullopt) and with the cutoff selection logic that skips nullopt candidates.

Changes:

  • Removed MONAD_ASSERT(sec_root != INVALID_OFFSET) when computing the secondary timeline’s contribution to the fast-list cutoff during rewind.
  • Kept the existing behavior where post_root_fast_offset_() returns nullopt for INVALID_OFFSET, and the cutoff calculation ignores nullopt.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No findings. The removed assertion was overly strict — secondary_ro[sec_max] can legitimately be INVALID_OFFSET, post_root_fast_offset_ already short-circuits to nullopt for that input (category/mpt/update_aux.cpp:270), and consider() skips nullopt candidates. The change also brings the secondary path in line with the primary path at category/mpt/update_aux.cpp:344-345, which never asserted this, and with the sibling check_root_past_offsets helper at category/mpt/update_aux.cpp:155-157 which treats INVALID_OFFSET as an expected early-return case.

Verdict: CORRECT

🤖 Generated with Claude Code

maxkozlovsky
maxkozlovsky previously approved these changes Jul 2, 2026
@Chen-Yifan
Chen-Yifan force-pushed the vicky/rewind-secondary branch 2 times, most recently from d9e5f15 to 84abc48 Compare August 6, 2026 15:55
@Chen-Yifan
Chen-Yifan force-pushed the vicky/rewind-secondary branch from 84abc48 to 3fd161e Compare August 14, 2026 23:07
@Chen-Yifan Chen-Yifan changed the title mpt: drop sec_root INVALID_OFFSET assert in rewind_to_version mpt: max version should return INVALID_BLOCK_NUM when rewind below its valid range Aug 17, 2026
@Chen-Yifan

Copy link
Copy Markdown
Contributor Author

@claude review the updated PR please

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No findings. The rewrite of root_offsets_delegator::rewind_to_version correctly handles the two ill-formed cases that previously left the ring reporting a max_version() with no root:

  • Below range (secondary activated after the target): version < version_lower_bound_reset_all() and return, matching a freshly activated secondary. UpdateAux::rewind_to_version's cutoff computation guards on sec_max != INVALID_BLOCK_NUM (category/mpt/update_aux.cpp:350), so the empty state flows through correctly without hitting the sec_root != INVALID_OFFSET tripwire on line 352.
  • Into a gap between valid roots: caught by the new (*this)[version] != INVALID_OFFSET assert, preserving the invariant that a non-empty ring always has a valid root at max_version().

The reset_all(version)reset_all() + advance_next_version(version) split is behavior-preserving. advance_next_version's reset branch (curr_max == INVALID_BLOCK_NUM || version - curr_max >= capacity_) writes the same two stores as the old reset_all(version); the small-gap else branch reproduces the old push(INVALID_OFFSET) loop with the same iteration count.

Dropping the trailing update_version_lower_bound_() in rewind_to_version is safe: version < version_lower_bound_ short-circuits to the empty-state path, and in the surviving path version_lower_bound_ still points to a valid slot (its slot is unchanged, since only slots strictly above version are cleared).

The two new tests cover both cases directly, including the ASSERT_DEATH for the gap invariant. Existing consumers of reset_all (clear_ondisk_db, update_root_offset) get the same 0/0 empty state they did before.

Verdict: CORRECT

🤖 Generated with Claude Code

Rewinding to a version before a ring's first valid root (e.g. a
secondary activated after the rewind target) left next_version at
target + 1, so max_version() reported a version whose slot held no
root and loading the latest root returned null. Reset the ring to
the empty state instead: max_version() reports INVALID_BLOCK_NUM,
matching a freshly activated secondary, and the next commit reseeds
it through the usual fast forward path.

Rewinding into a gap between valid roots is refused with an assert,
so a non-empty ring always has a valid root at max_version() and
the sec_root INVALID_OFFSET assert in UpdateAux::rewind_to_version
stays as a corruption tripwire.

Split reset_all(version) into reset_all(), which produces the empty
state, and advance_next_version(version), which positions the write
cursor for fast_forward_next_version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Chen-Yifan
Chen-Yifan force-pushed the vicky/rewind-secondary branch from 3fd161e to 5ee4c15 Compare August 28, 2026 19:02
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.

3 participants