Repository navigation
feat(platform)!: decision proposals open masternode yes/no polls (PV15) - #5684
Conversation
…nd fee (PV15) Co-Authored-By: Claude Opus 5.5 <[email protected]>
…no poll (PV15) Co-Authored-By: Claude Opus 5.5 <[email protected]>
…docs The @dashevo/decision-proposals-contract workspace (package.json, system ids, README and a unit spec of the proposal schema), the contract in the root, Docker, codecov and CI package lists the moderation charters contract is in, its row in the book's system contracts table, the 0.5 Dash decision proposal fund in the book's contest funds table, and a protocol guide for decision proposals. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…text provider at protocol 15 Behind the decision-proposals-contract feature (and all-system-contracts), the provider loads the decision proposals system contract from protocol version 15 on and reports it absent below, like the app-connect and moderation charters contracts from 14. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…he upgrade to 15 The contract is in a genesis state born at 15 and not at 14, is inserted by transition_to_version_15, is stored byte for byte as a chain born at 15 stores it (no storage flags, same trees), and its registration rolls back and retries to the same root through the upgrade dispatcher. It is one more sibling in the genesis contracts tree from protocol 15, which reshapes that tree and moves the byte-billed contract reads of the fee pins at the latest version: deletion and replacement -47_280, DPNS domain creates +95_920 each, the commit/reveal fixture's contract -45_920, token burn and direct purchase -48_640, the sponsored shielded token payments +45_920, and the single-entry shielded GroveDB root pins. Confirmed by gating the genesis registration off, which restores every old value. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…inimum; restore fee pin at 15 Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Important Review skippedToo many files! This PR contains 110 files, which is 10 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.
|
|
Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-10-30T17:26:32.592Z |
|
📜 System contracts This pull request adds or changes system contract document schemas. The contract viewer diagrams each one, and a comparison marks what is added, changed and removed, with the update rule each change meets.
Compared |
|
🔍 Review in progress — actively reviewing now (commit 5cc4fa4) · triage: critical |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Phase 1 + Phase 2
The decision-proposal feature is generally wired through the correct protocol-versioned layers, but the proposal fund is omitted from principal-balance accounting, allowing underfunded proposals to pass fee validation and fail during execution. The poll-opening conversion also loses batch context, so valid batches with proposals sharing an end date can emit duplicate GroveDB tree inserts; additional activation and mempool-time issues remain. CI is still incomplete, with Rust tests queued and several build jobs in progress.
🔴 1 blocking | 🟡 4 suggestion(s) | 💬 1 nitpick(s)
Review provenance
Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 5: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); 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); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — This is a large, intricate change that modifies consensus-critical protocol-version dispatch, document validation, voting and poll opening, fee-funded balance movement, system-contract persistence, and ABCI state transitions, including files such as packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/ - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-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 verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— security-auditor (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— tenderdash-consensus-security (completed, effort xhigh); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/transformer.rs`:
- [BLOCKING] packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/transformer.rs:103-105: Count the decision-proposal fund in principal-balance accounting
This branch removes a decision proposal's stated fund from `prefunded_voting_balance` and stores it only in `DecisionProposalCreate.stated_fund`. However, `BatchTransitionActionV0::all_conflicting_index_collateral_voting_funds` still sums only `prefunded_voting_balance`, so `all_used_balances()` and the resulting `ExecutionEvent::Paid.removed_balance` omit the 0.5 Dash identity debit emitted later by `document_create_transition/v1`. Fee validation therefore checks only the processing fee: an identity with enough credits for fees but less than the fund plus fees can pass validation, then fail when `RemoveFromIdentityBalance` executes. Include `decision_proposal.stated_fund` in the shared balance aggregate using checked addition, and add fee-estimation/execution parity coverage for an underfunded proposal.
In `packages/rs-drive/src/util/batch/drive_op_batch/vote_poll.rs`:
- [SUGGESTION] packages/rs-drive/src/util/batch/drive_op_batch/vote_poll.rs:43-50: Preserve pending batch operations when opening proposal polls
`VotePollOperationType::into_low_level_drive_operations` creates a fresh operation vector and passes `None` for `previous_batch_operations`. The opener's protocol-15 end-date deduplication explicitly relies on that context to recognize an end-date tree queued by an earlier poll or contest. Consequently, two valid proposal creates with the same `endDate` independently queue the same tree insert; the batch can then be rejected by GroveDB for duplicate writes. The same omission loses the storage flags and deletion-cancellation behavior documented by `open_yes_no_vote_poll_operations`. Pass the accumulated batch context through the conversion pipeline or aggregate poll openings in a batch-aware converter.
In `packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v2/mod.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v2/mod.rs:80-84: Prevent minimum-duration proposals from becoming invalid after mempool admission
The lower end-date bound is computed from `block_info.time_ms`. During `check_tx`, the caller supplies the last committed block's time, while block execution validates against the candidate block's later time. A proposal whose `endDate` is exactly one week after the committed timestamp can therefore pass mempool validation and fail the same structure check when included in the next block. Because this check occurs after identity processing, the transaction can become a paid consensus failure rather than simply being excluded. Use a conservative mempool margin or a stable reference time that guarantees a transaction accepted by `check_tx` remains valid at inclusion.
In `packages/rs-sdk-trusted-context-provider/src/provider.rs`:
- [SUGGESTION] packages/rs-sdk-trusted-context-provider/src/provider.rs:1209-1213: Apply the protocol activation check before returning cached contracts
The protocol-15 guard is reached only after the generic `known_contracts` lookup at lines 1041-1045. If a decision-proposals contract is preloaded through `with_known_contracts`, `add_known_contract`, `add_known_contracts`, or `register_data_contract`, a lookup at protocol 14 returns the cached contract immediately and bypasses this guard. That contradicts the provider's stated behavior that the contract is absent below protocol 15. Apply the activation filter before the generic cache lookup or make cached entries protocol-version aware, and add a regression test that preloads the contract before querying it at protocol 14.
In `packages/rs-platform-version/src/version/system_limits/mod.rs`:
- [SUGGESTION] packages/rs-platform-version/src/version/system_limits/mod.rs:267-270: Update the stale minimum-duration reader reference
The field comment says the limit is read by `YesNoVotePoll::validate_end_date`, but that function does not exist. The actual reader is `YesNoVotePoll::end_date_window`, which is also used by the Drive poll opener. Update the reference so future changes to the duration window find the correct implementation.
In `packages/rs-dpp/src/voting/vote_polls/yes_no_vote_poll/mod.rs`:
- [NITPICK] packages/rs-dpp/src/voting/vote_polls/yes_no_vote_poll/mod.rs:394-398: Document the empty end-date window at extreme timestamps
When `opening_time_ms` exceeds `i64::MAX`, `earliest` can remain above `i64::MAX` while `latest` is clamped to `i64::MAX`, producing an inverted `RangeInclusive`. This is safe for current block times because callers reject every date in that range, but it contradicts the documentation promising that both returned bounds are allowed. Document the empty-window behavior or explicitly handle this edge case.
|
Your move: thepastaclaw requested changes on this head; dismiss the review or push a fix; thepastaclaw left review threads unresolved; resolve them. |
…s and their text stays editable for a day - The rule is `passPercent` (50 to 80: yes must be strictly more than that share of yes plus no) and `minimumTurnoutPercent` (0 to 50 of the network's voting power, rounded up), replacing the supermajority fraction and the two kinds of minimum. The poll stores p/100 and a share minimum rounded up, as before. - `title`, `description` and `url` may be edited for min(1 day, run / 5) after creation (conditional `immutable` entries); the rule never changes; every version is kept (`documentsKeepHistory`). - The decision proposal fund counts in the balance a batch uses (`all_conflicting_index_collateral_voting_funds`), so an owner who cannot pay both the fund and the fees is refused before anything is written. - Review notes: the minimum-duration limit names `end_date_window`, the window documents when it is empty, and the poll-opening conversion says why it needs no earlier batch operations. - Re-pinned the shielded nullifier guard's settled hashes and root (the genesis contract changed) and the valid-payment characterization (the create action gained `decision_proposal`). Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
|
Reviewed |
Basic explanation
What this does: It lets anyone open a decision proposal from protocol version 15. A proposal is a yes/no question put to the masternodes. It is a
proposaldocument of a new system contract,DecisionProposals. Creating it opens a masternode yes/no vote poll with the rule the proposal states. The proposer pays 0.5 Dash, which funds the votes. When the poll closes, its result is recorded. Nothing acts on it: proposals are signaling only.Value:
passPercent(50 to 80; yes must be strictly more than that share of yes plus no, so a tie fails) andminimumTurnoutPercent(0 to 50 of the network's voting power, rounded up). A "passed" proposal always means more yes than no.This is steps 2 and 3 of #5679, in one PR as the owner chose on 2026-10-30, with the two open limits settled the same day: a threshold of at least a simple majority, and at least one week of voting.
Risks:
Issue being fixed or feature implemented
Steps 2 (contract, limits, fee) and 3 (opening on create) of #5679. The poll format they build on is #5680; the client queries that read a proposal's result are #5651.
What was done?
A system contract holds the proposals
DecisionProposalsisSystemDataContract11 (3iNyoNwjCTkb7XS3RHyWUtLx7pgC6AFAEQNNDHzxqngF, owner all zeros). Chains born at 15 register it at genesis (create_genesis_state1, behind>= 15), andtransition_to_version_15inserts it with no storage flags, as genesis stores system contracts. Its one document type,proposal, cannot be deleted. Its rule never changes; its text may be edited for a short window, and every version is kept:{ "title": "Raise the limit?", // 1-128 characters, at most 512 bytes "description": "Yes raises it, no keeps it.", // 1-4096 characters, at most 4096 bytes "url": "https://example.org/discussion", // optional, a URI, at most 512 bytes "passPercent": 50, // 50-80: passes when yes > 50% of yes + no "minimumTurnoutPercent": 10, // 0-50: yes + no must reach 10% of the network, rounded up "endDate": 1767225600000 // ms; one to four weeks after the block }Indexes:
byEndDate,byOwnerandbyCreatedAt.The type is mutable and keeps history (
documentsKeepHistory).passPercent,minimumTurnoutPercentandendDateare listed underimmutable.title,descriptionandurlare conditionalimmutableentries (#5217) that freeze once{ "greaterThan": [{ "subtract": ["$updatedAt", "$createdAt"] }, { "min": [86400000, { "divide": [{ "subtract": ["endDate", "$createdAt"] }, 5] }] }] }holds, that is a day after creation, or a fifth of the run if that is shorter. With a one-week minimum run a fifth is at least 1.4 days, so the day applies today; the fifth takes over if the minimum is ever lowered. An edit after the window, or of the rule at any time, is refused (
DocumentImmutablePropertyChangedError, 40128).The poll stores the rule as it always has: a supermajority of
passPercent / 100and a share minimum ofminimumTurnoutPercent / 100rounded up.Creating a proposal opens its poll
Before (protocol 15, #5680): nothing called
Drive::open_yes_no_vote_poll, so no yes/no poll could exist.After: a
proposalcreate("proposal", 50_000_000_000). The transition builders fill it in for a proposal (prefunded_voting_balance_for_document), as they do for a contested document;DecisionProposalFundMismatchError, 11100), a rule outside the limits (InvalidDecisionProposalRuleError, 11101) and an end date outside one to four weeks after the block (DecisionProposalEndDateOutOfRangeError, 41300). These run in check_tx too, so the mempool refuses what a block would;DriveOperation::VotePollOperation(OpenYesNoVotePoll { vote_poll, end_date })that opens the poll.The poll is read from the proposal by one rs-dpp function,
DecisionProposalVotePoll::from_proposal, which consensus and clients share. Its purpose isDecisionProposal { document_id }and its rule is the proposal's.The opener gained a fee-estimate path, since check_tx and block fee validation convert the create with estimated costs. When estimating, it reads no state and takes the poll as new: a proposal's document id is never reused.
Limits
passPercent)n/d >= 1/2(min_yes_no_vote_poll_supermajority_percent: 50, new,YesNoVotePoll::validate_parametersv0)minimumTurnoutPercent), rounded upmin_yes_no_vote_poll_duration_ms)YesNoVotePoll::end_date_windowv0, shared by validation and the openerFund
VoteResolutionFundFeesgaineddecision_proposal_fund_required_amount: 0 (unread) in the schedules before 15, and 0.5 Dash in the newVOTE_RESOLUTION_FUND_FEES_VERSION3, which the newFEE_VERSION4carries for protocol 15. Platform states store only a fee schedule's number, so no stored format changes. What the votes leave of the fund goes to processing fees when the poll closes, as for DPNS contests.How Has This Been Tested?
New tests:
masternode_vote/yes_no_tests/decision_proposal_tests.rs, 6 tests):passPercentof 49 or 81, aminimumTurnoutPercentof 51 and either one missing are refused; 50/50 and 80/0 pass;getYesNoVotePollStateproves the result.passPercent / 100, a share minimum rounded up), passing strictly above the percent (a tie fails at 50), each rule refusal, not active before 15, the contract and type names pinned to the contract crate's, the builders stating the fund, the edit window's condition at a day and at a fifth of a two-day run, the simple-majority floor (128/255 passes, 127/255 fails) and the end date window, including its cap ati64::MAX.transition_to_version_15inserts it;Updated tests: the opener and yes/no vote tests now end their polls two weeks after the opening block, inside the new window.
should_close_a_contested_poll_and_a_yes_no_poll_that_end_at_the_same_timeruns on a mainnet configuration, whose two-week DPNS contest ends inside that window (a test network's 90 minutes would not). One more contract in a protocol 15 genesis adds a sibling to the contracts tree, which moves some exact fee and root pins at protocol 15. Each one is re-pinned with a comment, after checking that it returns to its old value when the contract is moved to a later genesis branch:Runs (targeted, as usual): dpp (with
decision-proposals-contract, discriminants included) 37, drive 38, drive-abci decision proposals, masternode votes, create validation and document creation 221, contract create and update, fee claims, moderation, genesis and the protocol upgrade 491, the batch document and token modules (all pass after the re-pins), rs-sdk-trusted-context-provider 47. wasm-sdk (wasm32) and dash-sdk build.should_page_with_a_cursor_only_where_the_query_fixes_the_derived_propertyfailed once under parallel load and passes alone on this branch and on the registration-only commit: its replies take their creation time from the wall clock. Other fee or root pins at protocol 15 outside the modules run here may move the same way; CI will show them. Formatted withcargo fmt --all; clippy-clean on platform-version, dpp, drive, drive-abci, data-contracts and decision-proposals-contract with tests, and wasm-dpp for wasm32 (thedata-contractsunused-parameter warning without contract features predates this PR).In-place changes to shipped generations
SYSTEM_LIMITS_V1toV4min_yes_no_vote_poll_duration_msandmin_yes_no_vote_poll_supermajority_percent.YesNoVotePoll::validate_parametersandend_date_window, which areNone(not active) before 15.VOTE_RESOLUTION_FUND_FEES_VERSION1(andV2through it), and the pre-1.4Fromdecision_proposal_fund_required_amount: 0.SYSTEM_DATA_CONTRACT_VERSIONS_V1toV3decision_proposals: 0.VOTING_VERSION_V1,V2decision_proposal_vote_poll: None.DocumentCreateTransitionActiontransformerproposalkeeps its stated fund for validation instead of resolving it as a contested index.prefunded_voting_balance_for_documentv0proposal.DocumentCreateTransitionActionV0decision_proposal: Nonefor every other create.Breaking Changes
Protocol 15 is unreleased, so nothing breaks on a live network.
Drive::open_yes_no_vote_poll_operationstakesestimated_costs_only_with_layer_info.DriveOperationgainedVotePollOperation.VoteResolutionFundFees,SystemLimits,SystemDataContractVersionsandDPPVotingVersionsgained fields.SystemDataContract::ALLhas 12 entries.Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code
PR Hygiene ·
5cc4fa4/skip-botsproceeds without the ones not yet reported/self-reviewedgithub(.github/package-filters/js-packages-direct.yml,.github/package-filters/js-packages-no-workflows.yml,.github/package-filters/js-packages.ymland 6 more) — ktechmidas or shumkovsystem-contracts— you own itdpp— you own itrs-drive-abci— you own itrs-drive— you own itrust-sdk-ffi(packages/rs-sdk-ffi/Cargo.toml) — lklimek or shumkovrust-sdk(packages/rs-sdk/Cargo.toml) — lklimek or shumkovjs-wasm-sdk(packages/wasm-sdk/Cargo.toml) — shumkovWhen every merge requirement is met, the
PR Hygienecheck passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.