Repository navigation
fix(platform): load persisted protocol version votes before the epoch tally - #4754
Conversation
… 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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (4)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe 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. ChangesProtocol vote restoration
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to 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)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
🕓 Queued for automated review — 3rd in line, estimated start in ~5 min (commit 37e5fbd)
|
Codecov Report❌ Patch coverage is 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
🚀 New features to boost your workflow:
|
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_upgradetalliesProtocolVersionsCache(global plus block cache) without loading it from Drive. The only production loader wasupdate_validator_proposed_app_version, which runs later in the same block inrun_block_proposal_v0. On a reopened Drive the cache starts empty, so the epoch tally counted zero votes and keptnext = currentwhile warm peers locked in the new version. Becauseclear_version_informationwipes 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/v1tally modules stay byte-identical (same shape as #4222, which moved block-cache clearing into therun_block_proposalorchestrator):run_block_proposal/mod.rs: load the persisted counters through the block transaction right afterclear_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
v0tests seedglobal_cachedirectly 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:
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.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 withnext = 13on the reopened node againstnext = 14on 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 --checkandcargo clippy --testsare 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:
For repository code-owners and collaborators only
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests