Repository navigation
fix(platform)!: empty end dates no longer stall contested vote poll resolution (PV14) - #5321
Conversation
… opens its vote window (PV14) A contest resolved without locking moves its end date from the end of the join window to the full poll duration when a second contender joins. The move removed the entry with an up-tree delete stopping at height 2, the length of `Votes / e`, so GroveDB stopped before the time tree's key and left the time tree behind, empty. The ended-poll read counts an empty time tree toward its limit of 2, so two of them older than every real end date stop all contests from resolving. The delete now stops at height 1, as the document-expiration queue does for the same tree shape: the time tree goes with its last entry, `Votes / e` never does. The move test reads the queue raw, since the end-date query cannot show an empty time tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (3)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change updates end-date cleanup for ChangesNo-locking contest end dates
Priority: ⬆️ High Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to No actionable merge-blocking risk remains in the supplied evidence; the change is ready for normal merge checks. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 2 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 1📝 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 |
|
🔍 Review in progress — actively reviewing now (commit 0bf69da) · triage: normal |
|
Waiting for bot review — coderabbitai not yet · thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-10-07T22:36:10.734Z |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Phase 1 + Phase 2
The fix correctly extends deletion to the emptied timestamp tree while preserving the end-date queue root, and the changed implementation is selected only by protocol version 14. The raw-queue assertion catches the original regression; one non-blocking suggestion remains to cover preservation of a shared timestamp tree. Validation was static only: in the supplied CI snapshot, Rust workspace testing and the Swift SDK frozen schema check were still queued.
🟡 1 suggestion(s)
Review provenance
Source: reviewer 1: glm-5.3-flash (agent: phase1-reviewer, role: general); reviewer 2: glm-5.3-flash (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
lowbygpt-6.1-sol(effort low) — The diff makes a small, contained change to one deletion stop height with targeted regression assertions, so its consensus impact does not meet the large-or-intricate requirement for critical. - Phase 1 reviewers:
glm-5.3-flash— general (completed, effort high); agentphase1-reviewer,glm-5.3-flash— rust-quality (completed, effort high); agentphase1-reviewer - Phase 1 model:
glm-5.3-flash— zai quota: 5h 84% left, weekly 93% left; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 15% left, 5h 100% left) - Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort medium); agentphase2-reviewer,gpt-6.1-sol— architecture-layering (completed, effort medium); agentphase2-reviewer,gpt-6.1-sol— platform-versioning (completed, effort medium); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort medium); agentphase2-reviewer,gpt-6.1-sol— security-auditor (completed, effort medium); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/no_locking_contest_tests.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/no_locking_contest_tests.rs:633-645: Commit regression coverage for preserving a shared end-date tree
The new raw-queue assertion covers deleting the join-window timestamp when the moving contest owns its only entry. Lowering the stop height also makes the deletion inspect that timestamp tree when another contest still occupies it, but the committed no-locking tests do not cover this case. The existing shared-end-date award-cleanup tests exercise a separate explicit-delete implementation, not the up-tree deletion changed here. GroveDB's emptiness check currently protects sibling entries; this is a coverage gap, not evidence of an incorrect deletion. Retain the shared-timestamp case described in the PR as a committed regression test: start two distinct no-locking contests at the same block time, add a second contender to one, and assert with `vote_poll_end_dates` that the old timestamp still contains the other contest and the moved contest appears at its full-duration timestamp. Then move the remaining contest and assert that the old timestamp disappears. This pins both outcomes of the newly reached parent check and guards against silently losing another live contest's scheduled resolution.
Out-of-scope follow-up suggestions (1)
These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.
- Recover existing empty end-date trees on affected devnets — The changed insertion path prevents new empty timestamp trees but does not remove existing ones. The bounded ended-poll query still charges empty subtrees against its limit of two, and award cleanup only removes timestamps associated with returned polls. Consequently, an affected devnet can remain unable to resolve contests after an upgrade without a reset. This is a concrete pre-existing state-repair concern explicitly excluded from the PR, not a blocker for the preventive fix.
- Follow-up: Track consensus-versioned cleanup separately if affected beta devnets must be upgraded without resetting their state.
|
Bots are done — your move: post |
Two contests started in the same block share the end date of their join window. A second contender moving one of them keeps that end date for the other, and it goes once the other moves too. The contest helpers take a name so a test can run two contests side by side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s (PV14) An end date left with no vote poll under it ends nothing, yet the per-block read of ended vote polls counts it toward its limit of 2, so two of them due before every vote poll stall them all. Before that read, check_for_ended_vote_polls v1 now removes the empty end dates among the first maximum_vote_polls_to_process due, found by a raw read of the end-date keys (VotePollsByEndDateDriveQuery::execute_no_proof_empty_end_times). A chain that already holds some recovers in its next block. The move's stop height is derived from vote_root_path(), the path holding the end-date queries. Tests: a contest behind two empty end dates is awarded; the shared end-date test starts both contests in one block and ends both in one block; the single-contender award checks the queue raw; vote_poll_for reuses dpns_name_vote_poll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Waiting for bot review — coderabbitai not yet · thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
Basic explanation
What this does: Platform keeps a queue of contests ordered by the time each one ends, one folder per end time. In a contest without locking (protocol version 14, used by moderation elections), the end time moves later when a second contender joins. The move took the contest out of its old folder but left that folder behind, empty. This change removes the old folder too when nothing else ends at that time. Each block also now throws away any empty folders at the front of the queue before it looks for contests to end, so a chain that already has some recovers by itself.
Value: Each block looks at the 2 oldest folders that are due, and an empty folder still uses one of those 2 places. Once two empty folders are older than every real end time, no contest of any kind (moderation elections, DPNS names) is ever resolved again on that chain. With this fix the move leaves no empty folder, and any empty folder that exists (from before this fix, or from a future bug) is removed by the next block, so contests keep resolving.
Risks: Low. The move's fix is one argument to an existing delete, and the document-expiration queue already uses that value for the same tree shape. The per-block clean-up reads at most 2 folder headers and deletes only folders that are empty. Both change consensus only at protocol version 14, which is not live on testnet or mainnet; devnets running v5.0 betas need all nodes upgraded together or a reset.
Issue being fixed or feature implemented
A contest resolved without locking (
ContestedIndexResolution::MasternodeVoteNoLocking, PV14) opens with its end date at the end of the join window. The first additional contender moves it to the full poll duration (add_contested_document_for_contract_operations_v1). The move removed the old entry withbatch_delete_up_tree_while_emptyand a stop height ofvote_end_date_queries_tree_path_vec().len(), which is 2.GroveDB stops climbing as soon as the path holding the next key to delete has the stop height. The time tree's key sits under
Votes / e, a path of length 2, so the climb stopped before it. The comment beside the call already said the time tree should go with its last entry. The award cleanup (remove_contested_resource_vote_poll_end_date_query_operationsv1 and v2) deletes the time key explicitly, which is why ended contests never left one behind.check_for_ended_vote_pollsreads ended polls with a limit ofmaximum_vote_polls_to_process(2). Drive runs that read withdecrease_limit_on_range_with_no_sub_elements = true, so an empty time tree uses one slot of the limit and returns nothing. One such tree halves how many polls a block can end. Two of them, older than every real end date, leave nothing to end in any block, and nothing removed them. Every contested poll shares this queue, so DPNS name contests stop resolving too, even though they never move their own end dates.The move came in with #4907 and is in
v5.0.0-beta.2.What was done?
add_contested_document_for_contract_operationsv1: the move's up-tree delete stops atvote_root_path().len()(1), the path holding the end-date queries, as the document-expiration queue (Misc / E / <time>, the same shape) does. It removes the entry, then the time tree if the entry was its last, and neverVotes / eitself. GroveDB counts the batch's pending operations in that emptiness check, as it did before.VotePollsByEndDateDriveQuery::execute_no_proof_empty_end_times(Drive): the end times up to a given time that list no vote poll, among the firstlimitof them. It reads the end-date keys raw and keeps those whose tree has no root (Element::Tree(None, _)).check_for_ended_vote_pollsv1: before reading the polls due, it removes the empty end times among the firstmaximum_vote_polls_to_processdue, so the read reaches the polls behind them in the same block.contested-documents.md) describe both.The end-date queue after a second contender joins a contest that opened at
S:The ended-poll read at a block where two empty end times are due before a contest (limit 2):
A time that still holds another contest's entry is kept, as before.
How Has This Been Tested?
should_open_the_vote_window_when_a_second_contender_joins(drive-abci) now reads the queue raw withvote_poll_end_datesafter the move and after the award. Theend_dateshelper it already used reads through the subquery, where an empty time tree yields no rows, which is how the leftover tree went unnoticed. With the old stop height it fails withleft: {2711000: {}, 5411000: {<contest>}}againstright: {5411000: {<contest>}}.should_keep_a_shared_join_window_end_date_until_its_last_contest_moves(new, drive-abci): two contests start in one block and share their join window's end date; the first move keeps it for the other contest, the second move removes it (on the old stop height it fails at that step), and both contests then end in the one block at the end of their vote window.should_end_a_contest_behind_end_dates_left_empty(new, drive-abci): two empty end dates due before a single-contender contest. Without the clean-up it fails with no winner, as on the devnet; with it the contest is awarded and the queue is empty.should_award_a_single_contender_when_the_join_window_closeschecks the queue raw after the award too.no_locking_contest_testspass, as docargo test -p drive --lib votes(118),vote_polls_by_end_date(14),cargo test -p drive-abci --lib voting(73) andmasternode_vote(87), andcargo test -p platform-version --lib(25).{T1: {}}; stop height 1 removes a time tree with its last entry and keeps one still holding another contest; one empty time tree cuts a limit-2 read to 1 poll and two cut it to 0; an end time emptied by a real delete reads asElement::Tree(None, _), so the clean-up finds it.cargo fmt -p drive -p drive-abci -- --check, clippy on drive and drive-abci.Breaking Changes
Consensus at protocol version 14 only, which is not live on testnet or mainnet. The second contender's insert now also deletes the emptied time tree, so the state and the fee of that insert change, and a block now deletes empty end times due before reading the polls due. A node with this change computes a different app hash from an older v5.0 beta at the first such move or clean-up, so devnets running v5.0 betas need all nodes upgraded together or a reset. Once upgraded, a devnet that already holds empty end times clears them in its next block.
Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code
PR Hygiene ·
0bf69da/skip-botsproceeds without the ones not yet reported/self-reviewedWhen every merge requirement is met, the
PR Hygienecheck passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.Summary by CodeRabbit