Skip to content

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

Draft
QuantumExplorer wants to merge 20 commits into
v4.3-devfrom
claude/pr-4875-df4cd2
Draft

QuantumExplorer wants to merge 20 commits into
v4.3-devfrom
claude/pr-4875-df4cd2

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

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.

TODO

  • Move from protocol version 14 to protocol version 15. This PR now targets v4.3-dev, so everything it gates at 14 moves to 15, once protocol version 15 exists (v4.3-dev only has v14.rs so far):
    • bring the branch onto v4.3-dev (against v4.3-dev the diff also shows v4.2-dev commits it does not have yet);
    • StateTransition::active_version_range: a masternode vote naming a yes/no poll becomes active from 15;
    • version tables: the new slots and generations are selected by the protocol version 15 tables instead of DRIVE_VOTE_METHOD_VERSIONS_V3, DRIVE_ABCI_METHOD_VERSIONS_V10 and DRIVE_ABCI_VALIDATION_VERSIONS_V10;
    • generations that ship with protocol version 14 can no longer take yes/no code in place: the yes/no parts of check_for_ended_vote_polls v1 and masternode vote state v1 (both introduced by #4907), and of clean_up_after_vote_polls_end and the contested clean-up split, need new generations selected at 15;
    • genesis and upgrade: the decisions trees move from create_initial_state_structure v4 and transition_to_version_14 to the protocol version 15 genesis structure and transition_to_version_15;
    • SystemLimits: the four yes/no limits move to the protocol version 15 table and are backfilled in the earlier ones;
    • the "In-place changes to shipped generations" table and the protocol version 13 test move up one version (shipped up to 14, last shipped version 14);
    • append YesNoVotePollNotAvailableForVotingError at the end of StateError again, with its code and the frozen discriminant test, if anything landed after it;
    • update the comments and docs that say protocol version 14, and regenerate grovedb-structure.json.

Session notes, decisions taken and open design questions are in the PR comment "Session notes: where this PR stands".

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 opener also funds the poll's prefunded balance, which pays for every vote.
  2. Vote: a MasternodeVote carrying a YesNoVote puts the masternode's strength (masternode 1, evonode 4, as for contested polls) 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(n): 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 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) and the upgrade to protocol version 14 (transition_to_version_14) both call it, in the same order, so a chain born at 14 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 14 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 14 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 opener funds the poll.
  • One new error. YesNoVotePollNotAvailableForVotingError (code 40308, StateError discriminant 146) 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 14"] --> 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" --> 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 v1"]
    HOOK --> CL
    CL --> IDX["remove the closed polls' end date entries; an end date's tree goes only when nothing is left under it"]
    IDX --> CC["contested clean-up v2: documents, votes, contenders, fund"]
    IDX --> YC["yes/no clean-up: votes, sum trees, identity index entries, the rest of the fund to the processing pool"]
Loading

Why the end date clean-up moved up a level. With two poll kinds under one end date, a clean-up that only knows its own kind can delete an end date's tree that still holds the other kind's entry, and deleting a non-empty tree fails the block. clean_up_after_vote_polls_end v1 therefore removes every closed poll's entry itself, whatever its kind, and the contested clean-up v2 leaves the index alone. The per-block limit spans every due end date, so the last end date a block reaches can be only partly processed. An end date's tree is removed only when every entry it holds is being removed in the same batch, never inferred from how many polls closed. 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.
    • The resource-vote builders (wasm-dpp2 Vote and ResourceVote, wasm-sdk masternodeVote) refuse a yes/no poll.
    • wasm-dpp maps the new error.
  • rs-sdk:
    • get_current_dpns_contests skips yes/no entries and keeps paginating while pages are full.
    • The "already exists" recovery after broadcasting a yes/no vote returns a clear "not supported yet" error instead of querying contested votes.

How a yes/no vote looks on the wire (JSON). A share minimum is written { "fractionOfTotal": { "numerator": 1, "denominator": 3, "rounding": "up" } }:

{
  "$type": "yesNoVote",
  "$formatVersion": "0",
  "votePoll": {
    "resourcePath": ["wcHBwQ=="],
    "supermajorityNumerator": 2,
    "supermajorityDenominator": 3,
    "minimumVotingPower": { "absolute": 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 14 only, and every table is amended in place:

  • DRIVE_VOTE_METHOD_VERSIONS_V3 (new, drive v9).
  • DRIVE_ABCI_METHOD_VERSIONS_V10: check_for_ended_vote_polls 1, clean_up_after_vote_poll_end 1, clean_up_after_contested_resources_vote_poll_end 2, and four new yes/no slots.
  • DRIVE_ABCI_VALIDATION_VERSIONS_V10: masternode vote state 1, transform_into_action 1.

The contender poll change (#4907) merged first and had already introduced check_for_ended_vote_polls v1 and masternode vote state v1. This PR's changes to those methods are folded into the same v1 generations, so protocol version 14 has one v1 of each. New slots are 0 in every earlier table and unreachable there. The shipped generations this change touches are listed in the next section.

In-place changes to shipped generations

Every edit below is unreachable at each protocol version that selects the edited generation, and the edited lines say why. The root is in dpp: a masternode vote that names a yes/no poll (a YesNoVote, or a ResourceVote whose poll is a YesNoVotePoll) is active from protocol version 14 only (StateTransition::active_version_range). Before 14 it is refused at decode, unpaid, as a binary that cannot decode it refuses it, so no v0 validator ever sees one.

Generation Selected at Edit Why consensus cannot change there
masternode vote transform_into_action v0 1 to 13 error arms for Vote::YesNoVote and for a resource vote naming VotePoll::YesNoVotePoll such a vote does not decode before 14
masternode vote validate_state v0 1 to 13 error arms for ResolvedVote::YesNoVote and ResolvedVotePoll::YesNoVotePoll its action comes from transform v0 at the same versions, and the vote does not decode there
Drive::register_identity_vote v0 (both functions) 1 to 13 (DRIVE_VOTE_METHOD_VERSIONS_V1, V2) the same error arms its only caller is the masternode vote drive operation, whose vote cannot name a yes/no poll before 14
check_for_ended_vote_polls v0 (both branches) 1 to 13 error arm for ResolvedVotePoll::YesNoVotePoll a yes/no poll enters the end date index only through Drive::open_yes_no_vote_poll, which writes under Votes/d/p, a tree that exists from 14 only (genesis structure v4, transition_to_version_14)
clean_up_after_vote_polls_end v0 1 to 13 error arm for YesNoVotePollWithVotes only check_for_ended_vote_polls v1 builds that variant, and every table that selects this v0 selects check v0
perform_events_on_first_block_of_protocol_change v0 (v1 runs its body at 13 and 14) 3 to 14 a VotePoll::YesNoVotePoll arm in transition_to_version_8; insert_vote_decisions_trees in transition_to_version_14 the first runs once, on the block entering 8, when no yes/no poll can exist; the second runs only on the block entering 14
prove_state_transition v0 body (v1 reuses it) 1 to 14 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 14 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

Also consensus-neutral at shipped versions: the remove_all_votes_given_by_identities dispatcher now reads its own slot instead of remove_specific_vote_references_given_by_identity; both are 0 in every shipped table, so the untouched v0 still runs there. The new slots in shipped tables (DRIVE_ABCI_METHOD_VERSIONS_V1 to V9, DRIVE_VOTE_METHOD_VERSIONS_V1 and V2, DRIVE_VERIFY_METHOD_VERSIONS_V1, VOTING_VERSION_V1 and V2 (dpp), SYSTEM_LIMITS_V1 to V3) are backfills read only by yes/no code; no existing value changed.

should_run_a_contest_unchanged_and_strip_yes_no_polls_at_protocol_version_13 runs a DPNS contest at protocol version 13 through transform v0, state v0, register_identity_vote v0, check v0 and clean-up v0: the vote counts, the contender is awarded and the end date index empties. The same test shows both vote kinds naming a yes/no poll stripped before any validator runs, with the voter's nonce left unused.

Before and after for the one behaviour change at a shipped version (a new binary at protocol version 13, a ResourceVote naming a yes/no poll):

  • Before: it decoded and reached validation, which refused it as UnpaidConsensusError(PrefundedSpecializedBalanceNotFoundError).
  • After: it is refused at decode as StateTransitionIsNotActiveError (stripped from the block, like every transition not active yet). An older binary strips it too, with SerializedObjectParsingError. Both outcomes keep it out of a proposal and reject a proposal that carries it.

How Has This Been Tested?

Locally, run on the latest head:

  • the rs-dpp voting and state transition tests;

  • the rs-drive votes, query, verify, structure and initialization tests;

  • the rs-drive-abci masternode vote, voting event, processor and protocol upgrade tests, including the existing contested-poll suites;

  • the wasm32 builds of wasm-dpp, wasm-dpp2, wasm-sdk and wasm-drive-verify;

  • the verify-only rs-drive build;

  • clippy and fmt.

  • 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 13 and accepted at 14.
  • 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;
    • an upgraded chain and a chain born at 14 building the same votes Merk;
    • at protocol version 13, a contest running unchanged through the shipped generations and both vote kinds naming a yes/no poll stripped.

Every yes/no vote in the tests goes through CheckTx first. That checks committed state stays untouched, and a vote CheckTx admits must pass at both levels.

Breaking Changes

Consensus: new VotePoll and Vote variants, a new state error, and new genesis trees, all at protocol version 14 (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.

Nothing in this change opens a yes/no poll, so the break starts with the first opener: moderation team challenges or charter amendments, after protocol version 14. 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

@coderabbitai

coderabbitai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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 this PR is a draft.

  • 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 49df27f. 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-09-23T13:34:38.714Z

@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 af350a9f65 with 49df27f9fc. Updated at 2026-09-23T13:31:04.197Z

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

QuantumExplorer and others added 3 commits September 23, 2026 04:23
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… (PV14) (#4923)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…lance (#4887)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@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
QuantumExplorer and others added 10 commits September 23, 2026 05:44
…h or signedness (PV14) (#4925)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
… (PV14) (#4919)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…onstraints, untrusted lists, Swift refusal (#4924)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…4918)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
… (PV14) (#4926)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…4916)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…ties (#4927)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…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 4 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>
…he derive's consumed byte count (#4931)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…(PV14) (#4930)

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
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 modified the milestones: v4.2.0, v4.3.0 Sep 23, 2026
@github-actions github-actions Bot removed the waiting-bots Waiting for the review bots to report on this head label Sep 23, 2026
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Session notes: where this PR stands

A summary of the working session on 2026-09-23, written so the work can be picked up again later. The PR is now a draft against v4.3-dev. Moving it from protocol version 14 to 15 is the TODO at the top of the description.

Status

  • Head 49df27f9fc, draft, base v4.3-dev.
  • The branch is still built on v4.2-dev (last merge af350a9f65, which brought in #4930 and #4931). Against v4.3-dev, the diff also shows v4.2-dev commits that v4.3-dev does not have yet.
  • Everything is gated at protocol version 14 for now. See the TODO.
  • The last CI failures were infrastructure:
    • Swift SDK build: the runner ran out of disk space.
    • Dashmate testnet full node E2E: the test checked the container one second before Core finished shutting down.

What was done in the session

  1. Rebase. The branch carried three merges of v4.2-dev. Its net change was squashed into one commit and replayed onto v4.2-dev. Three conflicts came up, all because other PRs had appended to the same places: the StateError variants, the drive v9 table, and the wasm-dpp error mapping.
  2. Audit of shipped generations. A resource vote naming a yes/no poll was still active at every protocol version. A new binary at protocol version 13 therefore decoded it and ran shipped validation on it, while old binaries cannot decode it at all.
    • It is now inactive before 14, like a yes/no vote.
    • Every edited v0 generation says in a comment why its new arm is unreachable.
    • A protocol version 13 test runs a DPNS contest through the shipped generations and shows the outcome unchanged.
    • The description's "In-place changes to shipped generations" table lists every edit.
  3. Review fixes, from a multi-angle review and from the automated review on this PR:
    • End-date clean-up. An end date's tree is removed only when every entry it holds is removed in the same batch. The block's fetch limit spans every due end date, so the last end date can be only partly processed. Deleting its tree then made GroveDB refuse the batch and failed the block. A regression test puts one poll at T1 and two at T2 and closes them over two blocks.
    • Proof verification. Drive::verify_masternode_vote routes a yes/no vote to the yes/no verifier and refuses a resource vote naming a yes/no poll. Before this, a genuine absence proof taken from the contested index verified a yes/no vote as "not voted".
    • Genesis. Genesis builds the decisions trees with the same helper as the upgrade, insert_vote_decisions_trees.
    • Poll close.
      • Deleting votes no longer copies the pending batch on every delete, which was quadratic in the number of voters.
      • The choice sum trees are deleted without re-reading every vote.
    • Poll parameters.
      • validate_parameters refuses a zero supermajority numerator.
      • Poll ids hash a domain tag first, so the two poll kinds never share an id.
    • Yes/no verifier. It reuses the prover's query 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.
      • The resource-vote builders refuse a yes/no poll.
      • The TypeScript object and JSON types include the yes/no shapes.
    • Tests.
      • Every yes/no vote goes through CheckTx first.
      • There is no stale tolerance for an internal error.
      • Error 40308 is reached by re-funding a finished poll.
  4. Merged v4.2-dev again. #4930 took StateError discriminant 145, so the yes/no error is now 146. Its code, 40308, is unchanged.

How a yes/no vote can start today

It cannot yet. Drive::open_yes_no_vote_poll exists and is tested, but only tests call it.

  • Live once this lands: voting, closing, recording the result, and proofs.
  • Still to come: opening a poll, funding it, and acting on its result. These come with the team challenge and charter amendment features and with the funding work in #4876.
  • No standalone "open a poll" transition, by design: polls open as a side effect of those features' own transitions.
  • Later PR: the client query for a poll's state and the SDK Fetch.

Decisions taken

  • Shared end-date index. Yes/no polls stay in the index that getVotePollsByEndDate serves, so one block-end query and one per-block limit cover both poll kinds.
    • Cost: a client built before this change fails any page that contains a yes/no entry. SDKs, Dash Evo Tool and the mobile clients must upgrade before the first opener ships. This is recorded under Breaking Changes.
    • Alternative not taken: a separate index under the decisions branch.
  • Minimum voting power, set per poll, in one of two forms:
    • Absolute(n): from 0 up to 2000.
    • FractionOfTotal { numerator, denominator, rounding }: that share of the masternode list's total voting power (evonode 4, masternode 1) in the block that closes the poll, at most 50%.
      • The rounding is down, up, downPlusOne (floor + 1) or upMinusOne (ceil - 1, never below 0). Half the total rounded downPlusOne is a strict majority.
      • A separate percentage form was dropped, because the fraction covers it.
    • The poll's result records the bar it was judged against, as required_voting_power.
    • Both caps are SystemLimits fields.
  • Duration: left to whoever opens the poll.
    • open_yes_no_vote_poll takes an absolute end time. The end time is not part of the poll's id and has no bounds.
    • Declined for now: refusing an end time at or before the current block or beyond a maximum, and making the duration part of the poll's rule.
  • Merge approach. Merge after review rather than wait for the features that open polls. The poll's shape freezes when its protocol version ships, so the open questions below should be settled before then.

Open design questions

These are the five decisions I was least sure of: what the PR does, why it is uncertain, the alternative, and which way I lean.

  1. A poll's id is a hash of the whole poll, and every vote carries the whole poll.
    • What the PR does: the id is a double SHA-256 over a domain tag followed by the serialized poll, which includes the resource path, the supermajority and the minimum. A YesNoVote contains the full YesNoVotePoll.
    • Why it is uncertain:
      • Every vote carries up to about 1 KB of resource path plus the rule.
      • A voter must rebuild the poll's exact bytes, so a client that gets one field wrong votes on a poll that does not exist.
      • The format becomes permanent once its protocol version ships.
    • Alternative: hash only the resource path into the id, or let the opener choose the id. The rule goes into the stored info when the poll opens, and a vote carries only the 32-byte id and its answer. The rule still cannot change after opening. What is lost is the vote itself committing to the rule, but the stored rule can be proved.
    • Lean: the alternative. This is the largest wire-format choice in the PR.
  2. The resource path is opaque bytes, and the code that acts on a result will route on those bytes.
    • What the PR does: resource_path is a list of byte segments, and on_yes_no_vote_poll_finished is meant to match byte patterns to find the feature that opened the poll.
    • Why it is uncertain:
      • Two openers with overlapping conventions could misroute a result.
      • A poll that no feature recognizes finishes silently.
      • Nothing at compile time makes a new opener handle its own results.
    • Alternative: a typed purpose enum, for example TeamChallenge { contract_id, round } or CharterAmendment { contract_id, charter_id }, appended per feature and matched exhaustively. Every opener is consensus code anyway, so adding a variant costs little.
    • Lean: typed, decided before the protocol version ships.
  3. Every vote, answer changes included, is paid from one shared fund.
    • What the PR does: each vote takes the DPNS vote price, 0.0001 Dash, from the poll's prefunded balance. Once the fund is empty, further votes are refused.
    • Why it is uncertain:
      • A 0.5 Dash fund pays for 5,000 votes, while around 3,000 masternodes can each vote up to 5 times.
      • A coordinated group changing answers back and forth can drain the fund near the end and lock later voters out.
      • DPNS contests carry the same exposure today.
    • Alternatives:
      • Only a masternode's first vote draws on the fund, and changes are free.
      • One vote per masternode, final.
      • Require the fund to cover every masternode's full allowance when the poll opens.
    • Lean: free changes or no changes. Either one removes the drain.
  4. The code that acts on a result runs inside end-of-block processing.
    • What the PR does: on_yes_no_vote_poll_finished runs synchronously in the closing block's transaction. Seating a challenger or swapping a charter will live there.
    • Why it is uncertain:
      • Any error in a future feature's arm fails that block on every validator, which halts the chain.
      • The work also counts against the block's time budget.
    • Alternative: the closing block only records the decision, which the stored info already does. The feature applies it later in its own transition, for example a claim-seat transition, or on the contract's next moderation action. A failure then refuses that transition instead of halting the chain.
    • Lean: record now, apply later.
  5. A share minimum counts the whole masternode list, PoSe-banned masternodes included.
    • What the PR does: the total is summed over the closing block's full_masternode_list, which is the same list that may vote, and it includes banned masternodes.
    • Why it is uncertain: banned or offline masternodes inflate the total, so "a tenth of the network" is a higher bar than it sounds.
    • Alternative: leave PoSe-banned masternodes out of the total. Also refusing their votes would be consistent, but that changes DPNS voting too.
    • Lean: exclude banned masternodes from the total. It is a small change.

Where to focus a review

  1. Closing at the end of a block. A mistake here fails the block, and this path also changes how DPNS contests close:
    • check_for_ended_vote_polls v1 (finish_yes_no_vote_poll);
    • clean_up_after_vote_polls_end v1;
    • remove_vote_poll_end_date_query_operations v0;
    • the contested clean-up v2, which should be v1 minus the index removal;
    • clean_up_after_yes_no_vote_polls_end v0.
  2. Version gating: StateTransition::active_version_range, and the in-place table in the description.
  3. Genesis versus upgrade: create_initial_state_structure v4 and transition_to_version_14 must call insert_vote_decisions_trees at the same point.
  4. Casting a vote:
    • masternode vote transform_into_action v1 and state v1;
    • register_yes_no_identity_vote v0 (moving strength on a change, counting votes);
    • remove_all_votes_given_by_identities v1.
  5. Formats that freeze when the protocol version ships:
    • YesNoVotePoll: its fields, the minimum's forms and roundings, and the id;
    • the stored info and result;
    • the appended VotePoll and Vote variants, the error code and discriminant, and the Votes/d layout.
  6. Proof verification: the routing in verify_masternode_vote, the yes/no vote verifier, and the poll state proof.

Skim only: the version-table and SystemLimits backfills, the bindings, grovedb-structure.json, and the tests.

Not in this PR

  • Opening and funding polls, and acting on their results.
  • The client query for a poll's state and the SDK Fetch.
  • A JavaScript builder for yes/no votes.
  • A TypeScript conversion fixture. The automated review asked for one; the types were updated, but no fixture was added.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants