Skip to content

feat!: implement Decentralized Masternode Shares DIP - #7437

Open
PastaPastaPasta wants to merge 21 commits into
dashpay:developfrom
PastaPastaPasta:claude/masternode-shares-dip-c40ac2
Open

feat!: implement Decentralized Masternode Shares DIP#7437
PastaPastaPasta wants to merge 21 commits into
dashpay:developfrom
PastaPastaPasta:claude/masternode-shares-dip-c40ac2

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Jul 10, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Implements the Decentralized Masternode Shares DIP
(dashpay/dips#187), which extends
DIP-0003 and the DIP-0026 multi-party payouts introduced by #7340.

DIP-0026 lets the owner reward be split across multiple payout scripts, but its
Security Considerations note that it deliberately leaves the collateral UTXO
under a single key's control: "a share arrangement that requires immutable
payout rights must use additional contractual, wallet, or protocol mechanisms
outside the scope of this DIP." This PR is that protocol mechanism.

It lets 2–8 participants trustlessly co-own one masternode: they fund the
collateral atomically in a single registration, consensus splits the owner
reward across them in proportion to their recorded contributions, and the
collateral can leave the masternode only through a consensus-enforced
dissolution that refunds each participant's principal to a refund script fixed
at registration. No participant, operator, miner, or compromised update path
can redirect another participant's principal, and no participant can be
prevented from exiting.

There is no open issue; this PR tracks the DIP.

What was done?

All behaviour is gated behind DEPLOYMENT_V24 and deploys together with
DIP-0026; it is inert until activation. It extends the extended-address
(version 3) ProTx payload. The version 3 parser shipped in v23.0.x, but no
released chain can contain version 3 data: every tagged release rejects v3
ProTx with bad-protx-version until DEPLOYMENT_V24 activates, and V24 is
NEVER_ACTIVE on mainnet and testnet in all releases. The layout has already
been redefined once on develop (DIP-0026), unreleased, under the same policy;
both changes must ship in the same release.

  • Extended ProRegTx extension: a non-empty shares table turns an extended (v3) registration
    into a shared registration. Each share records an immutable amount,
    refundScript, and ownerKeyID plus an updatable rewardScript; the payload
    also carries one consent signature per share, the early-period length, and the
    penalty. A non-shared extended payload serialises a zero share count.
  • Shared collateral template: the collateral must be an internal output
    paying exactly the 7-byte script 04445348437551
    (0x04 "DSHC" OP_DROP OP_TRUE). Consensus restricts its creation (only as the
    collateral of a valid shared registration, checked for every transaction
    including the coinbase) and its spending (only via a ProDisTx). Pre-activation
    template outputs become permanently unspendable, following the reserved-pattern
    precedent.
  • Consent digest: SharedRegConsentHash binds the exact funding inputs, all
    outputs, the share table, the penalty terms, and the registrar configuration;
    every share owner signs it. Registration is atomic and co-signer txid
    malleability is harmless.
  • Three new special transactions:
    • ProDisTx (10) dissolves a shared masternode, refunding every share to
      its refund script. One signature (unilateral, penalised during the early
      period) or one per share (unanimous, penalty-free); the count is committed
      into the signed digest. Output rules are minimum-based and the required
      penalty is non-increasing in height, so a signed dissolution's validity is
      monotone — the basis for offline "standby dissolutions". The
      transaction fee is capped (1000000 duffs) and a unilateral
      dissolution's bonuses are capped at the configured earlyPenalty,
      bounding what a stolen share owner key can drain from its own
      share at earlyPenalty plus the fee ceiling.
    • ProUpShareTx (11) updates one share's reward script, signed by that
      share owner.
    • ProUpSharedRegTx (12) updates the operator and voting keys with a
      signature from every share owner. A plain ProUpRegTx on a shared masternode
      is invalid.
  • Reward split: the owner reward is split by share amounts (DIP-0026's
    sequential-floor, remainder-to-last convention), paying each share's reward
    script or its refund script.
  • State, mempool, policy, filters: the share table lives in
    CDeterministicMNState (excluded from the SML leaf hash, like DIP-0026 payout
    data) and is carried through extended protx diff output via a memory-only
    CSimplifiedMNListEntry field so share reward changes are visible to
    extended-diff consumers without touching the consensus leaf format; share
    owner keys share the keyIDOwner uniqueness namespace so reuse is rejected in
    both directions; voting-key and share-script separation is enforced at
    registration, on every update (including cross-BLS-encoding operator-key
    uniqueness and same-block interplay, rechecked against the evolving list
    during block processing), and mirrored by mempool pair guards so honest block
    assembly can never pick a combination consensus would reject; mempool conflict
    tracking and eviction cover the new types; the template output/prevout get
    targeted relay carve-outs; and bloom/GCS filters and CMerkleBlock match the
    new transactions and share fields for light clients.
  • RPC: protx register_shared_prepare / shared_sign / shared_combine
    for the multi-party registration and unanimous flows, protx dissolve /
    dissolve_prepare (with standby-dissolution support), protx update_share,
    and protx update_shared_registrar_prepare.

The branch is organised as one reviewable commit per concept.

Deliberate deviations from the original DIP draft, all now reconciled in
dips#187: operatorReward is fixed at registration and dropped from
ProUpSharedRegTx (matching DIP-0003 ProUpRegTx); the non-normative
replace-by-higher-fee relay suggestion is removed (Dash has no replacement);
the signature count is committed into the dissolution digest; a dissolution
takes effect in the collateral-spend phase of list construction (so an update
and a dissolution of the same masternode are valid together in one block in
either order); and lifecycle transactions are filter-matched by proTxHash,
like ProUpRegTx/ProUpRevTx, since they carry no share table. The
revised DIP additionally caps dissolution fees and unilateral penalty
overpayment (implemented here), makes the zeroed-shared-fields rule
for non-shared payloads normative, extends payee-reuse to registrar
voting-key updates, and pins the digest serialization and same-block
validation conventions — all matching this implementation.

How Has This Been Tested?

  • Unit (src/test/evo_sharedmn_tests.cpp, plus updated
    evo_deterministicmns_tests, masternode_payments_tests, bloom_tests):
    template-script matching, the share-list validation matrix, extended payload
    round-trips, consent-digest field coverage, canonical (low-S and canonical
    recovery-header) signature verification including malleation rejection, the
    amount-weighted reward split, MN-state diffs, bidirectional owner-key
    uniqueness, the full ProDisTx validation matrix (penalty floors,
    monotonicity across the early-period boundary, and the signature-count
    malleability guard), fail-fast serialization of a share/join-signature count
    mismatch, CMerkleBlock-level matching of the full shared lifecycle, and
    extended-SML-diff inclusion of share reward changes with an unchanged
    consensus leaf hash.
  • Functional (test/functional/feature_masternode_shares.py): end-to-end
    on regtest — multi-party registration via prepare/sign/combine, reward split
    verified to the duff, template covenant rejections driven through block
    connection (not just policy), reward-script and unanimous registrar updates,
    penalised unilateral dissolution, standby dissolutions valid across the
    early-period boundary, penalty-free unanimous dissolution, and an
    update+dissolution coexisting in one block, rejection of a registrar update
    moving the voting key onto a share's payee script, and both directions of the
    mempool guard that keeps a conflicting share-update/registrar-update pair out
    of one block template. Preflight rejection of invalid registration terms.
  • Regression: feature_masternode_payout_shares,
    feature_dip3_deterministicmns, and feature_asset_locks pass unchanged.
    The full test_dash unit suite passes. Every commit builds individually.
  • The implementation was reviewed in two adversarial multi-agent passes; all
    confirmed findings (a pre-activation consensus-split guard, two txid
    malleability vectors, a miner-abort DoS, and a mempool crash guard) are fixed.

Testing environment: regtest and unit tests on macOS (clang).

Follow-up work

Planned after this PR, deliberately out of scope here:

  • GUI integration: dash-qt now displays shared masternodes (payees,
    ownership); a guided multi-party registration and consent-signing flow (and
    standby-dissolution management) is a natural follow-up once the RPC flow has
    soaked.
  • DIP finalization: land docs: add Decentralized Masternode Shares DIP dips#187 (kept in sync with the deliberate
    deviations listed above) before the release that activates v24.
  • Devnet/testnet dry-run: exercise a combined DIP-0026 + shares activation
    on a fresh devnet, including the documented reset of any pre-release chain
    that activated v24 on an older extended-payload layout.
  • Light clients / Platform: consume the new share fields from extended
    protx diff output and the bloom/GCS matching in mobile SDKs and Platform
    services that track masternode payees.
  • Wallet UX for standby dissolutions: helpers to refresh, store, and share
    standby dissolutions as the required penalty decays.

Breaking Changes

This is a consensus change activated by the DEPLOYMENT_V24 EHF, deployed
together with DIP-0026. Before activation there is no behaviour change: shared
registrations and the new special transactions are invalid, and template
outputs are nonstandard but not consensus-invalid. Because it changes the
layout of the extended (v3) ProRegTx payload — which no released chain can
contain, since V24 is NEVER_ACTIVE on mainnet and testnet in every release —
it must ship in the same release as DIP-0026 (#7340); any pre-release
devnet/regtest chain that already activated v24 on an older extended format
needs a reset.

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 made corresponding changes to the documentation (doc/release-notes-7437.md)
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@PastaPastaPasta
PastaPastaPasta force-pushed the claude/masternode-shares-dip-c40ac2 branch from 16421b7 to a675299 Compare July 10, 2026 00:59
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

Potential PR merge conflicts

This is advisory only. It does not block CI, but it marks PRs that will likely need a rebase depending on merge order.

If this PR merges first

These open PRs will likely need a rebase:

If these PRs merge first

This PR will likely need a rebase:

@thepastaclaw

thepastaclaw commented Jul 10, 2026

Copy link
Copy Markdown

✅ Final review complete — no blockers (commit 27cb59c)

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This change implements decentralized masternode shares for v24. It adds shared collateral registration, share-aware deterministic state, dissolution and update transactions, participant signatures, consensus and mempool enforcement, RPC workflows, proportional rewards, filtering, JSON serialization, and unit and functional tests. Shared collateral rules activate only after v24 activation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WalletRPC
  participant SharedTransaction
  participant Validator
  participant DeterministicMNState
  WalletRPC->>SharedTransaction: prepare and sign shared transaction
  SharedTransaction->>Validator: submit transaction
  Validator->>DeterministicMNState: apply registration or update
  DeterministicMNState-->>Validator: return updated shared state
Loading

Suggested reviewers: knst, udjinm6

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the implementation of the Decentralized Masternode Shares DIP.
Description check ✅ Passed The description directly explains the shared masternode feature, consensus changes, RPCs, testing, and deployment requirements.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 16421b7d78

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +36 to +38
TRANSACTION_PROVIDER_DISSOLVE = 10,
TRANSACTION_PROVIDER_UPDATE_SHARE = 11,
TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR = 12,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add new ProTx types to merkle-block filtering

After adding these transaction types, CMerkleBlock still gates bloom matching through the hard-coded allowedTxTypes set in src/merkleblock.cpp, and it only calls filter->IsRelevantAndUpdate when the type is present there. Because these three IDs are absent from that allowlist, BIP37/SPV clients will not receive matched ProDisTx/ProUpShareTx/ProUpSharedRegTx transactions even though the bloom and compact-filter code now extracts their proTxHash and share fields. Add the new types to the merkle-block allowlist as part of introducing them.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed at the new head: TRANSACTION_PROVIDER_DISSOLVE/UPDATE_SHARE/UPDATE_SHARED_REGISTRAR are now in the CMerkleBlock allowlist (folded into f021946), and the filter unit test now drives a block containing the full lifecycle through CMerkleBlock and asserts all four transactions match (b8a4dad), covering the outer gate that the direct CBloomFilter checks bypassed.


🤖 Posted autonomously by Claude on behalf of pasta.

Comment thread src/evo/specialtxman.cpp
Comment on lines +1528 to +1530
if (dest == CTxDestination(PKHash(mnState.keyIDVoting))) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-proupshare-payee-reuse");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate share updates against in-block voting changes

In block validation this comparison uses mnState from dmnman.GetListForBlock(pindexPrev), but RebuildListFromBlock later applies ProUpSharedRegTx and ProUpShareTx from the same block without rerunning the payee-reuse rule against the evolving list. A block can therefore change the shared MN voting key to X and include a share update setting scriptReward to X; each transaction passes against the previous voting key, leaving a state that registration and ordinary ProUpShareTx validation are meant to reject. Validate share reward updates against the in-block/evolving state, or recheck the invariant after registrar updates.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed at the new head: RebuildListFromBlock now rechecks the voting/payee separation invariant against the list as rebuilt so far after applying each ProUpShareTx and ProUpSharedRegTx, so a same-block update pair that individually passes against pindexPrev can no longer leave the forbidden combination (folded into 25a6fcb). A matching mempool pair guard keeps an honest miner from assembling such a block in the first place (e9e4438).


🤖 Posted autonomously by Claude on behalf of pasta.

Comment thread src/evo/specialtxman.cpp
Comment on lines +1574 to +1576
// A shared registrar update requires unanimity: one signature per share, in share order
if (opt_ptx->vchSigs.size() != mnState.shares.size()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-proupsharedreg-sig-count");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recheck existing share scripts for new voting keys

A shared registrar update can set keyIDVoting to an address already used by an existing share refund or reward script because this path checks signature count and operator uniqueness but never reapplies the share payee-reuse rule to the current share table. With all share owners signing such an update, the masternode reaches a state that could not be registered and that ProUpShareTx would normally reject for new reward scripts. Reject keyIDVoting values that match any current share scriptRefund or RewardScript().

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed at the new head: CheckProUpSharedRegTx now rejects a new voting key whose P2PKH destination matches any share's refund or effective reward script (bad-proupsharedreg-payee-reuse), via the new IsShareListVotingKeySafe helper that mirrors the registration-time rule (folded into 25a6fcb; functional coverage in fec8b6a).


🤖 Posted autonomously by Claude on behalf of pasta.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/messagesigner.h (1)

36-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the recovery-header constraint.

The implementation also rejects compact headers outside 27..34; omitting this makes the stated non-malleability contract incomplete.

Proposed documentation update
-    /// Verify the hash signature and additionally require the exact 65-byte size and a low-S value,
-    /// making the signature bytes non-malleable by third parties. Returns true if successful.
+    /// Verify the hash signature and additionally require the exact 65-byte size, a canonical
+    /// recovery header (27..34), and a low-S value, making the signature bytes non-malleable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/messagesigner.h` around lines 36 - 38, Update the documentation for
VerifyHashCanonical to explicitly state that compact recovery headers must be
within the 27..34 range, alongside the existing 65-byte size and low-S
requirements.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/evo/core_write.cpp`:
- Around line 281-285: Update the serializers around IsShared() in
src/evo/core_write.cpp and the corresponding RPC help: emit ownerAddress only
for non-shared records, omit it entirely for shared records whose keyIDOwner is
null, and mark the field optional in the RPC documentation.

In `@src/policy/policy.cpp`:
- Around line 114-120: The ProRegTx policy exception is too broad because it
accepts any shared-collateral script. In the conditional using
`sharedcollateral::IsSharedCollateralScript`, decode the shared-collateral
payload and continue only when `IsShared()` is true, the output is internal
collateral, and `collateralOutpoint.n` equals the current output index;
otherwise apply normal policy checks.

In `@src/rpc/evo.cpp`:
- Around line 2609-2610: Make the `shared_combine` RPC available when wallets
are disabled by moving its wallet-independent registration and dissolution
combining paths out of `GetWalletEvoRPCCommands` into the globally registered
EVO RPC commands. Keep only the shared-registrar funding-input signing path
wallet-dependent, and update all related command registrations and help text so
wallet-free workflows can complete.
- Around line 1738-1740: Update the RPC result schema for SignAndSendSpecialTx
to document both outcomes of the submit parameter: identify the result as a
transaction ID when submit is true and signed transaction hex when submit is
false, using the appropriate conditional/result description supported by the RPC
schema.

---

Nitpick comments:
In `@src/messagesigner.h`:
- Around line 36-38: Update the documentation for VerifyHashCanonical to
explicitly state that compact recovery headers must be within the 27..34 range,
alongside the existing 65-byte size and low-S requirements.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6308fa88-db54-493e-97d1-94e0cfdee437

📥 Commits

Reviewing files that changed from the base of the PR and between 2bbf4a4 and a675299.

📒 Files selected for processing (32)
  • doc/release-notes-7437.md
  • src/Makefile.am
  • src/Makefile.test.include
  • src/common/bloom.cpp
  • src/core_write.cpp
  • src/evo/assetlocktx.cpp
  • src/evo/core_write.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/dmnstate.cpp
  • src/evo/dmnstate.h
  • src/evo/providertx.cpp
  • src/evo/providertx.h
  • src/evo/providertx_util.cpp
  • src/evo/sharedcollateral.h
  • src/evo/specialtx.cpp
  • src/evo/specialtx.h
  • src/evo/specialtx_filter.cpp
  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
  • src/masternode/payments.cpp
  • src/messagesigner.cpp
  • src/messagesigner.h
  • src/policy/policy.cpp
  • src/primitives/transaction.h
  • src/rpc/client.cpp
  • src/rpc/evo.cpp
  • src/rpc/rawtransaction.cpp
  • src/test/evo_sharedmn_tests.cpp
  • src/txmempool.cpp
  • src/validation.cpp
  • test/functional/feature_masternode_shares.py
  • test/functional/test_runner.py

Comment thread src/evo/core_write.cpp Outdated
Comment thread src/policy/policy.cpp Outdated
Comment thread src/rpc/evo.cpp Outdated
Comment thread src/rpc/evo.cpp

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

No blocking consensus issues at head a675299. The Decentralized Masternode Shares DIP implementation is coherent: v4 ProRegTx shared payload, template collateral covenant, three new special tx types (ProDisTx / ProUpShareTx / ProUpSharedRegTx), amount-weighted reward split, mempool conflict tracking, filter coverage, and RPC surface all line up with the DIP. Previously-fixed items (sig-count commit in the ProDisTx digest, deferred dissolution in the collateral-spend phase, and the removeProTxKeyChangedConflicts end() guard) are present. The only in-scope observations are two commit-hygiene suggestions: two of the tail commits fix consensus-safety / miner-liveness defects introduced earlier in the same PR and should be squashed so develop history does not carry the intermediate broken states.

Source: reviewers — opus (general, dash-core-commit-history; completed), gpt-5.5 (general, dash-core-commit-history; failed/unparseable). Verifier — opus.

🟡 2 suggestion(s)

The two findings are commit-history suggestions anchored to commits rather than changed lines; full details are included below.

🤖 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 `<commit:7f6f8834e9c>`:
- [SUGGESTION] <commit:7f6f8834e9c>:1: Squash the ProDisTx signing-digest fix into the commit that introduced CProDisTx
  Commit 7f6f8834e9c ('evo: commit the signature count into the ProDisTx signing digest') patches a txid-malleability / consensus-safety defect in code introduced by 2eb1ec2f7ac ('evo: shared masternode consensus rules, state, and special transactions'). CProDisTx::MakeSignHash was added in 2eb1ec2f7ac and only briefly existed without committing the signature count before being corrected here. Because Dash merges without squashing, leaving these as separate commits permanently records an intermediate state on develop where a signed unanimous ProDisTx can be malleated to a byte-identical unilateral variant — the exact hazard the fix commit's own body identifies. Even though v24 gating keeps the defect inert on-chain, a permanent bisect trap on a consensus-critical signing digest is precisely what the atomic-commit hygiene guideline is meant to prevent. Please fixup 7f6f8834e9c into 2eb1ec2f7ac and fold the rationale from its message into the target commit's body.

In `<commit:2c78c1b1397>`:
- [SUGGESTION] <commit:2c78c1b1397>:1: Squash the deferred-dissolution ordering fix into the shared-masternode consensus commit
  Commit 2c78c1b1397 ('evo: apply shared masternode dissolution in the collateral-spend phase') corrects a miner-liveness DoS in RebuildListFromBlock introduced by 2eb1ec2f7ac. The message itself frames it as a fix to code introduced earlier in the same PR: previously the removal ran mid-loop, so a ProDisTx ordered before a same-masternode ProUpShareTx / ProUpSharedRegTx aborted block-template production. Keeping the fix as a distinct commit permanently records a broken ordering in the shared-masternode dissolution path in develop's history — a bisect hazard on the consensus-critical list rebuild that any participant of a shared masternode could otherwise trigger. Please fixup 2c78c1b1397 into 2eb1ec2f7ac; the new functional case that mines a pending dissolution with a same-masternode update can either fold into 62e7eb7aafa or stay with the consensus commit.

@PastaPastaPasta
PastaPastaPasta force-pushed the claude/masternode-shares-dip-c40ac2 branch from a675299 to a55b9f9 Compare July 10, 2026 02:17

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a55b9f9091

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/evo/dmnstate.h
std::shared_ptr<NetInfoInterface> netInfo{nullptr};
CScript scriptPayout;
MasternodePayoutShares payouts;
CollateralShares shares; // non-empty = shared masternode (joinSigs are not kept in state)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include share keys in wallet ownership checks

Adding shared masternode state here makes keyIDOwner null and leaves payouts empty for these entries, but the wallet filter in protx list wallet still only checks collateral, keyIDOwner, voting, legacy payouts, and operator payout (src/rpc/evo.cpp:2166-2170). A participant wallet that holds only a share owner key or refund/reward script will therefore not see its shared masternode in wallet-scoped listings or ownership metadata; please scan shares for owner/refund/reward ownership when IsShared() is true.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was largely addressed by the rpc, gui commit (CheckWalletOwnsAnyOwnerKey / CheckWalletOwnsAnyPayout already match share owner keys and effective reward scripts in protx list wallet). The one residual gap — a share's refund script going unmatched when a distinct reward script is set — is fixed at the new head with CheckWalletOwnsAnyShareRefund (b5ce7e3), since the non-shared analog (the collateral UTXO via ListProTxCoins) cannot see the shared covenant output.


🤖 Posted autonomously by Claude on behalf of pasta.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

Cumulative review at head a55b9f909120ac049010f26dcbd6cfb46b1371b1 finds no consensus or correctness blockers. The delta from a6752995 is a small wallet-gating cleanup in src/rpc/evo.cpp: it moves shared-masternode RPC helpers under ENABLE_WALLET, fixes two ParseBLSPubKey argument-name typos, and registers the two shared-prepare RPCs with the wallet commands. The complete current-head stack was also re-reviewed.

Source: reviewers — opus (general, dash-core-commit-history; completed), gpt-5.5 (general, dash-core-commit-history; failed/unparseable due revoked ACP credential). Verifier — opus.

Prior finding reconciliation

  • STILL VALID: the prior ProDisTx signing-digest squash suggestion. The rebase rewrote 7f6f8834e9c to 5495e02f04f, but it remains a separate corrective commit for consensus code introduced by c7a7a408bdb.
  • STILL VALID: the prior deferred-dissolution ordering squash suggestion. The rebase rewrote 2c78c1b1397 to 4ac7f9b6d57, but it remains a separate corrective commit for the same earlier consensus commit.

New findings in the latest delta

None.

Additional cumulative findings

None.

🟡 2 carried-forward suggestion(s)

🤖 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 `<commit:5495e02f04f>`:
- [SUGGESTION] <commit:5495e02f04f>:1: Squash the ProDisTx signing-digest fix into the shared-masternode consensus commit
  Commit 5495e02f04f ('evo: commit the signature count into the ProDisTx signing digest') patches a txid-malleability / consensus-safety defect in CProDisTx::MakeSignHash — code introduced earlier in the same PR by c7a7a408bdb ('evo: shared masternode consensus rules, state, and special transactions'). Without the signature count in the digest, a signed unanimous ProDisTx has outputs byte-identical to a valid unilateral variant and can be malleated to a different txid, invalidating pre-signed refund spends or actor-output CPFP children. Because Dash merges without squashing, keeping this as a separate tail commit permanently records an intermediate state on develop where the consensus-critical signing digest is known-broken. Please fixup 5495e02f04f into c7a7a408bdb and fold the rationale from its message into the consensus commit's body; the test changes can move with it or into the tests commit 8911c80c103.

In `<commit:4ac7f9b6d57>`:
- [SUGGESTION] <commit:4ac7f9b6d57>:1: Squash the deferred-dissolution ordering fix into the shared-masternode consensus commit
  Commit 4ac7f9b6d57 ('evo: apply shared masternode dissolution in the collateral-spend phase') corrects a miner-liveness DoS in RebuildListFromBlock introduced earlier in the same PR by c7a7a408bdb. Previously, a ProDisTx ordered before a same-masternode ProUpShareTx / ProUpSharedRegTx removed the masternode before the later update was processed, failing BuildNewListFromBlock and aborting block-template production. Leaving this as a separate tail commit permanently records the broken ordering on the consensus-critical list-rebuild path. Please fixup 4ac7f9b6d57 into c7a7a408bdb; the functional regression case can move into tests commit 8911c80c103 or stay with the consensus commit.

@github-actions

Copy link
Copy Markdown

This pull request has conflicts, please rebase.

@PastaPastaPasta
PastaPastaPasta force-pushed the claude/masternode-shares-dip-c40ac2 branch from a55b9f9 to bdc584d Compare August 1, 2026 17:34

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bdc584d2c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/evo/dmnstate.h
std::shared_ptr<NetInfoInterface> netInfo{nullptr};
CScript scriptPayout;
MasternodePayoutShares payouts;
CollateralShares shares; // non-empty = shared masternode (joinSigs are not kept in state)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle shared payees when choosing update-service fees

When a shared masternode has no operator payout script (for example, operatorReward is zero) and protx update_service is called without feeSourceAddress, the existing fallback in src/rpc/evo.cpp:1293-1294 calls GetOwnerPayouts(*dmn->pdmnState).front(). Shared states deliberately leave payouts empty and store payees in shares, so this dereferences an empty vector and can crash the node instead of creating the update. Make that fallback select a share reward/refund script or return a validation error requiring an explicit fee source.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in the rpc, gui commit (now b5ce7e3): with no operator payout script and no feeSourceAddress, protx update_service on a shared masternode throws masternode has no default fee source; specify feeSourceAddress instead of calling front() on the empty payout list. We deliberately do not fall back to share reward scripts: those belong to the participants, not the operator issuing the update, so there is no sensible default fee source. Covered in feature_masternode_shares.py.


🤖 Posted autonomously by Claude on behalf of pasta.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
src/rpc/evo.cpp (1)

1728-1731: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the submit=false result for protx update_share.

SignAndSendSpecialTx returns the signed transaction hex when submit is false, but the schema declares only txid. This repeats the result-schema concern raised in the previous review.

Proposed RPC result schema
-        RPCResult{RPCResult::Type::STR_HEX, "txid", "The transaction id"},
+        {
+            RPCResult{"if \"submit\" is not set or set to true",
+                RPCResult::Type::STR_HEX, "txid", "The transaction id"},
+            RPCResult{"if \"submit\" is set to false",
+                RPCResult::Type::STR_HEX, "hex", "The serialized signed ProUpShareTx in hex format"},
+        },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/evo.cpp` around lines 1728 - 1731, Update the RPCResult schema for
the protx update_share handler to document both outcomes of the submit argument:
the transaction ID when submitted and the signed transaction hex when submit is
false. Keep the existing submit parameter and RPC example unchanged, and use the
established result-schema conventions for representing alternative return
values.
🧹 Nitpick comments (1)
src/evo/providertx.cpp (1)

428-446: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Commit the input and output counts, or reuse the existing hash helpers.

The writer concatenates all prevouts, then all sequences, then all outputs, with no element counts. The section boundaries are therefore implicit. Two transactions with different input counts can in principle produce the same byte string, because a shifted boundary can be absorbed by the following variable-length output data. CProRegTx::MakeSharedRegConsentHash avoids this by delegating to CalcTxInputsHash and CalcTxOutputsHash. Use the same helpers here, or write tx.vin.size() and tx.vout.size() before each section. This also removes the hand-rolled loops.

♻️ Proposed change
     hw << tx.nLockTime;
-    for (const auto& in : tx.vin) {
-        hw << in.prevout;
-    }
-    for (const auto& in : tx.vin) {
-        hw << in.nSequence;
-    }
-    for (const auto& out : tx.vout) {
-        hw << out;
-    }
+    hw << static_cast<uint32_t>(tx.vin.size());
+    for (const auto& in : tx.vin) {
+        hw << in.prevout;
+    }
+    for (const auto& in : tx.vin) {
+        hw << in.nSequence;
+    }
+    hw << static_cast<uint32_t>(tx.vout.size());
+    for (const auto& out : tx.vout) {
+        hw << out;
+    }
     hw << proTxHash;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/evo/providertx.cpp` around lines 428 - 446, Update the shared MN dissolve
hash construction around the visible CHashWriter flow to reuse the existing
CalcTxInputsHash and CalcTxOutputsHash helpers, matching
CProRegTx::MakeSharedRegConsentHash, instead of manually serializing vin and
vout in separate loops. Preserve the existing hash fields and ordering while
replacing the hand-rolled input/output serialization with the helper results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/evo/providertx.cpp`:
- Around line 260-289: Update CProRegTx::MakeSharedRegConsentHash to serialize
collateralOutpoint into the consent digest, preserving the existing field
ordering and hashing all of the outpoint including its payload index. Do not
alter the shared-registration validation or require a non-null hash; the index
must be bound while the existing null-hash requirement remains unchanged.

In `@src/evo/providertx.h`:
- Around line 188-199: Update the serialization write path around shares_count
to validate that obj.vchJoinSigs.size() equals obj.shares.size() before emitting
the count or signature data. Fail fast using the existing project convention for
serialization validation, while leaving the read-side resizing and iteration
behavior unchanged.

In `@src/rpc/evo.cpp`:
- Around line 2918-2924: Update the RPC command registration so
protx_shared_combine and protx_dissolve_prepare are available through the
non-wallet command set, not only GetWalletEvoRPCCommands. Preserve their
existing wallet registrations only if needed for wallet-enabled builds, and
ensure wallet-free nodes can complete the documented shared workflows.
- Around line 1796-1798: Update the help text describing ProUpSharedRegTx in the
surrounding RPC help definition to state that wallet fee inputs are added but
not signed, and that signing occurs later during protx shared_combine; preserve
the existing instructions for share-owner signing and combining.
- Around line 1924-1929: Resize opt_ptx->vchJoinSigs to at least
opt_ptx->shares.size() before the loop that processes sigs, while preserving the
existing share-index bounds validation and assignments.

---

Duplicate comments:
In `@src/rpc/evo.cpp`:
- Around line 1728-1731: Update the RPCResult schema for the protx update_share
handler to document both outcomes of the submit argument: the transaction ID
when submitted and the signed transaction hex when submit is false. Keep the
existing submit parameter and RPC example unchanged, and use the established
result-schema conventions for representing alternative return values.

---

Nitpick comments:
In `@src/evo/providertx.cpp`:
- Around line 428-446: Update the shared MN dissolve hash construction around
the visible CHashWriter flow to reuse the existing CalcTxInputsHash and
CalcTxOutputsHash helpers, matching CProRegTx::MakeSharedRegConsentHash, instead
of manually serializing vin and vout in separate loops. Preserve the existing
hash fields and ordering while replacing the hand-rolled input/output
serialization with the helper results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6416075-1bd0-4da6-a462-1cc3ad062aa9

📥 Commits

Reviewing files that changed from the base of the PR and between a55b9f9 and bdc584d.

📒 Files selected for processing (28)
  • doc/release-notes-7437.md
  • src/Makefile.am
  • src/Makefile.test.include
  • src/common/bloom.cpp
  • src/core_write.cpp
  • src/evo/assetlocktx.cpp
  • src/evo/core_write.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/dmnstate.cpp
  • src/evo/dmnstate.h
  • src/evo/providertx.cpp
  • src/evo/providertx.h
  • src/evo/providertx_util.cpp
  • src/evo/sharedcollateral.h
  • src/evo/specialtx.cpp
  • src/evo/specialtx.h
  • src/evo/specialtx_filter.cpp
  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
  • src/masternode/payments.cpp
  • src/messagesigner.cpp
  • src/messagesigner.h
  • src/policy/policy.cpp
  • src/primitives/transaction.h
  • src/rpc/client.cpp
  • src/rpc/evo.cpp
  • src/rpc/rawtransaction.cpp
  • src/test/evo_sharedmn_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (19)
  • src/primitives/transaction.h
  • src/rpc/rawtransaction.cpp
  • src/evo/assetlocktx.cpp
  • src/Makefile.am
  • src/messagesigner.h
  • src/evo/sharedcollateral.h
  • src/masternode/payments.cpp
  • src/Makefile.test.include
  • src/evo/specialtx.h
  • src/core_write.cpp
  • src/rpc/client.cpp
  • src/evo/dmnstate.cpp
  • src/common/bloom.cpp
  • src/policy/policy.cpp
  • src/evo/specialtxman.h
  • src/evo/core_write.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/dmnstate.h
  • src/evo/specialtxman.cpp

Comment thread src/evo/providertx.cpp
Comment thread src/evo/providertx.h
Comment thread src/rpc/evo.cpp Outdated
Comment thread src/rpc/evo.cpp
Comment thread src/rpc/evo.cpp
@PastaPastaPasta
PastaPastaPasta force-pushed the claude/masternode-shares-dip-c40ac2 branch from bdc584d to fe3d5c0 Compare August 1, 2026 17:54

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe3d5c016c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +171 to +174
if (dmnPayee->pdmnState->IsShared()) {
// Shared masternodes split the owner reward by recorded collateral contribution, paying
// each share's reward script (or its refund script when no reward script is set)
const auto& shares = dmnPayee->pdmnState->shares;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expose share reward scripts to masternode RPCs

When the selected payee is shared, rewards now go to shares[i].RewardScript(), but the existing reporting paths still enumerate only GetOwnerPayouts(state), which is empty for shared states. Consequently, masternode winners omits the actual payees and masternode list payee reports UNKNOWN for every shared masternode (src/rpc/masternode.cpp:232 and :268-279), also breaking address-based filtering of those results. Update these consumers to enumerate the share reward scripts when IsShared() is true.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was addressed by the rpc, gui commit (now b5ce7e3) via CDeterministicMNState::GetOwnerRewardScripts(): masternode winners, the masternodelist payee modes, the GUI list, and protx list/info wallet attribution all report each share's effective reward script for shared masternodes. masternode payments was already correct, since it derives payees from the consensus payment computation (GetBlockTxOuts).


🤖 Posted autonomously by Claude on behalf of pasta.

@PastaPastaPasta
PastaPastaPasta force-pushed the claude/masternode-shares-dip-c40ac2 branch from fe3d5c0 to 7773601 Compare August 1, 2026 18:16

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7773601dd5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/rpc/evo.cpp
Comment on lines +1761 to +1764
if (!IsValidDestination(rewardDest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid reward address: %s", request.params[2].get_str()));
}
ptx.scriptReward = GetScriptForDestination(rewardDest);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject unsupported reward destinations before funding

When rewardAddress is a valid destination type other than P2PKH/P2SH, such as a witness address, this check accepts it and the RPC proceeds to fund and sign the transaction, but CProUpShareTx::IsTriviallyValid later rejects the generated script via IsValidPayoutScript. Consequently, protx update_share fails after transaction construction for an address the RPC explicitly accepted; validate the destination against the same P2PKH/P2SH restriction before calling FundSpecialTx.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not reachable on Dash: CTxDestination has no witness variants (std::variant<CNoDestination, PKHash, ScriptHash>, script/standard.h) and DecodeDestination only produces PKHash/ScriptHash — bech32m strings are DIP-18 Platform addresses and decode to CNoDestination. Any address passing the RPC's IsValidDestination check therefore maps to exactly a P2PKH/P2SH script, which is precisely the set CProUpShareTx::IsTriviallyValid accepts (the shared-collateral template it additionally rejects is not expressible as an address). The RPC preflight is already equivalent to the consensus rule, so the claimed late-failure path has no realizable input.


🤖 Posted autonomously by Claude on behalf of pasta.

Comment thread src/evo/dmnstate.h
std::shared_ptr<NetInfoInterface> netInfo{nullptr};
CScript scriptPayout;
MasternodePayoutShares payouts;
CollateralShares shares; // non-empty = shared masternode (joinSigs are not kept in state)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Carry shared payees through extended masternode diffs

Once this field makes a state shared, its legacy payouts vector is empty, but CDeterministicMN::to_sml_entry() still copies only GetOwnerPayouts(state) and BuildSimplifiedDiff(..., extended=true) compares only scriptPayout, payouts, and the operator payout. Thus protx diff ... true never exposes a shared masternode's actual reward scripts, and a range containing only a ProUpShareTx can omit that masternode from the diff entirely because none of the compared fields changed. Extend the simplified extended entry, comparison, and JSON output to carry the share reward data.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed at the new head: CSimplifiedMNListEntry gained a memory-only shares field (excluded from network serialization, operator==, and CalcHash, exactly like the existing mem-only DIP-0026 payouts), populated by to_sml_entry(), compared in BuildSimplifiedDiff's extended path, and emitted in extended JSON via ShareListToJson — so a ProUpShareTx-only range now appears in protx diff ... true and new shared entries expose their share data, while the consensus SML leaf format is unchanged (folded into 25a6fcb; unit test proving hash-invariance and extended-diff inclusion in fec8b6a).


🤖 Posted autonomously by Claude on behalf of pasta.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/evo/providertx_util.cpp (1)

104-121: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a guard for a zero weight total.

base_uint::operator/= throws uint_error on a zero divisor. If shares is non-empty and every amount is zero, line 119 divides by zero and throws inside the payment path. Consensus validation of the share list should prevent this state, so this is defensive only.

♻️ Optional guard
     std::vector<CAmount> ret;
     ret.reserve(shares.size());
+    if (weight_total <= 0) return std::vector<CAmount>(shares.size(), CAmount{0});
     CAmount paid{0};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/evo/providertx_util.cpp` around lines 104 - 121, Guard the payout
calculation in the share-processing loop against a zero weight_total before the
arith_uint256 division, preserving the existing behavior for valid nonzero
totals and the final-share remainder calculation.
src/evo/specialtxman.cpp (1)

583-587: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

check_sigs is hardcoded during list rebuild.

ProcessSpecialTxsInBlock passes fCheckCbTxMerkleRoots as check_sigs into CheckSpecialTxInner (line 821-822), so signature verification is skipped on paths where the caller opts out. This call always verifies dissolution signatures. Each dissolution then costs one to eight ECDSA public-key recoveries per rebuild, including reindex and diff reconstruction, and repeats work already done by CheckProDisTx.

Consider threading the caller's check_sigs value into RebuildListFromBlock and passing it here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/evo/specialtxman.cpp` around lines 583 - 587, The list-rebuild path
hardcodes signature verification for dissolution transactions, duplicating work
when the caller disables checks. Thread the caller’s check_sigs value through
RebuildListFromBlock and its callers, then pass that value to
CheckProDisTxForList instead of true; preserve existing validation behavior when
signature checking is enabled.
test/functional/feature_masternode_shares.py (1)

165-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Re-sign the transaction after you append the extra template output.

Line 172 reuses good_hex, which the wallet already signed. Line 173 appends an output, so the existing input signatures no longer commit to the output set. The assertion on line 176 then depends on mempool checks running standardness before script verification. That ordering holds today, but the test would report scriptpubkey for the wrong reason if the check order changes.

Build the extra-output transaction from the unsigned combined hex and sign it again, so the only defect left is the extra template output.

♻️ Proposed change to isolate the policy rejection
-        extra_out_tx = tx_from_hex(good_hex)
+        combined = node.protx("shared_combine", sigtest_prepared["tx"], sigtest_sigs)
+        extra_out_tx = tx_from_hex(combined)
         extra_out_tx.vout.append(CTxOut(1000, CScript(bytes.fromhex(SHARED_COLLATERAL_SCRIPT))))
-        res = node.testmempoolaccept([extra_out_tx.serialize().hex()])[0]
+        extra_signed = node.signrawtransactionwithwallet(extra_out_tx.serialize().hex())
+        assert_equal(extra_signed["complete"], True)
+        res = node.testmempoolaccept([extra_signed["hex"]])[0]
         assert_equal(res["allowed"], False)
         assert_equal(res["reject-reason"], "scriptpubkey")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/functional/feature_masternode_shares.py` around lines 165 - 176, Update
the extra-output test setup to start from the unsigned combined transaction hex
rather than the already signed good_hex, append the additional CTxOut, and
re-sign the modified transaction before calling testmempoolaccept. Keep the
assertions unchanged so rejection is isolated to the extra template output.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/evo/specialtxman.cpp`:
- Around line 1528-1541: Update the reward-script validation in the ProUpShareTx
path around opt_ptx->scriptReward to enforce the same P2PKH-or-P2SH destination
restriction as IsShareListTriviallyValid, rather than accepting every
destination supported by ExtractDestination. Preserve the existing payee-reuse
checks after applying this registration-equivalent validation.
- Around line 1577-1592: Update CheckProUpSharedRegTx to reject
opt_ptx->keyIDVoting when it matches any existing mnState.shares keyIDOwner,
preserving the existing duplicate-property validation and returning the
appropriate invalid transaction result for this conflict.

---

Nitpick comments:
In `@src/evo/providertx_util.cpp`:
- Around line 104-121: Guard the payout calculation in the share-processing loop
against a zero weight_total before the arith_uint256 division, preserving the
existing behavior for valid nonzero totals and the final-share remainder
calculation.

In `@src/evo/specialtxman.cpp`:
- Around line 583-587: The list-rebuild path hardcodes signature verification
for dissolution transactions, duplicating work when the caller disables checks.
Thread the caller’s check_sigs value through RebuildListFromBlock and its
callers, then pass that value to CheckProDisTxForList instead of true; preserve
existing validation behavior when signature checking is enabled.

In `@test/functional/feature_masternode_shares.py`:
- Around line 165-176: Update the extra-output test setup to start from the
unsigned combined transaction hex rather than the already signed good_hex,
append the additional CTxOut, and re-sign the modified transaction before
calling testmempoolaccept. Keep the assertions unchanged so rejection is
isolated to the extra template output.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92bec2de-f47d-46db-807a-5da439b586ab

📥 Commits

Reviewing files that changed from the base of the PR and between bdc584d and 7773601.

📒 Files selected for processing (32)
  • doc/release-notes-7437.md
  • src/Makefile.am
  • src/Makefile.test.include
  • src/common/bloom.cpp
  • src/core_write.cpp
  • src/evo/assetlocktx.cpp
  • src/evo/core_write.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/dmnstate.cpp
  • src/evo/dmnstate.h
  • src/evo/providertx.cpp
  • src/evo/providertx.h
  • src/evo/providertx_util.cpp
  • src/evo/sharedcollateral.h
  • src/evo/specialtx.cpp
  • src/evo/specialtx.h
  • src/evo/specialtx_filter.cpp
  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
  • src/masternode/payments.cpp
  • src/messagesigner.cpp
  • src/messagesigner.h
  • src/policy/policy.cpp
  • src/primitives/transaction.h
  • src/rpc/client.cpp
  • src/rpc/evo.cpp
  • src/rpc/rawtransaction.cpp
  • src/test/evo_sharedmn_tests.cpp
  • src/txmempool.cpp
  • src/validation.cpp
  • test/functional/feature_masternode_shares.py
  • test/functional/test_runner.py
🚧 Files skipped from review as they are similar to previous changes (24)
  • src/rpc/rawtransaction.cpp
  • src/evo/specialtx.h
  • src/evo/dmnstate.cpp
  • src/Makefile.am
  • src/Makefile.test.include
  • src/rpc/client.cpp
  • src/messagesigner.h
  • src/masternode/payments.cpp
  • src/evo/assetlocktx.cpp
  • src/core_write.cpp
  • src/evo/sharedcollateral.h
  • src/primitives/transaction.h
  • doc/release-notes-7437.md
  • src/common/bloom.cpp
  • src/evo/deterministicmns.cpp
  • src/policy/policy.cpp
  • src/evo/specialtxman.h
  • src/evo/dmnstate.h
  • src/evo/core_write.cpp
  • src/validation.cpp
  • test/functional/test_runner.py
  • src/evo/providertx.h
  • src/txmempool.cpp
  • src/rpc/evo.cpp

Comment thread src/evo/specialtxman.cpp
Comment thread src/evo/specialtxman.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd5ac81d12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/evo/dmnstate.h
std::shared_ptr<NetInfoInterface> netInfo{nullptr};
CScript scriptPayout;
MasternodePayoutShares payouts;
CollateralShares shares; // non-empty = shared masternode (joinSigs are not kept in state)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expose shared ownership through the Qt masternode interface

For shared states, payouts is empty and keyIDOwner is null, but the unchanged GetOwnerPayoutScripts()/MnEntryImpl path in src/node/interfaces.cpp:105-140 still exports only GetOwnerPayouts(state) and that singular owner key. The Qt ownership scan in src/qt/masternodelist.cpp:221-235 therefore cannot recognize a participant wallet by any share owner, refund, or reward script, while MasternodeEntry displays UNKNOWN for its payout. Expose the share keys and effective reward/refund scripts through this interface and use them for Qt ownership and display.

AGENTS.md reference: AGENTS.md:L159-L164

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed at the new head (b5ce7e3): MnEntry now exposes getShareOwnerKeyIds(), the Qt owned-masternode filter scans share owner keys via isSpendable(PKHash(...)), and MasternodeEntry no longer encodes the null singular owner key — it joins the share owner addresses for the display instead.


🤖 Posted autonomously by Claude on behalf of pasta.

@PastaPastaPasta
PastaPastaPasta force-pushed the claude/masternode-shares-dip-c40ac2 branch from bd5ac81 to 0c15a43 Compare August 1, 2026 19:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/test/evo_sharedmn_tests.cpp (4)

600-616: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Guard owner_keys[ptx.actorIndex] against an out-of-range index.

In the non-unanimous branch, Sign indexes owner_keys with ptx.actorIndex. owner_keys holds 3 entries. The current cases never call Sign with unanimous=false and an out-of-range actorIndex, so there is no present defect. The out-of-range actor case at Line 695 uses unanimous=true. If a later case combines an out-of-range actorIndex with unanimous=false, the test reads out of bounds and the failure mode is undefined behavior instead of a clear assertion.

🛡️ Proposed guard
         } else {
+            BOOST_REQUIRE_LT(ptx.actorIndex, std::size(owner_keys));
             std::vector<unsigned char> sig;
             BOOST_REQUIRE(CHashSigner::SignHash(hash, owner_keys[ptx.actorIndex], sig));
             ptx.vchSigs.push_back(sig);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/evo_sharedmn_tests.cpp` around lines 600 - 616, In the non-unanimous
branch of Sign, validate that ptx.actorIndex is within owner_keys before
indexing it, using a clear test assertion that fails for out-of-range values.
Preserve the existing unanimous signing loop and valid actor-index signing
behavior.

503-513: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the collision that the comment describes.

The comment states that a normal registration reusing a share owner key must be detected. The test only queries HasUniqueProperty for the share owner keys. It never adds a second, non-shared masternode whose keyIDOwner equals owner_keys[0] and asserts that AddMN rejects it. The stated invariant stays unproven.

Add a case that builds a non-shared CDeterministicMNState with keyIDOwner == owner_keys[0].GetPubKey().GetID() and asserts list.AddMN throws or fails, matching how the existing deterministic-MN tests assert duplicate-key rejection.

As per coding guidelines: "For Dash-specific review hotspots, prefer small tests that prove the invariant being changed."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/evo_sharedmn_tests.cpp` around lines 503 - 513, Add a focused
collision test near the existing shared-owner uniqueness assertions: construct a
non-shared CDeterministicMNState whose keyIDOwner equals
owner_keys[0].GetPubKey().GetID(), then call list.AddMN and assert it throws or
otherwise fails using the established duplicate-key rejection pattern in these
deterministic-MN tests. Keep the existing HasUniqueProperty and cleanup
assertions unchanged.

Source: Coding guidelines


361-388: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover netInfo and pubKeyOperator in the consent digest test.

MakeSharedRegConsentHash also commits nType, nMode, netInfo and pubKeyOperator. The test does not mutate those fields. netInfo is serialized through a version-conditional NetInfoSerWrapper, and pubKeyOperator through CBLSLazyPublicKeyVersionWrapper. Both are the entries most likely to break in a later refactor. If either commitment is dropped, a third party can rewrite the operator key or the service address of a fully consented registration, and this test still passes.

Add mutation cases for the operator public key and the network address, plus nType and nMode, in the same style as the existing blocks.

As per coding guidelines: "Exercise extra care around consensus and script flags, transaction payload serialization, masternodes, LLMQs, ... BLS transitions".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/evo_sharedmn_tests.cpp` around lines 361 - 388, Extend the “Every
covered field changes the digest” cases around
CProRegTx::MakeSharedRegConsentHash to mutate and verify nType, nMode, netInfo,
and pubKeyOperator individually. Use valid alternate values for the network
address and operator public key, preserving the existing pattern that each
mutated registration produces a hash different from base and exercises their
versioned serialization wrappers.

Source: Coding guidelines


198-205: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a scriptRefund reuse case to match the comment.

The comment states that refund and reward scripts must not pay a table owner key or the voting key. The block tests only scriptReward. The refund side of the invariant stays untested, so a regression that drops the refund check would pass.

♻️ Proposed additional assertions
     {
         CollateralShares shares{two_shares};
         shares[0].scriptReward = GetScriptForDestination(PKHash(shares[1].keyIDOwner));
         CheckShares(shares, DummyJoinSigs(2), 0, 0, voting_id, "bad-protx-shares-payee-reuse");
         shares[0].scriptReward = GetScriptForDestination(PKHash(voting_key.GetPubKey()));
         CheckShares(shares, DummyJoinSigs(2), 0, 0, voting_id, "bad-protx-shares-payee-reuse");
     }
+    {
+        CollateralShares shares{two_shares};
+        shares[0].scriptRefund = GetScriptForDestination(PKHash(shares[1].keyIDOwner));
+        CheckShares(shares, DummyJoinSigs(2), 0, 0, voting_id, "bad-protx-shares-payee-reuse");
+    }
+    {
+        CollateralShares shares{two_shares};
+        shares[0].scriptRefund = GetScriptForDestination(PKHash(voting_key.GetPubKey()));
+        CheckShares(shares, DummyJoinSigs(2), 0, 0, voting_id, "bad-protx-shares-payee-reuse");
+    }

Note: the second case replaces shares[0].scriptRefund, so it no longer duplicates shares[1].scriptRefund. Confirm the expected reject reason if the implementation orders the duplicate-refund check first.

As per coding guidelines: "For Dash-specific review hotspots, prefer small tests that prove the invariant being changed."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/evo_sharedmn_tests.cpp` around lines 198 - 205, Add corresponding
scriptRefund reuse cases in the existing CollateralShares block, assigning
shares[0].scriptRefund to shares[1].keyIDOwner and then to
voting_key.GetPubKey(), and assert both are rejected with the appropriate
reason. Ensure the second setup does not retain or trigger an unintended
duplicate-refund condition; use the expected reject reason for the validation
order.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/evo/specialtxman.cpp`:
- Around line 604-622: The shared registrar path around SetStateVersion must
preserve higher CProUpSharedRegTx payload versions. Update the version
assignment for newState so it uses the maximum of old_version and
opt_proTx->nVersion, matching the ProUpRegTx path, while retaining the existing
state-version validation and return behavior.
- Around line 781-792: Update ProcessSpecialTxsInBlock’s per-transaction v24
validation to also invoke the shared-collateral spends check used by
ConnectBlock and mempool validation, not only
CheckSharedCollateralTemplateOutputs. Reject the block through the existing
TxValidationState and block-invalid return path when either check fails, while
preserving validation for all transactions including coinbase and non-special
transactions.

---

Nitpick comments:
In `@src/test/evo_sharedmn_tests.cpp`:
- Around line 600-616: In the non-unanimous branch of Sign, validate that
ptx.actorIndex is within owner_keys before indexing it, using a clear test
assertion that fails for out-of-range values. Preserve the existing unanimous
signing loop and valid actor-index signing behavior.
- Around line 503-513: Add a focused collision test near the existing
shared-owner uniqueness assertions: construct a non-shared CDeterministicMNState
whose keyIDOwner equals owner_keys[0].GetPubKey().GetID(), then call list.AddMN
and assert it throws or otherwise fails using the established duplicate-key
rejection pattern in these deterministic-MN tests. Keep the existing
HasUniqueProperty and cleanup assertions unchanged.
- Around line 361-388: Extend the “Every covered field changes the digest” cases
around CProRegTx::MakeSharedRegConsentHash to mutate and verify nType, nMode,
netInfo, and pubKeyOperator individually. Use valid alternate values for the
network address and operator public key, preserving the existing pattern that
each mutated registration produces a hash different from base and exercises
their versioned serialization wrappers.
- Around line 198-205: Add corresponding scriptRefund reuse cases in the
existing CollateralShares block, assigning shares[0].scriptRefund to
shares[1].keyIDOwner and then to voting_key.GetPubKey(), and assert both are
rejected with the appropriate reason. Ensure the second setup does not retain or
trigger an unintended duplicate-refund condition; use the expected reject reason
for the validation order.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f457c6c4-384e-418e-a0ae-0ef778bf551e

📥 Commits

Reviewing files that changed from the base of the PR and between 7773601 and 0c15a43.

📒 Files selected for processing (29)
  • doc/release-notes-7437.md
  • src/Makefile.test.include
  • src/common/bloom.cpp
  • src/core_write.cpp
  • src/evo/assetlocktx.cpp
  • src/evo/core_write.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/dmnstate.cpp
  • src/evo/dmnstate.h
  • src/evo/providertx.cpp
  • src/evo/providertx.h
  • src/evo/providertx_util.cpp
  • src/evo/specialtx.cpp
  • src/evo/specialtx.h
  • src/evo/specialtx_filter.cpp
  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
  • src/masternode/payments.cpp
  • src/messagesigner.cpp
  • src/messagesigner.h
  • src/policy/policy.cpp
  • src/rpc/client.cpp
  • src/rpc/evo.cpp
  • src/rpc/rawtransaction.cpp
  • src/test/evo_sharedmn_tests.cpp
  • src/txmempool.cpp
  • src/validation.cpp
  • test/functional/feature_masternode_shares.py
  • test/functional/test_runner.py
🚧 Files skipped from review as they are similar to previous changes (23)
  • src/Makefile.test.include
  • test/functional/test_runner.py
  • src/evo/specialtx.h
  • src/masternode/payments.cpp
  • src/evo/assetlocktx.cpp
  • src/messagesigner.h
  • src/core_write.cpp
  • src/rpc/rawtransaction.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/dmnstate.cpp
  • doc/release-notes-7437.md
  • src/rpc/client.cpp
  • src/common/bloom.cpp
  • src/policy/policy.cpp
  • src/evo/providertx.h
  • test/functional/feature_masternode_shares.py
  • src/evo/dmnstate.h
  • src/validation.cpp
  • src/txmempool.cpp
  • src/evo/providertx.cpp
  • src/evo/core_write.cpp
  • src/rpc/evo.cpp
  • src/evo/providertx_util.cpp

Comment thread src/evo/specialtxman.cpp
Comment thread src/evo/specialtxman.cpp
PastaPastaPasta and others added 4 commits August 12, 2026 08:43
… submit time

SignAndSendSpecialTx ignored the completeness of the wallet signing
step, so submitting a transaction whose inputs this wallet cannot sign
died later in the mempool with an opaque script-verification error
("Operation not valid with the current stack size"). Check the signing
result and fail with the actual cause and the recovery path instead.

The typical way to hit this is running "protx shared_combine" for a
ProUpSharedRegTx on a wallet other than the one that ran
update_shared_registrar_prepare: combining inserts the share signatures
into the payload, which invalidates the prepare-time fee-input
signatures, and only the preparing wallet can re-sign them. Document
that wallet affinity in both RPCs' help text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unit: bloom filters match a shared registration on every share's
refund script, effective reward script and share owner key id, and the
lifecycle transactions (ProDisTx, ProUpShareTx, ProUpSharedRegTx) on
proTxHash plus their own new elements; BLOOM_UPDATE_ALL inserts the
proTxHash on a registration or reward-script match so a watcher follows
the masternode lifecycle automatically. Compact-filter element
extraction is checked against the same field set, guarding the sync
requirement between CheckSpecialTransactionMatchesAndUpdate and
ExtractSpecialTxFilterElements.

Functional: disconnecting a block containing a ProUpShareTx rolls the
share table back and reconnecting replays it; disconnecting a
dissolution restores the masternode with an identical share table and
registration height and returns the ProDisTx to the mempool (where the
restored masternode makes it valid again), and reconnecting removes
both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vbparams start time rendered as 0 (self.mocktime is unset when set_test_params runs) and the min_activation_height of 100 falls inside the ~119 blocks the framework setup mines, so v24 was already active when run_test began and activate_v24() was a silent no-op: no pre-activation behavior was ever exercised. Raise the earliest activation height to 250 and assert the pre-activation window actually exists.

New coverage: each lifecycle transaction type (ProDisTx, ProUpShareTx, ProUpSharedRegTx) carries a trivially-valid payload and is driven through block connection before activation, rejecting with its dedicated too-early reason; the same dissolution after activation fails at the masternode lookup instead, proving the gate itself caused the rejections. The wallet path is also closed pre-activation (register_shared_prepare requires payload version 3).

Also exercise the shared-specific revive gate: an operator-key change via ProUpSharedRegTx resets operator fields and PoSe-bans the masternode, and the new operator's ProUpServTx must revive it despite the null keyIDOwner (without the IsShared() carve-out in the revive condition a banned shared masternode could never be revived). Verify the reward split resumes from the unchanged share table afterwards.
…ters

GetOwnerPayouts on a shared masternode state is empty (the owner reward derives from the share table instead), which left shared masternodes invisible wherever that helper fed a display or wallet filter: masternode winners printed an empty payee list, masternodelist's payee mode showed UNKNOWN, the GUI could not attribute a masternode to a wallet holding a share, protx list wallet skipped it, protx info reported hasOwnerKey/ownsPayeeScript false, and update_service's default fee-source fallback called front() on an empty vector (undefined behavior, reachable by any shared masternode operator omitting feeSourceAddress with no operator payout script set).

Add CDeterministicMNState::GetOwnerRewardScripts(), returning each share's effective reward script for shared masternodes and the payout-list scripts otherwise, and use it in those display and filter paths. Wallet attribution also recognizes share owner keys (hasOwnerKey, protx list wallet). update_service now fails with a clear error asking for an explicit feeSourceAddress: a shared masternode's reward scripts belong to the participants, not the operator running the command, so there is no sensible default.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fabd9bf927

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/evo/specialtxman.cpp
// fields and put the MN into PoSe-banned state until a new ProUpServTx arrives
newState->ResetOperatorFields();
newState->BanIfNotBanned(nHeight);
newState->pubKeyOperator = opt_proTx->pubKeyOperator;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Revalidate revocations after rotating the shared operator key

When a block orders a ProUpSharedRegTx operator rotation before a ProUpRevTx for the same shared masternode, CheckProUpRevTx verifies the revocation against the old key from pindexPrev, but this branch installs the new key and the later revocation branch clears that evolving state without rechecking its signature. A displaced operator can therefore collaborate with a miner to revoke and ban the masternode immediately after the unanimous owners replace it; reject this pairing/order or revalidate the revocation against the rebuilt list.

AGENTS.md reference: AGENTS.md:L166-L169

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same pre-existing class as the ProUpServTx finding above (#7582): CheckProUpRevTx verifies the signature against dmnman.GetListForBlock(pindexPrev)'s operator key — identical, byte-for-byte, to develop:src/evo/specialtxman.cpp's CheckProUpRevTx (lines 1423-1453). RebuildListFromBlock's UPDATE_REVOKE branch never re-verifies the signature either, on develop or here. This PR's shared registrar path mirrors that established ordinary-MN behavior rather than diverging from it, so a displaced operator revoking via a same-block race would work identically for a non-shared masternode with ProUpRegTx + ProUpRevTx today. Added to #7582 rather than filing a duplicate — out of scope for this PR.


🤖 Posted autonomously by Claude on behalf of pasta.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Final validation — Codex/Sol only (Phase 2 disabled)

The current implementation is coherent, and the previously reported standby-dissolution and update_share help defects are fixed. Two non-blocking commit-history issues remain: the RPC commit still calls the version 3 payload “v4,” and the standby-documentation commit still records the old penalty-paying command form.
Source: reviewers gpt-5.6-sol (general and dash-core-commit-history); final verifier gpt-5.6-sol. Orchestration-only: openclaw-agent/cliproxy/gpt-5.6-sol (not reviewer evidence).

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — dash-core-commit-history (completed), gpt-5.6-sol — general (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 1 suggestion(s) | 💬 1 nitpick(s)

2 additional finding(s) omitted (not in diff).

🤖 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 `<commit:d73f8343697>`:
- [SUGGESTION] <commit:d73f8343697>:1: Update the standby command in the commit body
  The committed RPC help correctly recommends `protx dissolve <proTxHash> <shareIndex> <fee> false false`, but this commit body still records the older form ending in `<fee> false`. During the early period, that final `false` disables submission while leaving `payPenalty` at its default `true`, so the stored transaction permanently redistributes the early penalty instead of recovering the full principal after the boundary. Amend the commit body to include both `false` arguments and state that the zero-penalty standby becomes valid when the early period ends.

In `<commit:03a7a1caebf>`:
- [NITPICK] <commit:03a7a1caebf>:1: Correct the payload version in the RPC commit message
  The RPC commit still says `register_shared_prepare` appends a “v4 shared payload.” The implementation sets `CProRegTx::nVersion` to `ProTxVersion::ExtAddr`, which is version 3, and the consensus commit, RPC help, release notes, and PR description all identify the shared registration as version 3. Correct this remaining commit-message reference so permanent history does not document a nonexistent wire-format version.

@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Status at head fabd9bf: thepastaclaw’s gate reports no blockers, and coderabbit has independently reconciled and agreed with every remaining old thread as resolved/non-issue. CI is green except linux64_asan-build, which is failing on the pre-existing, already-tracked QDBusConnectionManager LeakSanitizer flake (#7561, filed before this PR and unrelated to this diff) — confirmed twice (different byte counts, same leak class, same stack through QDBusConnectionManager::executeConnectionRequest), so I’m not rerunning it further per the two-failures rule.

Open items outside this PR’s scope, tracked separately rather than fixed here:

This PR is ready for human review/merge as far as automated review can take it.


🤖 Posted autonomously by Claude on behalf of pasta.

PastaPastaPasta and others added 5 commits August 17, 2026 11:16
The minimum-based ProDisTx output rules left two unbounded paths by which value could leave the actor's share: the transaction fee and voluntary penalty overpayment. A single stolen share owner key could sign a unilateral dissolution paying the victim's entire share to miners as fee, or as bonus concentrated on a colluding participant, contradicting the DIP's guarantee that owner-key compromise never costs principal beyond the consented penalty.

Cap the fee at CProDisTx::MAX_FEE (0.01 DASH) in both modes and cap the unilateral bonus sum at the configured earlyPenalty, per the revised DIP. Both ceilings are height-independent, so validity stays monotone and early-period standbys paying the full penalty remain valid forever. Worst-case loss under key compromise is now earlyPenalty + MAX_FEE. Unanimous dissolutions keep unrestricted bonuses: every owner signed the exact outputs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every other signature-bearing payload in the shared-masternode set checks the 65-byte compact size in IsTriviallyValid, but ProUpShareTx only enforced it inside signature verification, which is skipped for assumed-valid blocks. Add the stateless check, matching its siblings and the DIP's payloadSigSize rule. The functional test's pre-activation placeholder gains a real-sized signature so the too-early gate stays the isolated reject reason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…saction

The package-level recheck added for special transactions guards against mempool entries invalidated by v24 activating, but the covenant also binds normal transactions: on a node accepting nonstandard transactions (devnets), a template-creating or template-spending normal transaction could sit in the mempool across activation, get selected into every template, and make TestBlockValidity reject each one, leaving the miner unable to produce a block. Run the covenant checks for all packaged transactions; they are exact 7-byte script comparisons per output/input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dissolution digest commits nLockTime and the input sequence, and BIP68 applies to version-3 transactions, so a co-signer's wallet could embed a lock the other signers bake into their signatures without noticing, delaying when the dissolution can confirm. Per the DIP's wallet guidance, shared_sign now refuses a dissolution carrying a lock time or non-final sequence unless the new allowTimeLocks parameter is set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the enumerated vectors the DIP's Test Cases section lists that had no test: the five stateless registration reject shapes (shared Evo, external collateral, non-zero keyIdOwner, non-empty payouts, non-shared payload with non-zero shared fields), consent-digest coverage of mode, type and operator key, a pre-activation template output shown consensus-valid to mine and permanently unspendable after activation, and an end-to-end near-miss script (one tag byte off) that mines and spends freely post-activation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Pushed 5 commits syncing with the revised DIP (dashpay/dips#187) after a conformity audit of this branch against it:

  • 1307510 evo: cap dissolution fees and unilateral penalty overpayment — the security fix from the DIP revision. The minimum-based output rules let a single stolen share owner key drain the actor's entire share (uncapped fee to miners, or uncapped "bonus" to a colluding participant). Unilateral dissolutions now enforce fee <= CProDisTx::MAX_FEE (0.01 DASH, both modes) and sum(bonus) <= earlyPenalty; both ceilings are height-independent so monotone validity and standby dissolutions are unaffected.
  • 39123ed evo: enforce the ProUpShareTx signature size statelessly — the one payload that only checked the 65-byte rule inside signature verification (skipped under assumed-valid); now matches its siblings.
  • da05408 miner: recheck the shared-collateral covenant for every packaged transaction — closes a devnet-only miner-abort: a template-creating normal tx accepted pre-activation via acceptnonstdtxn sat in the mempool across activation and would poison every block template; the existing package recheck only covered special txs.
  • fe1f531 rpc: guard shared_sign against unnoticed dissolution time locks — implements the DIP's new wallet guidance (BIP68 applies to v3 txs and the digest commits lock fields); refuses locked dissolutions unless allowTimeLocks is set.
  • 27cb59c test: backfill DIP test-list coverage — the five untested stateless registration rejects, consent-digest mutations of mode/type/operator key, pre-activation template output frozen post-activation (end-to-end), and a near-miss script mined and spent post-activation.

Validated: full test_dash unit suite region (evo_sharedmn_tests all green) and feature_masternode_shares.py pass on macOS clang; every commit is content-identical to the tested tree.

Known remaining gaps (deliberate, low value-to-effort): no functional vector drives a raw ProUpRegTx at a shared MN through consensus (the RPC-layer guard is tested; the consensus check exists at two layers), and coinbase template creation has no direct test (the block-level check runs from vtx[0] so the coinbase is covered by the same code path the normal-tx test exercises).


🤖 Posted autonomously by Claude on behalf of pasta.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Final validation — Codex/Sol only (Phase 2 disabled)

Two user-facing ownership/reporting gaps remain for shared masternodes, and two previously verified commit-message inaccuracies are still present. Four late corrective commits should also be folded into the commits that introduced the affected unshipped consensus, mining, and RPC behavior; all eight findings are non-blocking.
Source: reviewers gpt-5.6-sol (general and dash-core-commit-history); final verifier gpt-5.6-sol. Orchestration-only: openclaw-agent/cliproxy/gpt-5.6-sol (not reviewer evidence).

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — dash-core-commit-history (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 7 suggestion(s) | 💬 1 nitpick(s)

7 additional finding(s) omitted (not in diff).

🤖 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 `src/evo/core_write.cpp`:
- [SUGGESTION] src/evo/core_write.cpp:270: Do not encode the null shared owner key as an address
  A shared masternode deliberately has no singular owner, but CDeterministicMNState::ToJson() unconditionally encodes its null keyIDOwner as ownerAddress. CProRegTx::ToJson() does the same at line 364. This exposes a syntactically valid address derived from the all-zero key hash through protx info, detailed protx list output, decoded ProRegTx payloads, and Qt raw-JSON details, falsely presenting it as an owner alongside the actual owners in shares. Emit ownerAddress only for non-shared states and payloads, and mark it optional in both RPC result schemas.

In `src/qt/masternodelist.cpp`:
- [SUGGESTION] src/qt/masternodelist.cpp:223-237: Include immutable refund scripts in the Qt ownership filter
  The Qt owned-masternode filter checks share owner keys and effective reward scripts, but not each share's immutable refund script. A wallet holding the refund destination is therefore omitted when rewards go elsewhere and the owner key is kept in another wallet, even though that wallet owns the principal returned by dissolution. The RPC wallet filter explicitly handles this case with CheckWalletOwnsAnyShareRefund because listProTxCoins cannot associate an individual participant with the shared covenant output. Expose the refund scripts through MnEntry and include them in the Qt ownership predicate.

In `<commit:d73f8343697>`:
- [SUGGESTION] <commit:d73f8343697>: Update the standby command in the commit body
  The committed RPC help correctly recommends `protx dissolve <proTxHash> <shareIndex> <fee> false false`, but this commit body still records the older form ending in `<fee> false`. During the early period, that final `false` disables submission while leaving `payPenalty` at its default `true`, so the stored transaction permanently redistributes the early penalty instead of recovering the full principal after the boundary. Amend the commit body to include both `false` arguments and state that the zero-penalty standby becomes valid when the early period ends.

In `<commit:03a7a1caebf>`:
- [NITPICK] <commit:03a7a1caebf>: Correct the payload version in the RPC commit message
  The RPC commit still says `register_shared_prepare` appends a “v4 shared payload.” The implementation sets CProRegTx::nVersion to ProTxVersion::ExtAddr, which is version 3, and the consensus commit, RPC help, release notes, and PR description all identify the shared registration as version 3. Correct this remaining commit-message reference so permanent history does not document a nonexistent wire-format version.

In `<commit:13075107807>`:
- [SUGGESTION] <commit:13075107807>:1: Fold the dissolution safety caps into the consensus commit
  Commit 13075107807 closes the unbounded fee and unilateral-bonus paths in the ProDisTx rules introduced by 25a6fcb4650. Without this correction, a stolen share owner key can drain substantially more than the consented penalty from its share, contradicting the feature's principal-protection guarantee. Because the consensus feature has not shipped independently, fold the production rules into 25a6fcb4650, place the regression coverage with e533abb8b82, and amend bd4804daf66 as appropriate so permanent history does not retain the known-incomplete implementation.

In `<commit:39123ed5cd5>`:
- [SUGGESTION] <commit:39123ed5cd5>:1: Fold the ProUpShareTx signature-size correction into its introduction
  Commit 39123ed5cd5 adds the stateless compact-signature-size check omitted when CProUpShareTx::IsTriviallyValid was introduced by 25a6fcb4650. Before the correction, this shared payload differed from its siblings under assumed-valid processing because signature verification could be skipped. Fold the production hunk into 25a6fcb4650 and move its unit and pre-activation test adjustments into the corresponding earlier test commits.

In `<commit:da054089f5a>`:
- [SUGGESTION] <commit:da054089f5a>:1: Fold the miner covenant correction into the consensus feature
  Commit da054089f5a completes the shared-collateral covenant introduced by 25a6fcb4650 by rechecking normal as well as special packaged transactions after activation. Without this hunk, an intermediate checkout can repeatedly assemble templates that fail final validity checks when a nonstandard normal transaction crosses activation. Fold the correction into 25a6fcb4650 so the covenant and its block-assembly integration enter history atomically.

In `<commit:fe1f531afd8>`:
- [SUGGESTION] <commit:fe1f531afd8>:1: Fold the time-lock guard into the shared signing flow
  Commit fe1f531afd8 adds the DIP-mandated time-lock acknowledgement to shared_sign, which was introduced earlier in this stack by 03a7a1caebf. Without the guard, a co-signer can overlook nLockTime or a non-final sequence that is committed into the signed dissolution and delays confirmation. Since this RPC has not shipped without the safeguard, fold the RPC and client-conversion changes into 03a7a1caebf, with the functional assertion and release-note update placed in the earlier test and documentation commits.

Comment thread src/qt/masternodelist.cpp
Comment on lines 224 to 237
const bool owns_payout{std::any_of(script_payouts.begin(), script_payouts.end(), [&](const auto& script) {
return walletModel->wallet().isSpendable(script);
})};
// A shared masternode has a null keyIDOwner; its share owner keys take its place
const auto share_owner_key_ids{entry->shareOwnerKeyIdsRaw()};
const bool owns_share{std::any_of(share_owner_key_ids.begin(), share_owner_key_ids.end(), [&](const auto& key_id) {
return walletModel->wallet().isSpendable(PKHash(key_id));
})};
bool fMyMasternode{setOutpts.count(entry->collateralOutpointRaw()) ||
walletModel->wallet().isSpendable(PKHash(entry->keyIdOwnerRaw())) ||
owns_share ||
walletModel->wallet().isSpendable(PKHash(entry->keyIdVotingRaw())) ||
owns_payout ||
walletModel->wallet().isSpendable(entry->scriptOperatorPayoutRaw())};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: Include immutable refund scripts in the Qt ownership filter

The Qt owned-masternode filter checks share owner keys and effective reward scripts, but not each share's immutable refund script. A wallet holding the refund destination is therefore omitted when rewards go elsewhere and the owner key is kept in another wallet, even though that wallet owns the principal returned by dissolution. The RPC wallet filter explicitly handles this case with CheckWalletOwnsAnyShareRefund because listProTxCoins cannot associate an individual participant with the shared covenant output. Expose the refund scripts through MnEntry and include them in the Qt ownership predicate.

source: ['codex']

@github-actions

Copy link
Copy Markdown

This pull request has conflicts, please rebase.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants