Skip to content

fix(platform): load persisted protocol version votes before the epoch tally - #4754

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/persisted-protocol-votes-fix-e2e69a
Sep 15, 2026
Merged

QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/persisted-protocol-votes-fix-e2e69a

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

A node whose first processed block after a Drive restart is an epoch boundary could lock in a different next protocol version than the peers that never restarted, and its app hash diverged on that very block.

check_for_desired_protocol_upgrade tallies ProtocolVersionsCache (global plus block cache) without loading it from Drive. The only production loader was update_validator_proposed_app_version, which runs later in the same block in run_block_proposal_v0. On a reopened Drive the cache starts empty, so the epoch tally counted zero votes and kept next = current while warm peers locked in the new version. Because clear_version_information wipes the on-disk counters in the same transaction, the late load then read an empty tree and marked the cache loaded, so the restarted node never saw the pre-epoch votes at all. The epoch-change operations write the next protocol version into the epoch tree, so the app hashes differ on the lock-in block itself, and one epoch later the warm peers activate the new version while the restarted node does not.

What was done?

Cache warmness is not versioned behaviour, and the bug affects every protocol version including PV13 on mainnet, so the fix lives in non-versioned infrastructure and the shipped v0/v1 tally modules stay byte-identical (same shape as #4222, which moved block-cache clearing into the run_block_proposal orchestrator):

  • run_block_proposal/mod.rs: load the persisted counters through the block transaction right after clear_drive_block_cache, before anything in the block reads the cache. A no-op once the cache is loaded.
  • Drive::open: eagerly load the persisted counters whenever a protocol version is stored, so a reopened Drive presents the same votes as a warm node from the first instant, for the block path and for queries alike.
  • ProtocolVersionsCache::is_loaded() accessor.

A hard guard in the tally dispatcher that errors on an unloaded cache was considered and rejected: the shipped v0 tests seed global_cache directly without loading, and a fallback load there would clobber those seeded votes.

How Has This Been Tested?

Regression tests were written first and observed failing before the fix:

  • rs-drive cache::protocol_version::tests: reopening a Drive at the same path presents the persisted votes; a cold cache loads them through a transaction; a pre-genesis Drive has nothing to load.
  • drive-abci strategy test run_chain_reopened_drive_at_epoch_boundary_locks_in_the_same_version_as_a_warm_node: 50 evonodes voting 13 to 14 over 60-block epochs, with the persisted vote count asserted against the threshold. A deterministic twin chain reopens its Drive right before the boundary, and the test compares height, epoch, current, next and app hash on both nodes at the lock-in block and again after activation. Before the fix it failed at height 61 with next = 13 on the reopened node against next = 14 on the warm node and different app hashes.

Targeted runs after the fix, all green: rs-drive cache, upgrade and operations tests; drive-abci upgrade-fork strategy tests, protocol-upgrade unit tests, drive-cache unit tests and finalize-block tests. cargo fmt --check and cargo clippy --tests are clean for both crates.

Breaking Changes

None. The tally rule is unchanged; nodes whose cache was already loaded compute exactly what they computed before. Replay from genesis is unaffected because the first vote of block 1 loads the cache long before any epoch boundary.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Protocol-version votes are now restored consistently when reopening a node, ensuring epoch-boundary upgrades select the same version as continuously running nodes.
    • Persisted vote counters are loaded before block processing and immediately when opening existing data.
  • Tests

    • Added coverage comparing reopened and continuously running nodes across protocol upgrades, including matching activation results and application hashes.
    • Added validation for persisted vote loading in fresh, reopened, and transactional scenarios.

… tally

The epoch tally in check_for_desired_protocol_upgrade read the protocol
version counter cache without loading it from Drive. The only production
loader ran later in the same block, so a node whose first block after a
Drive restart was an epoch boundary counted zero votes, kept
next = current while warm peers locked in the new version, and diverged
on the app hash of that block because the epoch tree records the next
version. The late load then read the tree that clear_version_information
had already wiped, so the restarted node never saw the votes at all.

Load the persisted counters through the block transaction at block start
in the non-versioned run_block_proposal orchestrator, and eagerly on
Drive::open once a protocol version is stored, so a reopened Drive
mirrors a warm node. The shipped tally modules are unchanged.

Regression tests: rs-drive cache tests that reopen a Drive, and a
strategy test that runs a warm node and a restarted twin through the
lock-in and activation boundaries and compares versions and app hashes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 14, 2026
@coderabbitai

coderabbitai Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 6f30933f-b80f-4528-94e8-b413e99d5157

📥 Commits

Reviewing files that changed from the base of the PR and between 4bb2b3e and 37e5fbd.

📒 Files selected for processing (4)
  • packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs
  • packages/rs-drive-abci/tests/strategy_tests/test_cases/upgrade_fork_tests.rs
  • packages/rs-drive/src/cache/protocol_version.rs
  • packages/rs-drive/src/open/mod.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change loads persisted protocol-version votes when a Drive opens and before block execution reads them. Unit and integration tests verify that reopened and continuously running nodes select the same protocol version and app hash at epoch boundaries.

Changes

Protocol vote restoration

Layer / File(s) Summary
Protocol vote cache state and persistence tests
packages/rs-drive/src/cache/protocol_version.rs
Adds is_loaded() and tests persisted vote loading through Drive reopening and transactions.
Drive opening restores protocol votes
packages/rs-drive/src/open/mod.rs
Loads persisted protocol-version votes when Drive::open resolves a platform version.
Block execution and epoch-boundary validation
packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs, packages/rs-drive-abci/tests/strategy_tests/test_cases/upgrade_fork_tests.rs
Loads votes before block processing and compares warm and reopened chains during protocol-version lock-in and activation.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 37e5f

The change restores persisted protocol-vote state before epoch-boundary tallying, and the supplied coverage compares reopened and continuously running chains through lock-in and activation. No remaining merge-blocking risk is identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: loading persisted protocol version votes before the epoch tally.
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 4 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/persisted-protocol-votes-fix-e2e69a

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.

@thepastaclaw

thepastaclaw commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 3rd in line, estimated start in ~5 min (commit 37e5fbd)
Estimated review time once started: ~5 min (two-phase automated review; median of recent runs).

  • Request priority review — tick this box and the review moves to the front of the queue.

@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.99099% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.09%. Comparing base (4bb2b3e) to head (37e5fbd).

Files with missing lines Patch % Lines
...bci/src/execution/engine/run_block_proposal/mod.rs 64.28% 5 Missing ⚠️
packages/rs-drive/src/cache/protocol_version.rs 96.59% 3 Missing ⚠️
packages/rs-drive/src/open/mod.rs 77.77% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4754      +/-   ##
============================================
- Coverage     87.32%   87.09%   -0.24%     
============================================
  Files          2827     2827              
  Lines        371564   372559     +995     
============================================
+ Hits         324476   324484       +8     
- Misses        47088    48075     +987     
Components Coverage Δ
dpp 88.93% <ø> (+0.04%) ⬆️
drive 86.55% <94.84%> (+0.06%) ⬆️
drive-abci 87.54% <64.28%> (-1.00%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.92% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 40.73% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Approved

@QuantumExplorer
QuantumExplorer merged commit 13bd220 into v4.2-dev Sep 15, 2026
25 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/persisted-protocol-votes-fix-e2e69a branch September 15, 2026 00:28
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