Skip to content

feat(platform)!: yes/no masternode vote poll kind with supermajority and minimum voting power - #4899

Merged
QuantumExplorer merged 23 commits into
v5.1-devfrom
claude/pr-4875-df4cd2
Oct 28, 2026
Merged

QuantumExplorer merged 23 commits into
v5.1-devfrom
claude/pr-4875-df4cd2

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

What this does: Masternodes can already vote on one kind of question: which of several identities gets a contested name, such as a DPNS username more than one person wants. This adds a second kind of question they can vote on: a plain "yes or no?" poll, for example "should this moderation team be replaced?". A poll passes when the yes votes are more than a set share of the yes and no votes (two thirds, say) and enough voting power took part. This PR builds the voting machinery only: casting a vote, counting, closing the poll at its end date, recording the result, and proofs. Nothing opens such a poll yet; the moderation team challenge and charter amendment features will. It all activates with protocol version 15.

Value: Decentralized moderation teams (#4865) need the network to answer yes/no questions with a supermajority and a turnout floor, which the contested name poll cannot express. With this in place, each later feature only opens a poll and acts on its result, without new voting consensus code.

Risks: Medium. It is consensus code: from protocol version 15 the end of every block also closes due yes/no polls, and a mistake there fails the block on every node. Until something opens a yes/no poll that path never runs, and every yes/no vote is refused. At protocol version 14 and earlier nothing changes: a vote naming a yes/no poll is refused when it is decoded, as older binaries refuse it, and tests run a DPNS contest at 13 and at 14 to show it unchanged. Clients built before this change will fail to read getVotePollsByEndDate pages once the first yes/no poll exists, so they must upgrade before a feature opens one (see Breaking Changes).

Issue being fixed or feature implemented

Closes #4875, part of decentralized moderation teams (#4865).

Masternodes can only vote on one kind of poll today: the contested document resource poll that decides who gets a premium DPNS name. That poll picks a winner among contenders for a document. Decentralized moderation teams (#4865) need a different question answered: "should this happen, yes or no?"

  • A team challenge first asks the network "replace the sitting team?" (stage 1). It passes only with more than two thirds of yes over yes plus no, abstain left out, and at least 400 voting power cast. Only then does a contender poll pick the new team (stage 2, the contender poll from #4907).
  • A charter amendment asks the same kind of question with the same rule.

A contested poll cannot express either one: it has no contenders to choose between, no document to award, and its tally is a plurality with Lock and Abstain, not a supermajority with a turnout floor. So this PR adds a second poll kind, the yes/no poll, with its own vote, storage, closing and proofs.

flowchart LR
    TEAMS["Decentralized moderation teams, 4865"]
    CONTENDER["Contender poll without Lock, PR 4907 (merged)"]
    YESNO["Yes/no poll kind (this PR)"]
    FUND["Poll windows and fund from the target contract, 4876"]
    CHALLENGE["Team challenges (not filed yet)"]
    AMEND["Charter amendments (not filed yet)"]
    TEAMS --> CONTENDER
    TEAMS --> YESNO
    TEAMS --> FUND
    CONTENDER --> CHALLENGE
    YESNO --> CHALLENGE
    YESNO --> AMEND
    FUND --> CHALLENGE
    FUND --> AMEND
Loading

This PR is the poll machinery only. Nothing opens a yes/no poll yet: Drive::open_yes_no_vote_poll exists and is tested, but only the team challenge and charter amendment features will call it, as a side effect of their own transitions, and the funding work in #4876 funds the poll. Until then no yes/no poll can exist on any chain, so every yes/no vote is refused.

Status (2026-10-20): this branch now sits on v5.1-dev and everything it adds activates at protocol version 15 (it was parked at 14). The open design questions are listed in the comment "Session notes: where this PR stands"; they are worth settling before protocol version 15 ships, since the poll's format freezes then.

What was done?

The life of a yes/no poll

stateDiagram-v2
    [*] --> Started: opened and funded by a later feature
    Started --> Started: a masternode votes or changes its answer
    Started --> Finished: the first block at or after the end date tallies it
    Finished --> [*]: the stored info stays as the record of the decision
Loading
  1. Open (Drive::open_yes_no_vote_poll, no production caller yet): writes the stored info as Started(block), creates the three vote sum trees and adds the poll to the end date index. The end date must come after the opening block and at most four weeks later (SystemLimits::max_yes_no_vote_poll_duration_ms), which also keeps it below i64::MAX, past which the end date index would sort it before every block time. The opener writes no balance: the feature that opens the poll funds its prefunded balance, which pays for every vote, under the poll's specialized_balance_id. The poll's end date entry goes through the same writer as a contest's (add_vote_poll_end_date_query_operations). Openings in a batch follow the contract of document operations: earlier operations go in as the previous operations, so a poll the batch already opens is refused, a tree the batch already queues is queued once (an end date tree a contest queued keeps its contender's storage flags, and a contest after the opening adds only its entry), and a queued deletion of the end date's tree (a contest leaving its join-window end date) is cancelled.
  2. Vote: a MasternodeVote carrying a YesNoVote puts the masternode's strength (masternode 1, evonode 4, as for contested polls; the SystemLimits fields yes_no_vote_poll_masternode_vote_strength and yes_no_vote_poll_evonode_vote_strength, which the share minimum's masternode list total reads too) into the sum tree of its answer. Changing the answer moves the whole strength.
  3. Close: at the end of the first block at or after the end date, the tallies are read from the sum trees, the poll's rule decides (a minimum given as a share is measured against this block's masternode list), the result is written into the stored info, and the votes, trees, index entries and the rest of the fund are cleaned up.
  4. Act: on_yes_no_vote_poll_finished receives the result. Its v0 does nothing on purpose: the challenge and amendment features add the code that seats a challenger or swaps a charter.

Why the rule lives in the poll

YesNoVotePoll carries its own rule: supermajority_numerator, supermajority_denominator and minimum_voting_power, next to an opaque resource_path that the opening feature chooses (a contract id and a purpose, for example).

The poll passes when yes * denominator > (yes + no) * numerator and yes + no reaches the minimum. Abstain counts toward neither side, so abstaining neither helps nor blocks a decision. It only shows the masternode took part.

The minimum is set per poll, in one of two forms:

  • A fixed voting power, Absolute { voting_power }: from 0 (no floor) up to 2000.
  • A share of the network, FractionOfTotal { numerator, denominator, rounding }: that fraction of the total voting power of the masternode list (masternode 1, evonode 4) in the block that closes the poll, at most 50% of it. The share is rounded one of four ways: down (floor), up (ceil), downPlusOne (floor + 1) or upMinusOne (ceil - 1, never below 0). 1/10 rounded up is a tenth of the network; 1/3 rounded up is ceil(total / 3); 1/2 rounded down plus one is a strict majority (6 of 10, 5 of 9).

Why both: a fixed floor is predictable, while a share keeps the same meaning as the network grows or shrinks. The share is measured when the poll closes, so the bar reflects the network that decides. The bar the poll was judged against is recorded in its result (required_voting_power), so a finished poll shows why it passed or failed. The two caps (2000 and 50%) are SystemLimits fields.

With the challenge rule (2/3, a fixed minimum of 400):

yes no abstain result why
267 133 50 passes 267 x 3 = 801 > 400 x 2 = 800
266 134 0 fails 798 is not above 800
300 0 200 fails yes + no = 300 is under the minimum 400; abstain does not count

With a share instead (2/3, at least 1/10 of the total rounded up) and a masternode list worth 3,801 voting power, the bar is 381: 381 yes and 0 no passes, 380 fails, and the same 381 would fail once the list is worth 3,811.

The rule is part of the poll's identity. The poll's unique id is a double SHA-256 over a yes/no domain tag followed by the serialized VotePoll, so:

  • A vote names the exact poll it answers. A poll with another rule or path has another id, and nobody can change the rule after the poll opens.
  • Each feature sets its own rule. Challenges and amendments can differ without new consensus code.
  • The two poll kinds never share an id. The domain tag keeps a yes/no id away from a contested poll's id, which hashes an encoding that starts with a contract id. That matters because the id is also the prefunded balance id and the key in the end date index.

validate_parameters (selected by the dpp voting slot yes_no_vote_poll_parameters) refuses rules no poll should run with: an empty or oversized resource path, a zero denominator, a zero numerator (any single yes would pass), a numerator not below the denominator, a fixed minimum above 2000, and a share minimum with a zero denominator or above 50%. The path is bounded (SystemLimits: 16 segments, 1024 bytes) because every vote carries the whole poll.

A finished poll keeps its stored info forever as the record and cannot be reopened under the same id. A feature that asks the same question again puts a round marker in the resource path.

Why the storage looks like this

flowchart TD
    V["Votes"] --> C["c: contested resources (unchanged)"]
    V --> E["e: end date queries, shared by both poll kinds"]
    V --> D["d: decisions (reserved and empty since genesis)"]
    E --> ET["end time"]
    ET --> EP["poll id: the serialized VotePoll, either kind"]
    D --> P["p: active polls"]
    P --> PID["poll id"]
    PID --> S0["0: stored info, Started or Finished with the result"]
    PID --> S1["1: yes votes, sum tree of pro_tx_hash to strength"]
    PID --> S2["2: no votes, sum tree"]
    PID --> S3["3: abstain votes, sum tree"]
    D --> IV["i: identity votes"]
    IV --> MN["pro_tx_hash"]
    MN --> ANS["poll id: the answer and how many times it voted"]
Loading
  • The decisions branch d has existed, empty, since genesis for this purpose, so yes/no storage stays apart from contested storage.
  • One sum tree per answer makes the tally three reads at close time, whatever the number of voters.
  • The identity votes index i gives the previous answer for the change rules, finds every vote of a masternode that leaves the list, and lets a client prove one masternode's answer.
  • The p and i trees are created by one helper, insert_vote_decisions_trees. Genesis (create_initial_state_structure v4, behind >= YES_NO_VOTE_POLL_INITIAL_PROTOCOL_VERSION (15) on the chain's initial protocol version) and the upgrade to protocol version 15 (transition_to_version_15) both call it, in the same order, so a chain born at 15 and an upgraded chain build the same votes Merk. A test compares the two.
  • The end date index e is shared with contested polls. This is deliberate. One query at block end finds every due poll of either kind under one per-block limit, instead of two sweeps that would compete for it. The cost is the client compatibility break described under Breaking Changes.

Voting

flowchart TD
    T["MasternodeVote carrying a YesNoVote"] --> PV{"protocol version 15 or later?"}
    PV -- no --> R1["refused at decode, unpaid"]
    PV -- yes --> SIG["signature and nonce"]
    SIG --> FUND{"does the poll's fund cover one vote?"}
    FUND -- no --> R2["refused, unpaid: PrefundedSpecializedBalanceNotFound or Insufficient"]
    FUND -- yes --> PREV{"this masternode's previous answer"}
    PREV -- "the same answer" --> R3["MasternodeVoteAlreadyPresentError"]
    PREV -- "already voted 5 times" --> R4["MasternodeVotedTooManyTimesError"]
    PREV -- "none, or a different answer" --> ST{"is the poll open?"}
    ST -- "not opened" --> R5["VotePollNotFoundError"]
    ST -- "Finished" --> R6["YesNoVotePollNotAvailableForVotingError, 40308"]
    ST -- "Started" --> OK["pay the vote from the fund, move the strength to the chosen sum tree, update the identity index"]
Loading
  • Version gate. A masternode vote that names a yes/no poll exists from protocol version 15 only (StateTransition::active_version_range). That covers both a YesNoVote and a ResourceVote whose poll is a yes/no poll. Binaries from before this change cannot decode such a vote, so an earlier protocol version refuses it at decode, unpaid, exactly as they do. No shipped validator ever sees one.
  • Same change rules as contested votes. Giving the same answer twice is refused. A masternode may vote on a poll at most votes_allowed_per_masternode (5) times, its first vote included.
  • Same price as contested votes. Each vote is paid from the poll's prefunded balance at contested_document_single_vote_cost. The feature that opens the poll funds that balance; the opener writes none.
  • One new error. YesNoVotePollNotAvailableForVotingError (code 40308, StateError discriminant 176) refuses a vote on a finished poll. A ResourceVote naming a yes/no poll is refused as VotePollNotFoundError, since no contested poll has that id.

Closing at the end of a block

flowchart TD
    B["end of every block, protocol version 15"] --> RM["remove the votes of masternodes that left the list, both kinds"]
    RM --> Q["fetch the due polls from the shared end date index: at most 2 per block, across all end dates"]
    Q --> K{"poll kind"}
    K -- contested --> CT["tally and award, as before"]
    K -- "yes/no, already finished" --> LO["a leftover end date entry: only the entry goes"]
    K -- "yes/no" --> YT["read the three sums, measure a share minimum against this block's masternode list, apply the poll's rule"]
    YT --> REC["write Finished with the result into the stored info"]
    REC --> HOOK["on_yes_no_vote_poll_finished: no-op until challenges and amendments exist"]
    CT --> CL["clean_up_after_vote_polls_end v0 and contested clean-up v1, as at 14: end date entries, documents, votes, contenders, fund"]
    CL --> YC["then the yes/no clean-up, in its own batch: end date entries, the three vote trees (dropped flat), identity index entries, the rest of the fund to the processing pool"]
    HOOK --> YC
    LO --> YC
Loading

Why each clean-up removes its own end date entries. With two poll kinds under one end date, an end date's tree must go only once nothing of either kind is left under it, and deleting a non-empty tree fails the block. The contested end date removal v2 (#4996, protocol version 14) already decides that from what it reads under the end date, whatever the kind, so contested polls keep their clean-up (clean_up_after_vote_polls_end v0, contested clean-up v1) at protocol version 15 unchanged. check_for_ended_vote_polls v1 hands the yes/no polls it closed to the yes/no clean-up after the contested one, and that clean-up removes its own polls' entries through the same body (remove_vote_poll_ids_from_end_dates_operations_v2). Each clean-up applies its own batch, so the second reads what the first left: an end date holding a contest and a yes/no poll goes with the second batch.

A poll listed twice. Two openings of one poll that cannot see each other (each built with no previous operations) would list it under two end dates. The poll closes at the first; when a later entry comes due for a poll that is already finished, only that entry is removed, rather than the block failing on a finished poll. The per-block limit spans every due end date, so the last end date a block reaches can be only partly processed; the read keeps its tree. Tests close a DPNS contest and a yes/no poll that end in the same millisecond, and three polls over two end dates that need two blocks.

Proofs and clients

  • Poll state: YesNoVotePollStateDriveQuery proves a poll's stored info and tallies (verify_yes_no_vote_poll_state_proof).
  • One masternode's answer: verify_masternode_yes_no_vote proves it, using the prover's own IdentityBasedVoteDriveQuery path query and the bounded untrusted decoder.
  • Executed vote transitions: prove_state_transition and verify_state_transition_was_executed_with_proof handle the new vote kind.
  • The generic verifier: Drive::verify_masternode_vote routes a yes/no vote to the yes/no verifier. A genuine absence proof from the contested index can no longer pass as "this masternode did not vote". It refuses a resource vote that names a yes/no poll.
  • Bindings:
    • wasm-dpp2 Vote and VotePoll expose the kind (kind, yesNoChoice), and their TypeScript object and JSON types are unions that include the yes/no shapes.
    • wasm-dpp maps the new error.
  • rs-sdk:
    • get_current_dpns_contests skips yes/no entries and pages until a page comes back empty (the limit also counts empty end date trees, so a short page can still be cut, and an empty page is read again at the full page size before the listing ends). It never asks for a range that starts past the caller's end time, and refuses rather than drops an end date holding more polls than one page can list.
    • After a duplicate broadcast ("already exists"), a yes/no vote waits for the transition's result like a fresh one, since no query fetches yes/no votes yet.

How a yes/no vote looks on the wire (JSON). The minimum is internally tagged like every sum type; a share minimum is written { "$type": "fractionOfTotal", "numerator": 1, "denominator": 3, "rounding": "up" }:

{
  "$type": "yesNoVote",
  "$formatVersion": "0",
  "votePoll": {
    "resourcePath": ["wcHBwQ=="],
    "supermajorityNumerator": 2,
    "supermajorityDenominator": 3,
    "minimumVotingPower": { "$type": "absolute", "votingPower": 400 }
  },
  "voteChoice": "yes"
}

The YesNoAbstainVoteChoice type already existed in rs-dpp, unused. Its variants are now Yes, No and Abstain: the old rename_all produced "yES" on the wire.

Not in this PR

  • Opening and funding polls: team challenges, charter amendments and the funding in #4876.
  • Acting on a result: the arms of on_yes_no_vote_poll_finished, which the challenge and amendment features add.
  • Client queries: the gRPC query for a poll's state, its FromProof and the SDK Fetch, which are the client PR's.
  • A JS builder for yes/no votes.

Versioning

Every new behaviour is selected at protocol version 15 only. Protocol version 15 is unreleased, so its tables are amended in place:

  • DRIVE_VERSION_V10 (protocol version 15 only, shared with compiled contract storage, which selects its contract methods) with DRIVE_VOTE_METHOD_VERSIONS_V4 (new): insert.register_identity_vote 1, remove_all_votes_given_by_identities 1, every yes_no method Some(0) (each an OptionalFeatureVersion, None in every earlier table, so opening, voting on and closing a yes/no poll are not active there), cleanup.remove_vote_poll_end_date_query_operations Some(0), and contested_resource_insert.add_vote_poll_end_date_query_operations 1, which adds only the entry when the batch already queues the end date's tree with other storage flags (a contest and an opening sharing an end date), where 0 queued the tree twice. Contests in one batch share their owner and epoch, so their trees never differ in flags: for contests, 1 queues what 0 does. DRIVE_VERSION_V10 also selects DRIVE_VERIFY_METHOD_VERSIONS_V4 (new): the yes/no proof verifiers Some(0).
  • VOTING_VERSION_V3 (dpp, new, protocol version 15 only): the yes/no poll's stored info, id, parameter rules and decision rules Some(0).
  • DRIVE_ABCI_METHOD_VERSIONS_V11: the yes/no poll's tally, record, clean-up and finish hook Some(0).
  • DRIVE_ABCI_VALIDATION_VERSIONS_V11: masternode vote transform_into_action 1.
  • create_initial_state_structure 4 creates the decisions trees behind >= YES_NO_VOTE_POLL_INITIAL_PROTOCOL_VERSION (15, in feature_initial_protocol_versions.rs, which the masternode vote's active_version_range also reads) on the chain's initial protocol version, and the new transition_to_version_15 rung creates them on an upgrading chain. Opening a poll also creates them when missing, for a chain that reached 15 before they existed.

Every method slot the yes/no kind adds is an OptionalFeatureVersion, None (not active) in every earlier table and Some(0) at 15, so each yes/no method refuses to run before 15 (should_activate_every_yes_no_vote_poll_method_at_protocol_version_15 checks every protocol version's tables). Among them, the dpp voting slots yes_no_vote_poll_parameters and yes_no_vote_poll_decision select the poll's parameter rules and its decision (required_voting_power, passes), so a later protocol version can change either without touching the generation 15 decides with. yes_no_vote_poll_id selects how a poll's id is derived (YesNoVotePoll::unique_id): the id keys the poll's trees and its end date entry and names its prefunded balance, so a later id format would be a new generation while 15 keeps addressing what it wrote. Every caller of a poll's id or balance id (VotePoll::unique_id, Vote::vote_poll_unique_id, the specialized_balance_id methods, the yes/no path helpers and the identity vote query) passes the platform version; a contested poll's id does not read it. The seven SystemLimits fields (five caps and the two vote weights) carry the same values in every table, since nothing reads them before 15; should_weigh_yes_no_votes_one_for_a_masternode_and_four_for_an_evonode_at_protocol_version_15 pins the weights 15 tallies with. The new generations build on the shipped ones: transform_into_action v1, register_identity_vote v1 and remove_all_votes_given_by_identities v1 hand resource votes to their v0, and the yes/no clean-up reuses the contested end date removal and fund drain.

check_for_ended_vote_polls v1 and masternode vote state v1 (from #4907) are selected at both 14 and 15. They take their yes/no handling in place instead of through new v2 copies: no yes/no poll or vote can exist at 14, so it cannot run there. The next section lists every generation edited this way.

In-place changes to shipped generations

Protocol version 14 is not live on mainnet yet, but v5.0-dev ships it, so the generations it selects are treated as shipped here. Every edit below is unreachable at each protocol version up to 14 that selects the edited generation, or leaves its operations unchanged, and the edited lines say why. There are two roots of unreachability:

  • Votes. A masternode vote that names a yes/no poll (a YesNoVote, or a ResourceVote whose poll is a YesNoVotePoll) is active from protocol version 15 only (StateTransition::active_version_range). Before 15 it is refused at decode, unpaid, as a binary that cannot decode it refuses it, so no validator selected before 15 ever sees one.
  • Polls. A yes/no poll enters the end date index only through Drive::open_yes_no_vote_poll, which is not active before protocol version 15 (every yes_no drive method, open_yes_no_vote_poll included, is None in every table before 15).
Generation Selected at Edit Why consensus cannot change there
masternode vote transform_into_action v0 1 to 14 error arms for Vote::YesNoVote and for a resource vote naming VotePoll::YesNoVotePoll such a vote does not decode before 15
masternode vote validate_state v0 1 to 13 error arm for ResolvedVote::YesNoVote the same
masternode vote validate_state v1 14 and 15 the yes/no vote arm: it checks its poll's stored info (unknown poll: VotePollNotFoundError; finished: 40308) the same at 14; 15 is unreleased
Drive::register_identity_vote v0 (both functions) 1 to 14 (DRIVE_VOTE_METHOD_VERSIONS_V1 to V3) error arm for ResolvedVote::YesNoVote its only caller is the masternode vote drive operation, whose vote cannot name a yes/no poll before 15
register_identity_vote dispatcher 1 to 15 reads insert.register_identity_vote instead of contested_resource_insert.register_identity_vote both are 0 in every table up to 14, so v0 still runs there
check_for_ended_vote_polls v1 14 and 15 the yes/no polls due are split off before resolution and closed (finish_yes_no_vote_poll), or their leftover entry noted; the contested loop is as on base, and the yes/no clean-up runs after the contested one no yes/no poll is in the end date index before 15, so the contested polls are the polls the index returned, in the same order, through the same loop and clean-up call
masternode vote transform_into_action v0 1 to 14 the vote-change rule and the masternode lookup moved, unchanged, into previous_vote_to_replace_v0 and voting_masternode_v0, which v1 also calls and which are marked shipped (v0 keeps its own 1/4 strength) the same checks and errors in the same order
clean_up_after_contested_resources_vote_polls_end v1 8 to 15 the fund drain moved, unchanged, into empty_vote_poll_prefunded_balances_operations_v1, which the yes/no clean-up also calls the same operations in the same order
remove_all_votes_given_by_identities v0 1 to 14 its identity votes index query moved, unchanged, into identity_vote_entries_of_identities_v0, which v1 calls with the yes/no index path the same read
remove_specific_vote_references_given_by_identity v0 1 to 15 its delete loop moved, unchanged, into remove_identity_vote_references_operations_v0, which the yes/no reference removal also calls the same deletes in the same order
remove_contested_resource_vote_poll_end_date_query_operations v2 14 and 15 the body after grouping moved, unchanged, into remove_vote_poll_ids_from_end_dates_operations_v2, which the yes/no clean-up also calls the same operations in the same order
contested_vote_poll_for_document[_properties] v0 (dpp) and document create advanced_structure v1 1 to 15 / 14 and 15 the helper returns the ContestedDocumentResourceVotePoll it always built instead of wrapping it in VotePoll; the caller matches it directly a type-only change: the same poll and the same checks
create_initial_state_structure v4 14 and 15 insert_vote_decisions_trees behind >= YES_NO_VOTE_POLL_INITIAL_PROTOCOL_VERSION (15) on the chain's initial protocol version chain-creation content: genesis at 14 does not take the branch
perform_events_on_first_block_of_protocol_change v0 (v1 and v2 run its body) 3 to 15 a VotePoll::YesNoVotePoll arm in transition_to_version_8; the transition_to_version_15 rung the first runs once, on the block entering 8, when no yes/no poll can exist; the second runs only on the block entering 15
prove_state_transition v0 body (v1 reuses it) 1 to 15 a masternode vote's path query picks the identity votes branch by poll kind off the block execution path (it answers proof queries), and a contested poll's path query is byte-identical
verify_state_transition_was_executed_with_proof v0 body (v1 reuses it) 1 to 15 a yes/no vote is verified with verify_masternode_yes_no_vote; a resource vote naming a yes/no poll is refused client side only, and a contested vote verifies exactly as before
verify_masternode_vote dispatcher 1 to 15 before the version match, a yes/no vote goes to verify_masternode_yes_no_vote and a resource vote naming a yes/no poll is refused client side only; every contested vote reaches the version match unchanged
add_vote_poll_end_date_query_operations v0 (v1 runs it) 1 to 15 passes platform_version to VotePoll::unique_id only a yes/no poll's id reads the version, and no yes/no poll is opened before 15
add_contested_document_for_contract_operations v1 14 and 15 passes platform_version to VotePoll::unique_id of the contest's poll a contested poll's id does not read the version
masternode vote balance pre-check v0 and its drive operation conversion (masternode_vote_transition 0) 1 to 15 pass platform_version to Vote::specialized_balance_id / ResolvedVote::specialized_balance_id only a yes/no poll's id reads the version, and no vote naming one decodes before 15
verify_masternode_vote v0, prove_state_transition v0 body and prove_multiple_state_transition_results v0 1 to 15 pass platform_version to the poll id and to IdentityBasedVoteDriveQuery::construct_path_query off the block execution path, and a contested poll's id and path query are byte-identical

Also consensus-neutral up to 14: the remove_all_votes_given_by_identities dispatcher now reads its own slot instead of remove_specific_vote_references_given_by_identity, and the now unread contested_resource_insert.register_identity_vote slot is removed; both slots are 0 in every table up to 14, so the untouched v0 still runs there. Resolving a VotePoll now refuses a yes/no poll, which no path resolves (the end of block check closes it first, and a resource vote naming one is refused before resolution or does not decode). The new method slots in earlier tables (DRIVE_ABCI_METHOD_VERSIONS_V1 to V10, DRIVE_VOTE_METHOD_VERSIONS_V1 to V3, DRIVE_VERIFY_METHOD_VERSIONS_V1, VOTING_VERSION_V1 and V2 (dpp)) are None, so the yes/no methods they select refuse there, and the new SYSTEM_LIMITS_V1 to V4 fields (which SYSTEM_LIMITS_V5, protocol version 15's table, takes from V4) are read only by yes/no code; no existing value changed.

should_run_a_contest_unchanged_and_strip_yes_no_polls_at_protocol_version_13 and ..._14 run a DPNS contest at each version through the generations above: the vote counts, the contender is awarded and the end date index empties. Both also show the two vote kinds naming a yes/no poll stripped before any validator runs, with the voter's nonce left unused. The end date clean-up tests (end_date_cleanup_tests, and the contests without locking whose end dates are left empty or shared) run at 14 and at the latest version.

Before and after for the one behaviour change up to 14 (a ResourceVote naming a yes/no poll):

  • Before: a binary without this change cannot decode it and strips it with SerializedObjectParsingError.
  • After: a binary with this change decodes it, finds it not active before 15 and strips it unpaid like every transition not active yet: as an unpaid StateTransitionNotActiveError consensus error at 13 and at 14 (decode_raw_state_transitions 0 and 1). Every one of these outcomes keeps it out of a proposal and rejects a proposal that carries it.

How Has This Been Tested?

Locally, after the merge of v5.1-dev and the move to protocol version 15:

  • rs-dpp: the voting tests, the version gate and the frozen error discriminants (38 tests);

  • rs-drive: the votes, verify, query, structure, initialization and unsigned-key migration tests (245 tests), with grovedb-structure.json regenerated;

  • rs-drive-abci: the masternode vote tests (yes/no, end date clean-up, charter elections, contests without locking), the voting event tests and the protocol upgrade tests (186 tests);

  • cargo check --tests of platform-version, dpp, drive, drive-abci, dash-sdk and drive-proof-verifier, and a wasm32 cargo check of wasm-dpp, wasm-dpp2, wasm-sdk and wasm-drive-verify;

  • the verify-only rs-drive build, clippy on the touched crates and cargo fmt --all.

  • rs-dpp:

    • the passing rule at and one below the threshold;
    • the minimum with a unanimous yes, and no overflow at u32::MAX;
    • parameter validation, including a zero numerator and both minimum caps (2000; 50%, with 1/2 accepted and 51/100 refused);
    • a share minimum resolved with each of the four roundings (1/3 of 10 and of 9, and of 0: down plus one gives 1, up minus one stays 0), without overflow on the largest total, and judged against the total at close; the JSON name of every rounding;
    • the id changes with every parameter and is hashed under its domain tag;
    • JSON and Value round trips with the full wire shape, and the frozen StateError discriminants;
    • both vote kinds naming a yes/no poll refused as inactive at protocol version 14 and accepted at 15.
  • rs-drive:

    • proof round trips of a poll's state, with votes and absent;
    • a masternode's answer proved present, proved absent, and refused when the claimed answer differs;
    • a yes/no vote proved executed, and refused when not executed;
    • the generic verifier routing a yes/no vote and refusing a contested-index absence proof for it;
    • the structure description lint and conformance, with grovedb-structure.json regenerated.
  • rs-drive-abci:

    • the rule at the threshold and one power below it with abstains present, the minimum with 100% yes, and an evonode counting 4;
    • a minimum of half the masternode list rounded up (one evonode and five masternodes: a bar of 5), where the evonode alone fails and the evonode with one masternode passes, the result recording the bar;
    • clean-up leaving only the record, and a finished poll refusing votes and refusing to reopen;
    • refusals: an unknown poll, a resource vote naming a yes/no poll, the same answer twice, one vote too many, and a re-funded finished poll (reaching 40308);
    • a changed vote moving the whole strength, and removed masternodes losing their yes/no votes;
    • a contested poll and a yes/no poll ending in the same millisecond both closing;
    • one poll at T1 and two at T2 closing over two blocks;
    • a chain upgraded from 14 and a chain born at 15 building the same votes Merk;
    • at protocol versions 13 and 14, a contest running unchanged through the generations that gained yes/no arms, and both vote kinds naming a yes/no poll stripped.

Every yes/no vote in the tests goes through CheckTx first, which checks committed state stays untouched. A vote the block accepts must pass CheckTx at both levels, and a vote the block refuses must be refused by CheckTx's first check (except a refusal for a missing or short fund: CheckTx does not run the prefunded balance pre-check, for contested votes either).

Added after the review:

  • rs-drive: opening a poll a batch already opens is refused; opening a poll on a chain born at 14 creates the decisions trees.
  • rs-drive-abci: an unfunded poll closes without draining anything; the leftover fund of a closed poll reaches the processing pool; a contest closing beside a yes/no poll is finished and its fund drained; a share minimum is judged against a closing block list that differs from the committed one (a bar of 7, where the committed list gives 5); end dates are read with vote_poll_end_dates, which sees an emptied end date tree; each close is its own block.
  • rs-sdk: where the next page of vote polls by end date starts, including at the caller's end time.
  • wasm-dpp2: the yes/no shapes of VotePoll and Vote through toJSON/fromJSON/toObject/fromObject, the kind and yesNoChoice getters and the refused contested getters.

Added after the second review:

  • rs-drive: opening a poll is refused before protocol version 15 and when its end date is not after the opening block; two openings in one batch whose polls share an end date, on a chain lacking the decisions trees, queue each shared tree once (the test drive verifies batch consistency).
  • rs-drive-abci: a poll listed under two end dates closes at the first and only loses its later entry at the second; a CheckTx error counts as a refusal only where the block reports an internal error; the frozen remove_all_votes_given_by_identities v0 tests run at protocol version 14.

Added after the third review:

  • rs-drive: an end date past i64::MAX is refused; openings in one batch follow the previous-operations contract, and a contest's end date tree queued earlier in the batch keeps its contender's flags while the poll adds only its entry.
  • rs-drive-abci: the removal sweep drops a removed masternode's yes/no index tree (the existing sweep test checks its votes are gone).

Added after the fourth review:

  • rs-drive: an opening cancels a queued deletion of its end date's tree (a contest leaving its join-window end date) instead of writing under a tree the batch deletes; a contest after an opening at the same end date queues the tree once; a poll running longer than four weeks is refused and one running exactly four weeks opens.
  • rs-drive-abci: the end of block tests at 14 and at the latest version run as one test each over both versions.
  • wasm-dpp2: kind equals the $type of toJSON() for both poll kinds.

Added for the review of the fourth round's head:

  • rs-dpp: ResourceVoteV0::try_new refuses a yes/no poll; the wasm-dpp2 Vote and ResourceVote constructors and poll setters and wasm-sdk's masternodeVote build resource votes through it (specs check the refusal).
  • rs-drive: every yes/no drive method answers VersionNotActive at protocol version 14.

Added for the final validation:

  • rs-platform-version: every yes/no method slot is None before 15 and Some(0) from it in every protocol version's tables, and the vote weights are 1 and 4 at 15.
  • rs-drive-abci: the token shielded pool's nullifier batch guard pins state roots at the latest protocol version, so its root and nine settled hashes move with the decisions trees a chain born at 15 creates. With those trees left out, both tests pass on the old values, and the nine validation hashes did not change. After merging the compiled contract storage (feat(drive)!: store contracts compiled from protocol version 15 #5579), which moves the same values, they were pinned again for both changes together, keeping that PR's validation hashes.

Added for the review of the merge:

  • rs-drive: the yes/no poll state query (execute_with_proof, execute_no_proof) answers VersionNotActive at protocol version 14 (slot fetch_yes_no_vote_poll_state).

Added for the review of the merge's head:

  • rs-dpp: a yes/no poll's unique_id and specialized_balance_id answer not active at protocol version 14 (slot yes_no_vote_poll_id), and the id is unchanged at 15.
  • rs-platform-version: yes_no_vote_poll_id is among the slots checked None before 15 and Some(0) from it.
  • rs-sdk: get_current_dpns_contests with a limit of zero answers an empty map without sending a request, as before this change (an empty page at a small limit is read again at the default one, which a zero limit no longer reaches).

Re-run after versioning the yes/no poll id: platform-version yes/no gating (2 tests), rs-dpp voting (104), rs-drive votes (106), voting verifiers (20), proofs (64) and the masternode vote conversion (5), rs-drive-abci masternode vote (105), voting events (10), protocol upgrade (45), processor (5) and offset controls (5), rs-sdk contested queries (12) and the vote poll fetch tests (15); clippy on the touched crates, cargo fmt --all and the verify-only rs-drive build.

Re-run after merging v5.1-dev 60cc5f4 (compiled contract storage): platform-version (27 tests), rs-dpp voting (105), rs-drive votes, verify and initialization (136) and deterministic_root_hash, rs-drive-abci yes/no, masternode vote, protocol upgrade and the nullifier batch guard pins (182).

Re-run after merging v5.1-dev 0d91c13 and the review of the fourth round's head: rs-dpp voting (108 tests), rs-drive votes, queries, structure, fees and the contract migration (257), rs-drive-abci masternode vote, voting event, protocol upgrade, contests and DPNS (303), rs-sdk contested queries with mocks (11); clippy on the touched crates, cargo fmt --all and a wasm32 check of wasm-dpp2 and wasm-sdk. The protocol version 13 stripped-vote test now expects the unpaid not-active error that v5.1-dev's decode_raw_state_transitions v0 reports.

Re-run after the fourth review: rs-dpp voting (107 tests), rs-drive votes, queries, structure, fees and pending operations (246), rs-drive-abci masternode vote, voting event and protocol upgrade (187), contests (120), rs-sdk paging (3); clippy on the touched crates, cargo fmt --all and a wasm32 check of wasm-dpp2.

Re-run after the third review: rs-dpp voting (108 tests), rs-drive votes, verify, query, structure and the document counters (304), rs-drive-abci masternode vote, voting event, protocol upgrade and contests (269), the CheckTx-guard users (107), rs-sdk paging (3); clippy on the touched crates, cargo fmt --all, the verify-only rs-drive build and a wasm32 check of wasm-dpp2, wasm-sdk and wasm-drive-verify.

Re-run after the second review: rs-dpp voting (108 tests), rs-drive votes, verify, query and structure (273), rs-drive-abci masternode vote, voting event and protocol upgrade (191), contests and the CheckTx-guard users (245), rs-sdk paging (3); clippy on the touched crates, cargo fmt --all, the verify-only rs-drive build and a wasm32 check of wasm-dpp2, wasm-sdk and wasm-drive-verify.

Breaking Changes

Consensus: new VotePoll and Vote variants, a new state error (40308, StateError discriminant 176), and new trees under Votes/d, all at protocol version 15 (unreleased).

Clients: yes/no polls share the getVotePollsByEndDate index with contested polls. A client built before this change decodes every entry as a contested-only VotePoll. Once a yes/no poll is open, any page whose range covers its end date fails to verify as a whole, DPNS contest listings included.

  • Before: getVotePollsByEndDate only ever returns contested document resource polls.
  • After: it can also return VotePoll::YesNoVotePoll entries. This change's rs-sdk skips them in get_current_dpns_contests, and the wasm bindings expose the kind.

Rust API: DocumentTypeV0Methods::contested_vote_poll_for_document and contested_vote_poll_for_document_properties return Option<ContestedDocumentResourceVotePoll> instead of Option<VotePoll>. They always built a contested poll, and with VotePoll gaining a yes/no variant the narrower type spares callers an arm that cannot occur. A caller wrapping the result keeps working with .map(VotePoll::from). VotePoll::unique_id, VotePoll::specialized_balance_id, Vote::vote_poll_unique_id, Vote::specialized_balance_id and ResourceVoteV0::vote_poll_unique_id (dpp), and Drive's ResolvedVote::specialized_balance_id and IdentityBasedVoteDriveQuery::construct_path_query, take a &PlatformVersion, because a yes/no poll's id is derived as the version selects. A contested poll's id is unchanged; callers pass the version they run (sdk.version() in the SDK).

Nothing in this change opens a yes/no poll, so the break starts with the first opener: moderation team challenges or charter amendments, at protocol version 15 or later. SDKs, Dash Evo Tool and the mobile clients must upgrade before then.

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 · fe6482b

  • Bots — thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed
  • Build running
  • Approvals
    • files with no dedicated owner — you own it
    • dpp — you own it
    • rs-drive-abci — you own it
    • rs-drive — you own it
    • rust-sdk (packages/rs-sdk/src/platform/documents/contest_fund.rs, packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs, packages/rs-sdk/src/platform/transition/vote.rs and 4 more) — lklimek or shumkov
    • js-wasm-sdk (packages/wasm-sdk/src/state_transitions/identity.rs) — shumkov

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.

@coderabbitai

coderabbitai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 172 files, which is 72 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 1a5f362 and fe6482b.

Files selected for processing: 172
  • book/src/data-model/contested-documents.md
  • book/src/error-handling/error-codes.md
  • packages/rs-dpp/src/data_contract/document_type/methods/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs
  • packages/rs-dpp/src/errors/consensus/codes.rs
  • packages/rs-dpp/src/errors/consensus/state/state_error.rs
  • packages/rs-dpp/src/errors/consensus/state/voting/mod.rs
  • packages/rs-dpp/src/errors/consensus/state/voting/yes_no_vote_poll_not_available_for_voting_error.rs
  • packages/rs-dpp/src/serialization/json/safe_fields.rs
  • packages/rs-dpp/src/state_transition/mod.rs
  • packages/rs-dpp/src/voting/vote_choices/yes_no_abstain_vote_choice/mod.rs
  • packages/rs-dpp/src/voting/vote_info_storage/mod.rs
  • packages/rs-dpp/src/voting/vote_info_storage/yes_no_vote_poll_stored_info/mod.rs
  • packages/rs-dpp/src/voting/vote_info_storage/yes_no_vote_poll_stored_info/v0/mod.rs
  • packages/rs-dpp/src/voting/vote_polls/mod.rs
  • packages/rs-dpp/src/voting/vote_polls/yes_no_vote_poll/mod.rs
  • packages/rs-dpp/src/voting/votes/mod.rs
  • packages/rs-dpp/src/voting/votes/resource_vote/mod.rs
  • packages/rs-dpp/src/voting/votes/resource_vote/v0/mod.rs
  • packages/rs-dpp/src/voting/votes/yes_no_vote/accessors/mod.rs
  • packages/rs-dpp/src/voting/votes/yes_no_vote/accessors/v0/mod.rs
  • packages/rs-dpp/src/voting/votes/yes_no_vote/mod.rs
  • packages/rs-dpp/src/voting/votes/yes_no_vote/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/check_for_ended_vote_polls/v1/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/clean_up_after_contested_resources_vote_polls_end/v1/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/clean_up_after_yes_no_vote_polls_end/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/clean_up_after_yes_no_vote_polls_end/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/keep_record_of_finished_yes_no_vote_poll/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/keep_record_of_finished_yes_no_vote_poll/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/on_yes_no_vote_poll_finished/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/on_yes_no_vote_poll_finished/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/tally_votes_for_yes_no_vote_poll/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/voting/tally_votes_for_yes_no_vote_poll/v0/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v1/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/shielded/nullifier_batch_guard.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/balance/v0/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/charter_election_tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/end_date_cleanup_tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/no_locking_contest_tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/state/v0/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/state/v1/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/transform_into_action/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/transform_into_action/v0/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/transform_into_action/v1/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/yes_no_tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/mod.rs
  • packages/rs-drive-abci/src/test/helpers/state_mutation_guard.rs
  • packages/rs-drive-abci/tests/query_request_errors/offset_controls.rs
  • packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs
  • packages/rs-drive/grovedb-structure.json
  • packages/rs-drive/src/drive/contract/migration/rekey_unsigned_integer_index_values.rs
  • packages/rs-drive/src/drive/document/insert_contested/add_contested_document_for_contract_operations/v1/mod.rs
  • packages/rs-drive/src/drive/initialization/v4/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_all_votes_given_by_identities/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_all_votes_given_by_identities/v0/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_all_votes_given_by_identities/v1/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_contested_resource_vote_poll_end_date_query_operations/v2/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_specific_votes_given_by_identity/v0/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_vote_poll_end_date_query_operations/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_vote_poll_end_date_query_operations/v0/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_yes_no_vote_poll_votes_operations/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_yes_no_vote_poll_votes_operations/v0/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_yes_no_vote_references_given_by_identity/mod.rs
  • packages/rs-drive/src/drive/votes/cleanup/remove_yes_no_vote_references_given_by_identity/v0/mod.rs
  • packages/rs-drive/src/drive/votes/fetch/fetch_identities_voting_in_yes_no_vote_poll/mod.rs
  • packages/rs-drive/src/drive/votes/fetch/fetch_identities_voting_in_yes_no_vote_poll/v0/mod.rs
  • packages/rs-drive/src/drive/votes/fetch/fetch_identity_yes_no_vote/mod.rs
  • packages/rs-drive/src/drive/votes/fetch/fetch_identity_yes_no_vote/v0/mod.rs
  • packages/rs-drive/src/drive/votes/fetch/fetch_yes_no_vote_poll_stored_info/mod.rs
  • packages/rs-drive/src/drive/votes/fetch/fetch_yes_no_vote_poll_stored_info/v0/mod.rs
  • packages/rs-drive/src/drive/votes/fetch/mod.rs
  • packages/rs-drive/src/drive/votes/insert/mod.rs
  • packages/rs-drive/src/drive/votes/insert/register_identity_vote/mod.rs
  • packages/rs-drive/src/drive/votes/insert/register_identity_vote/v0/mod.rs
  • packages/rs-drive/src/drive/votes/insert/register_identity_vote/v1/mod.rs
  • packages/rs-drive/src/drive/votes/insert/vote_poll/add_vote_poll_end_date_query_operations/mod.rs
  • packages/rs-drive/src/drive/votes/insert/vote_poll/add_vote_poll_end_date_query_operations/v0/mod.rs
  • packages/rs-drive/src/drive/votes/insert/vote_poll/add_vote_poll_end_date_query_operations/v1/mod.rs
  • packages/rs-drive/src/drive/votes/insert/yes_no/insert_stored_info_for_yes_no_vote_poll/mod.rs
  • packages/rs-drive/src/drive/votes/insert/yes_no/insert_stored_info_for_yes_no_vote_poll/v0/mod.rs
  • packages/rs-drive/src/drive/votes/insert/yes_no/mod.rs
  • packages/rs-drive/src/drive/votes/insert/yes_no/open_yes_no_vote_poll/mod.rs
  • packages/rs-drive/src/drive/votes/insert/yes_no/open_yes_no_vote_poll/v0/mod.rs
  • packages/rs-drive/src/drive/votes/insert/yes_no/register_yes_no_identity_vote/mod.rs
  • packages/rs-drive/src/drive/votes/insert/yes_no/register_yes_no_identity_vote/v0/mod.rs
  • packages/rs-drive/src/drive/votes/mod.rs
  • packages/rs-drive/src/drive/votes/paths.rs
  • packages/rs-drive/src/drive/votes/resolved/vote_polls/resolve.rs
  • packages/rs-drive/src/drive/votes/resolved/votes/mod.rs
  • packages/rs-drive/src/drive/votes/resolved/votes/resolve.rs
  • packages/rs-drive/src/drive/votes/resolved/votes/resolved_yes_no_vote/mod.rs
  • packages/rs-drive/src/drive/votes/setup/insert_vote_decisions_trees/mod.rs
  • packages/rs-drive/src/drive/votes/setup/mod.rs
  • packages/rs-drive/src/drive/votes/storage_form/mod.rs
  • packages/rs-drive/src/drive/votes/storage_form/vote_storage_form/mod.rs
  • packages/rs-drive/src/drive/votes/storage_form/yes_no_vote_reference_storage_form/mod.rs
  • packages/rs-drive/src/drive/votes/structure.rs
  • packages/rs-drive/src/fees/op.rs
  • packages/rs-drive/src/prove/prove_multiple_state_transition_results/v0/mod.rs
  • packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs
  • packages/rs-drive/src/query/mod.rs
  • packages/rs-drive/src/query/vote_query.rs
  • packages/rs-drive/src/query/yes_no_vote_poll_state_query.rs
  • packages/rs-drive/src/state_transition_action/action_convert_to_operations/identity/masternode_vote_transition.rs
  • packages/rs-drive/src/state_transition_action/identity/masternode_vote/v0/mod.rs
  • packages/rs-drive/src/structure/tests.rs
  • packages/rs-drive/src/util/test_helpers/mod.rs
  • packages/rs-drive/src/verify/bounded_decode.rs
  • packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs
  • packages/rs-drive/src/verify/voting/mod.rs
  • packages/rs-drive/src/verify/voting/verify_masternode_vote/mod.rs
  • packages/rs-drive/src/verify/voting/verify_masternode_vote/v0/mod.rs
  • packages/rs-drive/src/verify/voting/verify_masternode_yes_no_vote/mod.rs
  • packages/rs-drive/src/verify/voting/verify_masternode_yes_no_vote/v0/mod.rs
  • packages/rs-drive/src/verify/voting/verify_yes_no_vote_poll_state_proof/mod.rs
  • packages/rs-drive/src/verify/voting/verify_yes_no_vote_poll_state_proof/v0/mod.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_voting_versions/mod.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_voting_versions/v1.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_voting_versions/v2.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_voting_versions/v3.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v1.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v10.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v11.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v2.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v3.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v5.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v6.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v7.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v8.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v9.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v11.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v4.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_vote_method_versions/mod.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_vote_method_versions/v1.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_vote_method_versions/v2.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_vote_method_versions/v3.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_vote_method_versions/v4.rs
  • packages/rs-platform-version/src/version/drive_versions/v10.rs
  • packages/rs-platform-version/src/version/feature_initial_protocol_versions.rs
  • packages/rs-platform-version/src/version/mocks/v2_test.rs
  • packages/rs-platform-version/src/version/mocks/v3_test.rs
  • packages/rs-platform-version/src/version/protocol_version.rs
  • packages/rs-platform-version/src/version/system_limits/mod.rs
  • packages/rs-platform-version/src/version/system_limits/v1.rs
  • packages/rs-platform-version/src/version/system_limits/v2.rs
  • packages/rs-platform-version/src/version/system_limits/v3.rs
  • packages/rs-platform-version/src/version/system_limits/v4.rs
  • packages/rs-platform-version/src/version/v15.rs
  • packages/rs-sdk/src/platform/documents/contest_fund.rs
  • packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs
  • packages/rs-sdk/src/platform/transition/vote.rs
  • packages/rs-sdk/tests/fetch/mock_fetch_many.rs
  • packages/rs-sdk/tests/fetch/prefunded_specialized_balance.rs
  • packages/rs-sdk/tests/fetch/vote_poll_default_limit.rs
  • packages/rs-sdk/tests/fetch/vote_poll_default_limit_controls.rs
  • packages/wasm-dpp/src/errors/consensus/consensus_error.rs
  • packages/wasm-dpp/src/voting/state_transition/masternode_vote_transition/mod.rs
  • packages/wasm-dpp2/src/voting/resource_vote.rs
  • packages/wasm-dpp2/src/voting/vote.rs
  • packages/wasm-dpp2/src/voting/vote_poll.rs
  • packages/wasm-dpp2/tests/unit/ResourceVote.spec.ts
  • packages/wasm-dpp2/tests/unit/Vote.spec.ts
  • packages/wasm-dpp2/tests/unit/VotePoll.spec.ts
  • packages/wasm-sdk/src/state_transitions/identity.rs

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · 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.

@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 21, 2026
@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 21, 2026
@thepastaclaw

thepastaclaw commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Review not started yet because the new head is waiting for the 30-minute push debounce.

  • Request normal review — click when the PR is ready for review.
  • Request priority review — click to move this review to the front of the queue.

Commit fe6482b. Normal review starts when eligible; priority review starts as soon as a slot is available.

@github-actions

github-actions Bot commented Sep 21, 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-28T20:13:33.412Z

@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

🌳 GroveDB structure

This pull request changes the described GroveDB structure. Open it in the structure viewer: new nodes glow, removed ones stay as ghosts, and the tour walks through each change.

Added (12 nodes)

  • votes.decisions.identity_votes
  • votes.decisions.active_polls

Changed (4 nodes)

  • votes
  • votes.decisions
  • votes.end_date_queries.end_date
  • votes.end_date_queries.end_date.poll

Compared 1a5f362936 with fe6482b836. Updated at 2026-10-28T20:19:48.021Z

@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai review

No review for fcafe483 yet, so PR Hygiene is asking once. If nothing arrives, the requirement is dropped for this commit and the pull request is labelled bot-review-skipped.

@github-actions

github-actions Bot commented Sep 22, 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 Sep 22, 2026
…and minimum voting power

A second kind of masternode vote poll for the moderation team challenge and
charter amendment votes (#4875, decisions D21 and D41 of #4865).

`YesNoVotePoll` is keyed by a caller-chosen resource path and carries its own
rule: it passes when yes power is strictly more than numerator/denominator of
yes plus no power (abstain left out) and yes plus no reaches the minimum. Its
unique id hashes the serialized `VotePoll`, and the prefunded balance the votes
are paid from has the same id. `YesNoVote` answers it with yes, no or abstain;
a masternode vote carrying one is active from protocol version 14.

Storage lives under the votes tree's decisions branch: one tree per poll with
the stored info and three vote sum trees, and an identity votes index keyed by
pro tx hash. The trees are created at genesis (vote setup v1) and on the first
block of protocol version 14. `check_for_ended_vote_polls` v1 tallies a poll
whose time came, records the result in its stored info, hands it to the
`on_yes_no_vote_poll_finished` hook and cleans the votes up; the stored info
stays as the record. `clean_up_after_vote_polls_end` v1 removes every finished
poll of either kind from the shared end date index itself, since the contested
clean-up's count of contested polls alone would delete an end date's tree that
still held a yes/no entry; `clean_up_after_contested_resources_vote_polls_end`
v2 leaves the index to it.

Masternode vote transform v1 looks up the previous yes/no answer for the vote
change rules, state v1 requires the poll to exist and be started (new
`YesNoVotePollNotAvailableForVotingError`, 40308). Proofs: a poll state query
with verification and a masternode yes/no vote verifier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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 Sep 23, 2026
QuantumExplorer and others added 2 commits September 23, 2026 09:07
…tocol version 14

A masternode vote carrying a yes/no vote was already active from protocol
version 14 only, but a resource vote whose poll is a yes/no poll stayed active
at every version. Binaries from before the yes/no poll kind cannot decode
either, while a new binary at protocol version 13 decoded the resource vote and
ran the shipped (v0) masternode vote validation on it, where the fund pre-check
refused it. Both kinds are now inactive before 14, so decoding refuses them
without charging, exactly as older binaries do, and no validator selected
before 14 meets a yes/no poll.

That makes the yes/no arms this change added to shipped generations
unreachable by construction. Each now says why at the edited lines, and a
protocol version 13 test runs a DPNS contest through the shipped transform,
state validation, vote registration, end of poll check and clean-up with the
outcome unchanged, and shows both vote kinds naming a yes/no poll stripped
before any of them runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#4930 appended ReferencedDocumentLookupInvalidError as StateError discriminant
145, so YesNoVotePollNotAvailableForVotingError moves to 146; the frozen
discriminant test pins both, and wasm-dpp maps both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai review

No review for 44545bef yet, so PR Hygiene is asking once. If nothing arrives, the requirement is dropped for this commit and the pull request is labelled bot-review-skipped.

@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 2 only (queue backlog)

Confirmed two blocking defects: partial end-date batches can abort the new poll-cleanup lifecycle, and the generic vote verifier accepts authenticated absence proofs from the wrong storage branch for yes/no votes. Both were independently reproduced against the assigned head; source inspection also confirms that the exported TypeScript serialization types exclude the newly supported variants. The worktree remains unchanged.

🔴 2 blocking | 🟡 1 suggestion(s)

Review provenance

Source: reviewer 1: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 2: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6-astra (agent: phase2-reviewer, role: ffi-engineer); reviewer 4: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 5: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)

  • Triage: critical by gpt-6-astra (effort low) — This large, intricate change adds consensus-critical vote validation and poll resolution in transform_into_action/v1 and check_for_ended_vote_polls/v1, and migrates voting storage during protocol activation in perform_events_on_first_block_of_protocol_change/v0.
  • Phase 1 reviewers: not run (skipped for throughput: 18 PRs queued, above the 10 limit)
  • Fresh verifier: gpt-6-astra — final-verifier; agent astra-verifier
  • Phase 2 reviewers: gpt-6-astra — general (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — security-auditor (completed, effort xhigh); 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/src/drive/votes/cleanup/remove_vote_poll_end_date_query_operations/v0/mod.rs`:
- [BLOCKING] packages/rs-drive/src/drive/votes/cleanup/remove_vote_poll_end_date_query_operations/v0/mod.rs:55-65: Do not delete an end-date tree based on its partial batch count
  `maximum_vote_polls_to_process` limits the query across all timestamps, not each timestamp separately. With the current limit of two, one due poll at T1 and two at T2 return one poll from each timestamp. Both per-timestamp counts are below two, so this helper schedules deletion of T2's parent while an unprocessed poll remains. An independently rebuilt regression using three yes/no polls and production batch settings fails with `GroveDB(InvalidBatchOperation("modification of tree when it will be deleted"))`; `clean_up_after_vote_polls_end_v1` propagates that error into block processing. Although the contested-only predecessor contains the same assumption, this PR introduces the shared helper and makes the new yes/no lifecycle depend on it, so this is required for the advertised cleanup behavior to work. Check actual remaining entries, accounting for queued removals, before deleting each timestamp tree, and add a regression where a globally limited batch only partially consumes its final timestamp.

In `packages/rs-dpp/src/voting/votes/mod.rs`:
- [BLOCKING] packages/rs-dpp/src/voting/votes/mod.rs:70-73: Dispatch or reject yes/no votes in the existing generic proof verifier
  The widened `Vote` enum now supplies yes/no poll IDs to the existing public `Drive::verify_masternode_vote`, but that verifier still unconditionally queries `Votes/c/i/<pro_tx_hash>`. Yes/no votes are stored under `Votes/d/i/<pro_tx_hash>`. A proof-serving node can therefore supply a genuine contested-branch absence proof and have the client authenticate a false absence result for an existing yes/no vote. An independently rebuilt regression registered a yes/no vote, supplied the wrong-branch absence proof, and received `Ok((root_hash, None))`, with the root matching the database containing the vote. The exported `wasm-drive-verify` `verifyMasternodeVote` function also reaches this path because it deserializes `Vote` and delegates directly to the generic verifier. Routing in state-transition verification does not protect these callers. Dispatch by vote kind at the shared verifier boundary, or explicitly reject unsupported vote/poll combinations, and add a wrong-branch absence-proof regression.

In `packages/wasm-dpp2/src/voting/vote_poll.rs`:
- [SUGGESTION] packages/wasm-dpp2/src/voting/vote_poll.rs:128: Include yes/no variants in the exported TypeScript wire types
  The bindings now expose yes/no polls and votes, but `VotePollObject` and `VotePollJSON` still require `$type: "contestedDocumentResourceVotePoll"` and contested-only fields. Likewise, `voting/vote.rs` declares `VoteObject` and `VoteJSON` exclusively as resource votes. The five-argument `impl_wasm_conversions_inner!` macro uses these declarations for both conversion inputs and outputs, so valid yes/no `fromJSON`/`fromObject` calls fail TypeScript checking, and serialized results cannot be correctly narrowed or inspected without bypassing the published types. This affects the serialization API added here, independently of deferred client query APIs. Make all four declarations discriminated unions matching DPP's wire shapes, including the untagged poll body inside a yes/no vote, and add a TypeScript conversion fixture.

Comment thread packages/rs-dpp/src/voting/votes/mod.rs Outdated
Comment thread packages/wasm-dpp2/src/voting/vote_poll.rs
…ting, genesis trees, bindings

- The end-date clean-up deleted an end date's tree whenever fewer than
  maximum_vote_polls_to_process polls closed there, but the block's fetch
  shares that limit across every due end date. A partly fetched last end date
  lost its tree in the same batch as the fetched entry, GroveDB refused the
  batch and the block failed. The tree now goes only when the entries it holds
  are all being removed; regression test with one poll at T1 and two at T2.
- Drive::verify_masternode_vote queried the contested resource votes index for
  any vote, so a genuine absence proof from that branch verified a yes/no vote
  as "not voted". It now routes a yes/no vote to verify_masternode_yes_no_vote
  and refuses a resource vote naming a yes/no poll; wrong-branch regression.
- Genesis creates the decisions trees through insert_vote_decisions_trees, the
  helper the protocol version 14 upgrade uses (vote setup v1 removed).
- Closing a poll no longer copies the pending batch on every delete (quadratic
  in voters) and deletes the choice sum trees without re-reading every vote.
- validate_parameters refuses a zero numerator; poll ids hash a domain tag
  before the serialized poll, so the two poll kinds never share an id.
- The yes/no vote verifier reuses the prover's IdentityBasedVoteDriveQuery and
  the bounded untrusted decoder.
- SDK: the "already exists" recovery no longer queries contested votes for a
  yes/no vote; DPNS contest pagination continues while pages are full.
- wasm-dpp2 / wasm-sdk: resource-vote paths refuse a yes/no poll; TypeScript
  Vote and VotePoll object/JSON types are unions with the yes/no shapes.
- Tests: CheckTx root-invariance guard on every yes/no vote, shared test
  helpers, no stale InternalError tolerance, error 40308 reached by
  re-funding a finished poll; change-cap wording (votes, first included).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QuantumExplorer and others added 2 commits September 23, 2026 19:58
… share of the total

A yes/no poll's minimum voting power was a fixed number. It is now either:

- `Absolute(voting_power)`, at most `max_yes_no_vote_poll_minimum_voting_power`
  (2000), 0 included; or
- `FractionOfTotal { numerator, denominator, rounding }`: that share of the total
  voting power of the masternode list (masternode 1, evonode 4) in the block that
  closes the poll, rounded up or down, at most
  `max_yes_no_vote_poll_minimum_voting_power_percent_of_total` (50) percent of it.
  1/3 rounded up represents ceil(total / 3).

The closing block sums its masternode list as votes are weighed, resolves the
minimum, and records the resolved bar as `required_voting_power` in the poll's
result, so a finished poll shows what it was judged against. Both caps are new
SystemLimits fields, backfilled into every table.

Wire shape: `"minimumVotingPower": { "absolute": 400 }` or
`{ "fractionOfTotal": { "numerator": 1, "denominator": 3, "rounding": "up" } }`;
the wasm-dpp2 TypeScript types follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nus one

`VotingPowerRounding` gains `DownPlusOne` (floor + 1) and `UpMinusOne` (ceil - 1,
never below zero), appended after `Down` and `Up`. Half the total rounded down
plus one is a strict majority of it: 6 of 10, 5 of 9. JSON names are
`downPlusOne` and `upMinusOne`; the wasm-dpp2 TypeScript type lists all four.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer marked this pull request as draft September 23, 2026 14:57
@QuantumExplorer
QuantumExplorer changed the base branch from v4.2-dev to v4.3-dev September 23, 2026 14:57
@github-actions github-actions Bot removed this from the v4.2.0 milestone Sep 23, 2026
@github-actions github-actions Bot removed the waiting-self-review Waiting for the author to post /self-reviewed label Oct 28, 2026
Conflicts, both sides kept:
- DRIVE_VERSION_V10 (added on both sides): compiled contract storage's
  DRIVE_CONTRACT_METHOD_VERSIONS_V5 next to the yes/no vote V4 and verify V4
  tables.
- DRIVE_ABCI_VALIDATION_VERSIONS_V11: contract update transform_into_action 1
  next to masternode vote transform_into_action 1.
- v15.rs: compiled contract storage keeps note 8; yes/no vote polls become
  note 9.
- nullifier_batch_guard: base's validation hashes; the latest-version root
  and nine settled hashes re-pinned for compiled contracts plus the vote
  decisions trees.

SYSTEM_LIMITS_V5 builds on V4, so it carries the yes/no limits and vote
weights.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — 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.

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

Re-review — Final validation — Phase 1 + Phase 2

The protocol-15 yes/no voting implementation is broadly coherent and the prior consensus, storage, proof, and binding findings are addressed. Four non-blocking compatibility and pagination issues remain: empty end-date pages can still truncate SDK results, an existing JSON wire format changed, a shipped public document-method return type narrowed in place, and the new yes/no state query executes before its proof verifier is active. CI has no reported failures on this head, but the Rust workspace and several test checks are still running or queued.

🟡 4 suggestion(s)

1 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (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); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: tenderdash-consensus-security); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 11: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 12: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 13: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 14: gpt-6.1-sol (agent: phase2-reviewer, role: tenderdash-consensus-security); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This is a large, intricate change that modifies consensus-critical voting rules, protocol-version activation and peer-facing serialization, persistent vote storage and cleanup, state-transition validation, and proofs across rs-dpp, rs-drive, and rs-drive-abci.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — tenderdash-consensus-security (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — tenderdash-consensus-security (completed, effort xhigh); 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-dpp/src/voting/vote_choices/yes_no_abstain_vote_choice/mod.rs`:
- [SUGGESTION] packages/rs-dpp/src/voting/vote_choices/yes_no_abstain_vote_choice/mod.rs:26-35: Preserve the existing JSON wire names for yes/no vote choices
  This change renames the existing enum variants from `YES`, `NO`, and `ABSTAIN` to idiomatic Rust names while retaining the unversioned `serde(rename_all = "camelCase")` representation. As a result, JSON and Value serialization changes from the established `"yES"`, `"nO"`, and `"aBSTAIN"` strings to `"yes"`, `"no"`, and `"abstain"`. The bincode discriminants are unchanged, but existing JSON/Value consumers and persisted representations can no longer round-trip the prior format. Preserve the old serde names, optionally accept the new spellings as aliases, or introduce an explicitly versioned wire representation.

In `packages/rs-dpp/src/data_contract/document_type/methods/mod.rs`:
- [SUGGESTION] packages/rs-dpp/src/data_contract/document_type/methods/mod.rs:1045-1070: Do not narrow the shipped v0 document-method return type in place
  Both `DocumentTypeV0Methods::contested_vote_poll_for_document` and `contested_vote_poll_for_document_properties` change their public return type from `Result<Option<VotePoll>, ProtocolError>` to `Result<Option<ContestedDocumentResourceVotePoll>, ProtocolError>`, while the existing method-version slot remains v0 for all protocol versions. The implementation may return the same concrete poll today, but downstream Rust callers compiled against the public v0 trait API no longer type-check. Keep the v0 signature and add a compatibility-preserving helper or a new versioned method for the concrete return type.

In `packages/rs-drive/src/query/yes_no_vote_poll_state_query.rs`:
- [SUGGESTION] packages/rs-drive/src/query/yes_no_vote_poll_state_query.rs:116-145: Gate yes/no poll state query execution consistently with its proof verifier
  `execute_with_proof` and `execute_no_proof` directly execute the new decisions-branch query for any `PlatformVersion`; unlike `verify_yes_no_vote_poll_state_proof`, neither consults an optional protocol-version method slot or returns `VersionNotActive` before protocol 15. On a pre-15 platform snapshot, the no-proof method can silently return an empty state and the proof method can generate a proof that the inactive verifier will reject. Add a versioned query slot and inactive dispatch, or explicitly reject pre-15 versions at both execution entry points.

In `packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs`:
- [SUGGESTION] packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs:494-502: Do not treat a page consumed entirely by empty end dates as exhaustion
  (existing thread: https://github.com/dashpay/platform/pull/4899#discussion_r4341361723)
  The loop still terminates when the verified page contains no timestamp groups. The underlying GroveDB query counts empty end-date trees toward its limit, while proof reconstruction drops those trees before producing `VotePollsGroupedByTimestamp`, so a page can be empty even though a later timestamp contains a live DPNS contest. Retrying once at `DEFAULT_QUERY_LIMIT` does not establish exhaustion when more empty trees remain, and the proof exposes no authenticated continuation cursor for the skipped trees. The SDK can therefore return a successful but incomplete contest map. Preserve an authenticated last-scanned end-date cursor, retain empty timestamp groups through verification, or return an explicit incomplete-enumeration error instead of breaking.

Comment thread packages/rs-dpp/src/data_contract/document_type/methods/mod.rs
Comment thread packages/rs-drive/src/query/yes_no_vote_poll_state_query.rs
@github-actions

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 and removed waiting-bots Waiting for the review bots to report on this head labels Oct 28, 2026
…n 15

YesNoVotePollStateDriveQuery::execute_with_proof and execute_no_proof read
the decisions branch at any protocol version, while the verifier of their
proof is not active before 15. They now check the new yes/no slot
fetch_yes_no_vote_poll_state (None before 15, Some(0) in
DRIVE_VOTE_METHOD_VERSIONS_V4) and answer VersionNotActive before 15, like
every other yes/no method. The only server caller, the end-of-block tally,
is itself not active before 15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — 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 labels Oct 28, 2026

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

Re-review — Final validation — Phase 1 + Phase 2

The head fixes the previously identified activation, proof-routing, cleanup, decoding, binding, and versioned-policy defects. Three issues remain: the new yes/no poll identity and persisted-key derivation is not selected through a platform-version slot, and the SDK can silently truncate results both for an empty page of end-date trees and for an explicit zero limit. CI has no reported code failure, but policy reconciliation remains pending and the Kotlin SDK job is queued.

🔴 1 blocking | 🟡 3 suggestion(s)

2 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (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); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 11: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 12: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This large, intricate diff changes consensus-critical voting rules, protocol-version activation, peer-facing vote deserialization, persistent vote storage and cleanup, and state proofs in the rs-dpp, rs-drive, and rs-drive-abci implementations.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); 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-dpp/src/voting/vote_polls/yes_no_vote_poll/mod.rs`:
- [BLOCKING] packages/rs-dpp/src/voting/vote_polls/yes_no_vote_poll/mod.rs:413-426: Version the yes/no poll identity and persisted-key derivation
  `sha256_2_hash`, `specialized_balance_id`, and `unique_id` determine the yes/no poll's active-poll key, end-date index key, and prefunded-balance identifier, but they are ordinary unversioned methods. The protocol-15 implementation therefore has no `PlatformVersion`-selected identity generation that can be frozen while a later protocol changes the format; editing this code in place would address different persisted trees and balances during replay. Add an optional DPP voting method slot for the yes/no poll identity and dispatch this generation through the active platform version, preserving it for protocol 15.

In `packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs`:
- [SUGGESTION] packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs:481-482: Preserve the existing zero-limit behavior
  An explicit `limit: Some(0)` now sets `query_limit` to zero, but the empty-page branch treats every limit below `DEFAULT_QUERY_LIMIT` as a retry and changes it to the default. The method can therefore return contests even though the caller requested zero results, whereas the previous implementation returned an empty map. Handle an explicit zero before entering the pagination loop.
- [SUGGESTION] packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs:494-503: Do not treat a page consumed entirely by empty end dates as exhaustion
  (existing thread: https://github.com/dashpay/platform/pull/4899#discussion_r4341361723)
  `VotePollsByEndDateDriveQuery` applies its limit while traversing timestamp trees, including empty trees. The proof verifier then drops those empty elements at `verify_vote_polls_by_end_date_proof_v0` when it evaluates `element?`, so a page can decode to an empty `VotePollsGroupedByTimestamp` even though later timestamps contain live DPNS contests. This loop retries only once at `DEFAULT_QUERY_LIMIT` and then breaks, returning an incomplete map without an error when enough empty trees precede a live contest. Preserve an authenticated continuation marker or empty timestamp keys, or return an explicit incomplete-enumeration error instead of treating the empty decoded page as EOF.

In `packages/rs-dpp/src/data_contract/document_type/methods/mod.rs`:
- [SUGGESTION] packages/rs-dpp/src/data_contract/document_type/methods/mod.rs:1045-1087: Do not narrow the shipped v0 document-method return type in place
  (existing thread: https://github.com/dashpay/platform/pull/4899#discussion_r4400373228)
  Both public `DocumentTypeV0Methods` methods now return `Result<Option<ContestedDocumentResourceVotePoll>, _>` while their existing method-version slot remains v0 for every protocol version. Downstream Rust callers that used the shipped `Result<Option<VotePoll>, _>` API no longer type-check, even though the implementation still produces the same contested poll. The `feat!` marker makes an intentional break possible, but it does not preserve the existing v0 API; either retain the old signatures and add a concrete helper, or document and explicitly version the Rust API migration.

Comment thread packages/rs-dpp/src/voting/vote_polls/yes_no_vote_poll/mod.rs Outdated
Comment thread packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs
@github-actions

Copy link
Copy Markdown
Contributor

Your move: thepastaclaw requested changes on this head; dismiss the review or push a fix; thepastaclaw left review threads unresolved; resolve them.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Oct 28, 2026
QuantumExplorer and others added 2 commits October 28, 2026 22:27
get_current_dpns_contests reads an empty page at a small limit again at
the default one, so an explicit limit of zero listed contests. A zero
limit now answers an empty map without a request, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orm version

A yes/no poll's id keys its trees and its end date index entry and
names its prefunded balance. YesNoVotePoll::unique_id now dispatches on
the dpp voting slot yes_no_vote_poll_id (None before protocol version
15, Some(0) from it), so a later id format is a new generation while 15
keeps addressing what it wrote.

VotePoll::unique_id, VotePoll::specialized_balance_id,
Vote::vote_poll_unique_id, Vote::specialized_balance_id,
ResourceVoteV0::vote_poll_unique_id, ResolvedVote::specialized_balance_id,
the yes/no poll path helpers and IdentityBasedVoteDriveQuery's path query
take the platform version. A contested poll's id does not read it, and
no yes/no poll or vote reaches the shipped generations that now pass it
before 15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — 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 labels Oct 28, 2026
v5.1-dev added protocol version 15 note 9 (token configuration updates on
read-only contracts), so the yes/no vote polls note becomes note 10 and
the table comments that name it follow. DRIVE_ABCI_VALIDATION_VERSIONS_V11
keeps both the batch advanced_structure 2 and the masternode vote
transform_into_action 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — 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.

@QuantumExplorer

Copy link
Copy Markdown
Member Author

Reviewed

@QuantumExplorer
QuantumExplorer merged commit a828103 into v5.1-dev Oct 28, 2026
8 of 9 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/pr-4875-df4cd2 branch October 28, 2026 20:14
QuantumExplorer added a commit that referenced this pull request Oct 28, 2026
…et-escrow-contract

v5.1-dev adds the yes/no vote polls (#4899) and the read-only token config
update refusal (#5632). DRIVE_VERSION_V10 keeps both sides: the
marketplace's methods with the yes/no vote and verify tables. v15 notes 9
and 10 are v5.1-dev's; the generated token decimals become note 11 and the
marketplace state note 12. transition_to_version_15 creates the vote
decisions trees and then the marketplace's escrow trees and contract. The
shielded characterization roots are re-pinned for the merged genesis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QuantumExplorer added a commit that referenced this pull request Oct 28, 2026
#4899 took protocol version 15 note 10, so the single read is note 11.
DRIVE_VERSION_V10 lists all three of its changes: compiled contract
storage, the yes/no poll kind and the consume's single read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Vote polls: yes/no poll kind with supermajority and minimum voting power (moderation teams A2)

2 participants