Skip to content

fix(platform)!: empty end dates no longer stall contested vote poll resolution (PV14) - #5321

Merged
QuantumExplorer merged 3 commits into
v5.0-devfrom
claude/platform-contested-poll-wedge-04764d
Oct 7, 2026
Merged

QuantumExplorer merged 3 commits into
v5.0-devfrom
claude/platform-contested-poll-wedge-04764d

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

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 with batch_delete_up_tree_while_empty and a stop height of vote_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_operations v1 and v2) deletes the time key explicitly, which is why ended contests never left one behind.

check_for_ended_vote_polls reads ended polls with a limit of maximum_vote_polls_to_process (2). Drive runs that read with decrease_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_operations v1: the move's up-tree delete stops at vote_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 never Votes / e itself. 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 first limit of them. It reads the end-date keys raw and keeps those whose tree has no root (Element::Tree(None, _)).
  • check_for_ended_vote_polls v1: before reading the polls due, it removes the empty end times among the first maximum_vote_polls_to_process due, so the read reaches the polls behind them in the same block.
  • PV14 note 23 and the book (contested-documents.md) describe both.

The end-date queue after a second contender joins a contest that opened at S:

Before:
votes / end_date_queries
├── S + join_window        (empty, never removed)
└── S + poll_duration
    └── <contest>

After:
votes / end_date_queries
└── S + poll_duration
    └── <contest>

The ended-poll read at a block where two empty end times are due before a contest (limit 2):

Before: [T1 (empty), T2 (empty)] -> nothing ends, in this block and every later one
After:  T1 and T2 are removed first, then the read finds the contest and ends it

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 with vote_poll_end_dates after the move and after the award. The end_dates helper 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 with left: {2711000: {}, 5411000: {<contest>}} against right: {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_closes checks the queue raw after the award too.
  • All 15 no_locking_contest_tests pass, as do cargo test -p drive --lib votes (118), vote_polls_by_end_date (14), cargo test -p drive-abci --lib voting (73) and masternode_vote (87), and cargo test -p platform-version --lib (25).
  • Local Drive checks (not committed): stop height 2 leaves {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 as Element::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:

  • 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
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · 0bf69da

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed
  • Build running
  • Approvals — you own every area touched; none needed

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

Summary by CodeRabbit

  • Bug Fixes
    • Corrected end-date listings for no-locking contests when additional contenders extend a contest beyond the join window. Shared end-date entries remain available until all contests using that date have moved to a later end date.
  • Documentation
    • Clarified that vote-poll listings show the applicable contest end date.

… 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>
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: dashpay/platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 13d0efb6-925c-4d24-8b7b-ae04da35e876
📥 Commits

Reviewing files that changed from the base of the PR and between 5881ec7 and 8e0c4ad.

📒 Files selected for processing (3)
  • book/src/data-model/contested-documents.md
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/no_locking_contest_tests.rs
  • packages/rs-drive/src/drive/document/insert_contested/add_contested_document_for_contract_operations/v1/mod.rs

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


📝 Walkthrough

Walkthrough

The change updates end-date cleanup for MasternodeVoteNoLocking contests, adds tests for contests with shared join-window end dates, and clarifies the documented end-date behavior.

Changes

No-locking contest end dates

Layer / File(s) Summary
Limit join-window cleanup
packages/rs-drive/src/drive/document/insert_contested/add_contested_document_for_contract_operations/v1/mod.rs, book/src/data-model/contested-documents.md
Cleanup uses a tree height of one to remove the end-date entry and its empty time tree while preserving the Votes tree. The documentation describes the end-date behavior.
Test individual and shared end dates
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/no_locking_contest_tests.rs
Test helpers create contests with supplied names. Tests check raw end-date entries after a second contender joins and when two contests share a join-window end date.

Priority: ⬆️ High

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: thepastaclaw

Merge Risk: ⚪ Minimal · up to 8e0c4

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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning 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… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: removing empty end dates so contested vote polls can resolve without stalling. It also identifies the protocol version impact.
Full details: Docstring Coverage

Explanation

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.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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 Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit 0bf69da) · triage: normal
█░░░░░░░░░░░░░░░░░░░ 6% · about 15 min left · running for 1 min
✅ triage → ⏳ Phase 1 (0/2 lanes) → ▫️ verify 1 → ▫️ gate → ▫️ Phase 2 → ▫️ verify 2 → ▫️ publish
Estimated from recent reviews of this tier · updated 21:42 UTC · live progress

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Oct 7, 2026
@github-actions github-actions Bot added this to the v5.0.0 milestone Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

📖 Book Preview built successfully.

Download the preview from the workflow artifacts.
To view locally: download the artifact, unzip, and open index.html.

Updated at 2026-10-07T22:36:10.734Z

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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: low by gpt-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); agent phase1-reviewer, glm-5.3-flash — rust-quality (completed, effort high); agent phase1-reviewer
  • Phase 1 model: glm-5.3-flash — zai quota: 5h 84% left, weekly 93% left; passed over gemini-3.8-flash-high (antigravity below 15% reserve: weekly 15% left, 5h 100% left)
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort medium); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort medium); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort medium); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort medium); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort medium); agent phase2-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.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. and removed waiting-bots Waiting for the review bots to report on this head labels Oct 7, 2026
QuantumExplorer and others added 2 commits October 7, 2026 22:34
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>
@QuantumExplorer QuantumExplorer changed the title fix(drive)!: remove the join window's emptied end date when a contest opens its vote window (PV14) fix(platform)!: empty end dates no longer stall contested vote poll resolution (PV14) Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. labels Oct 7, 2026
@QuantumExplorer
QuantumExplorer merged commit 331f255 into v5.0-dev Oct 7, 2026
11 of 13 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/platform-contested-poll-wedge-04764d branch October 7, 2026 21:48
@QuantumExplorer QuantumExplorer mentioned this pull request Oct 13, 2026
8 of 26 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants