Skip to content

fix(platform-wallet): a ProUpServTx always carries an output - #5105

Merged
QuantumExplorer merged 2 commits into
v4.2-devfrom
claude/masternode-update-service-confirmed-address
Sep 28, 2026
Merged

QuantumExplorer merged 2 commits into
v4.2-devfrom
claude/masternode-update-service-confirmed-address

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

A masternode operator sends an "update service" transaction (ProUpServTx) to state where their node lives; sending one also lifts a PoSe ban. This PR makes sure the wallet never builds one without an output. Every ProUpServTx now carries a small zero-value data output, the same one Dash Core adds, so the network accepts it however small the coin that pays the fee.

Example:

  • Before: a wallet whose chosen coin was about 342 to 888 duffs built a service update with no outputs (the coin covered the fee, and the leftover was below the dust limit, so no change output was added). The network refuses such a transaction (bad-txns-vout-empty), and the app reported it as maybe sent with the coin left reserved.
  • After: every update carries a zero-value data output, as Dash Core's protx update_service does, so the same wallet sends a valid transaction. Change is still returned whenever the leftover clears the dust limit, and a transaction without outputs is refused before signing, releasing the coin.

Value: A wallet holding only small coins can still update its node, and a transaction the network would refuse is never signed or sent.

Risks: Every ProUpServTx carries one extra output of about 11 bytes, which costs about 11 duffs of fee. The service address handling is unchanged from v4.2-dev, and so are the FFI and Swift APIs.

Issue being fixed or feature implemented

The update-service builder added no outputs of its own. When coin selection booked the whole leftover of a small coin as fee (anything from about 342 to 888 duffs in a single coin), the ProUpServTx had no outputs. The network refuses a transaction without outputs, and the selected coin stayed reserved while the wallet waited on a broadcast that could not confirm.

What was done?

rs-platform-wallet (masternode/update_service.rs):

  • The new update_service_builder adds a zero-value OP_RETURN output to every ProUpServTx, as Dash Core's FundSpecialTx does.
  • The payload finalizer, now the named finalize_update_service_payload, refuses a transaction without outputs before signing anything. key-wallet releases the build's reservation on any finalizer error, so the refusal leaves the coin spendable.
  • The comment on reading the registration transaction no longer calls the proTxHash "SPV-authenticated". It now says the fetched transaction must hash to the requested proTxHash, that only the operator reward is read from it, and that the service values come from the synced masternode list's entry for that proTxHash.

The service address, masternode type and platform fields still come from the synced masternode list, exactly as on v4.2-dev. No FFI or Swift file changes. This is wallet code only; no consensus or protocol-version change.

How Has This Been Tested?

  • cargo test -p platform-wallet --lib masternode:: (66 passed), including:
    • should_carry_an_output_when_the_only_utxo_leaves_no_change ([600]: one zero-value data output, fee 600)
    • should_carry_an_output_when_selection_prefers_the_small_utxo ([1_000_000_000, 600])
    • should_fail_before_broadcast_when_the_fee_cannot_be_covered ([200]: insufficient-funds error, nothing sent)
    • should_release_the_reservation_when_the_transaction_has_no_outputs (a builder without the data output is refused by the finalizer, and the same coin funds the next build)
    • builds_signs_and_broadcasts_a_pro_up_serv_tx now also asserts the transaction carries an output.
  • With the data output removed, the [600], [1_000_000_000, 600] and reservation-release tests fail, confirming they cover the change.
  • cargo clippy -p platform-wallet --tests -- -D warnings, cargo fmt --all -- --check, and cargo check -p platform-wallet-ffi (the FFI crate is unchanged).

Breaking Changes

None. The Rust, FFI and Swift update-service APIs are unchanged.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · c939f01

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs — this one is beyond the limit; it waits until one merges
  • Build running
  • Approvals
    • rs-platform-wallet (packages/rs-platform-wallet/src/masternode/update_service.rs) — HashEngineering or ZocoLini or llbartekll or romchornyi

When every box is checked the PR Hygiene check passes and this can merge.

Summary by CodeRabbit

  • New Features
    • Masternode service updates now use confirmed service addresses and, for evonodes, platform node IDs and ports.
    • Added service suggestions showing synced endpoint details, evonode information, PoSe-ban status, and extended network information.
    • Service addresses are validated without guessing a port.
  • Bug Fixes
    • Updates can proceed when the synced masternode list lacks a plain IP-and-port address.
    • Invalid service details and mismatched masternode types are rejected before transaction preparation.

…on every ProUpServTx

The update-service (unban) flow now takes the service address as an
explicit, caller-confirmed input instead of copying it from the synced
masternode list: IP:port always, and for an evonode the platform node id,
platform P2P port and platform HTTP port. The payload is built from that
input alone. The confirmed type is checked against the registration
transaction, which is already bound to the requested proTxHash, and
values no payload can carry (a non-IPv4 or unspecified address, a zero
port, an empty node id, clashing ports) are refused before any network
work.

A read-only suggestion accessor (library, FFI and Swift) returns what the
list shows so hosts can prefill their form, including whether the list
shows the node PoSe-banned. Nothing refuses on that status.

A ProUpServTx always carries an output: the builder adds a zero-value
data output, the same placeholder Dash Core's protx update_service adds,
so a selection whose leftover is below the dust limit no longer yields a
transaction with no outputs. The payload finalizer also refuses an
output-less transaction before signing, which releases the reservation.

BREAKING CHANGE: MasternodeUpdateServiceParams takes a
ConfirmedMasternodeService in place of platform_p2p_port. The four
update-service FFI entry points take service_address, platform_node_id,
platform_p2p_port and platform_http_port in place of
has_platform_p2p_port and platform_p2p_port. The Swift wrappers take a
MasternodeServiceConfirmation in place of platformP2PPort.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 28, 2026
@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 28, 2026
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: d0a1a9c7-e5ad-489c-ac9e-76cafed07486

📥 Commits

Reviewing files that changed from the base of the PR and between 7894963 and b23e7ec.

📒 Files selected for processing (6)
  • packages/rs-platform-wallet-ffi/src/masternode_update_service.rs
  • packages/rs-platform-wallet/src/masternode/mod.rs
  • packages/rs-platform-wallet/src/masternode/update_service.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerMasternodes.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerTrackedMasternodes.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/MasternodeServiceConfirmationTests.swift

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


📝 Walkthrough

Walkthrough

Masternode service updates now use caller-confirmed regular or evonode service data. The Rust wallet validates and builds payloads from those values, exposes synced-list suggestions, and signs funded transactions. The FFI and Swift APIs pass the new fields for wallet-owned and tracked masternodes.

Changes

Masternode Service Updates

Layer / File(s) Summary
Confirmed service data and payload validation
packages/rs-platform-wallet/src/masternode/update_service.rs, packages/rs-platform-wallet/src/masternode/mod.rs
Update parameters and payloads use caller-confirmed regular or evonode values. Preparation validates these values against registration terms. Suggestion lookup returns synced-list details, and tests cover validation and payload fields.
Transaction output and payload signing
packages/rs-platform-wallet/src/masternode/update_service.rs
The builder adds a zero-value OP_RETURN output. The finalizer validates and signs the payload. Tests cover funding, output presence, and reservation behavior.
Rust FFI marshalling and suggestions
packages/rs-platform-wallet-ffi/src/masternode_update_service.rs
Broadcast and prepare APIs accept a service address and regular or evonode fields. The suggestion API converts synced-list data to an FFI result. Tests cover marshalling and pointer and handle errors.
Swift confirmations and manager APIs
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerMasternodes.swift, packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerTrackedMasternodes.swift, packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletMasternodes.swift, packages/swift-sdk/SwiftTests/SwiftDashSDKTests/MasternodeServiceConfirmationTests.swift
Swift adds confirmation and suggestion types. Wallet-owned and tracked update methods pass confirmed values to FFI. Tests check regular and evonode argument shapes and node-ID length validation.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant PlatformWalletManager
  participant RustFFI
  participant prepare_masternode_update_service
  participant update_service_builder
  PlatformWalletManager->>RustFFI: Submit confirmed service values
  RustFFI->>prepare_masternode_update_service: Marshal parameters and prepare update
  prepare_masternode_update_service->>update_service_builder: Build and finalize transaction
  update_service_builder-->>RustFFI: Return prepared transaction
  RustFFI-->>PlatformWalletManager: Return transaction result
Loading

Suggested reviewers: llbartekll

Merge Risk: ⚪ Minimal · up to b23e7

No actionable issue remains; the change is mergeable after normal checks.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to b23e7

The update now relies on the calling app to confirm service values before signing. The inspected wallet paths retain operator and registration checks, and no new bypass was established, but the app-level confirmation flow remains unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — A wrongly confirmed but shape-valid endpoint could affect the requested masternode's advertised service and consume wallet funding, rather than granting a general cross-wallet update capability. The inspected path requires a matching operator secret and wallet-backed signing.

Trust Boundaries and Controls

  • inferred — The calling app now owns human confirmation: the SDK accepts a supplied confirmation object, while the separate list query supplies only suggestions. The inspected library path validates values and signing authority but cannot establish that a person reviewed those values.

Resilience and Maintainability Implications

  • observed — Malformed service fields are marshalled before wallet context and operator-secret resolution in the inspected FFI broadcast path; the finalizer binds its signature to the assembled transaction's selected inputs.

Hardening Proposals

  • proposed — At each consuming app entrypoint, display the exact endpoint and evonode fields that will be signed and require explicit approval after any suggestion is prefilled. Verify that app flow during integration; the library sources alone cannot prove it occurs.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.21% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 78 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes a real change: every ProUpServTx now includes an output. It does not mention the broader caller-confirmed service-value and API changes, but it remains specific and rele…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@thepastaclaw

thepastaclaw commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit c939f01) · triage: critical

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Phase 1 + Phase 2

Verified the supplied findings against head b23e7ec. Two non-blocking issues remain in the new caller-confirmed service path: embedded NULs can truncate the confirmed address, and semantic validation occurs after mnemonic resolution in wallet-owned calls. The duplicate validation-order finding is consolidated; the pre-existing panic-hardening observation is excluded.

🟡 2 suggestion(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: architecture-layering); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: ffi-engineer); reviewer 4: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 5: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 6: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 7: gpt-6-astra (agent: phase2-reviewer, role: ffi-engineer); reviewer 8: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)

  • Triage: critical by gpt-6-astra (effort low) — The large cross-language change directly modifies signed ProUpServTx payload construction and transaction funding/output behavior in packages/rs-platform-wallet/src/masternode/update_service.rs, including prepare_masternode_update_service and build_sign_update_service, meeting the intricacy and funds-movement/signature-surface criteria.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — architecture-layering (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — ffi-engineer (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity below 15% reserve: weekly 13% left, 5h 100% left), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh verifier: gpt-6-astra — final-verifier; agent astra-verifier
  • Phase 2 reviewers: gpt-6-astra — general (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — rust-quality (completed, effort xhigh); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerMasternodes.swift`:
- [SUGGESTION] packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerMasternodes.swift:591-596: Reject embedded NUL characters before marshalling the confirmed address
  The shared preflight accepts embedded U+0000 characters, but `withFFIArguments()` passes the address through `withCString`, and Rust's `marshal_params()` reads it with `CStr::from_ptr`. Consequently, `"203.0.113.7:9999\u{0}:19999"` reaches Rust as the valid address `203.0.113.7:9999`; the malformed complete string is never validated. All four Swift update-service methods use this path, so an otherwise valid request can sign a truncated address rather than reject the caller-confirmed input. Reject NUL characters here, before crossing the C-string boundary, and add a regression test covering a valid address followed by a NUL and suffix.

In `packages/rs-platform-wallet-ffi/src/masternode_update_service.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/masternode_update_service.rs:205-211: Validate confirmed service values before resolving the operator mnemonic
  `marshal_params()` parses the endpoint and checks the regular-masternode shape, but accepts values that `validate_confirmed_service()` will unconditionally reject, such as an all-zero evonode node id or zero/clashing platform ports. Both wallet-owned entry points call `wallet_operator_secret()` after marshalling but before reaching that validator in `prepare_masternode_update_service()`. For external-signable or watch-only wallets, this invokes the host mnemonic resolver for a request already known to be invalid, potentially requiring Keychain authentication; a resolver failure also masks the service validation error. This falls short of the new call-site intent that malformed input fail before resolver use. Expose and reuse the wallet's semantic validator during marshalling, retain the prepare-time check, and add a test asserting that invalid service values do not invoke the resolver. The tracked-key entry points do not have this early mnemonic-resolution side effect.
Out-of-scope follow-up suggestions (1)

These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.

  • Crate-wide panic-across-extern "C" hardening — Pre-existing broad hardening outside this PR's scope: runtime.rs still propagates worker panics through expect, and this PR does not change that behavior or establish a new reachable panic. The supplied claim that a Rust panic would unwind into Swift as undefined behavior is also inaccurate for these non-unwinding extern "C" functions: a Rust panic reaching that boundary aborts rather than unwinding into the caller.
    • Follow-up: Consider creating a separate issue or author/maintainer-requested PR for this.

Comment thread packages/rs-platform-wallet-ffi/src/masternode_update_service.rs Outdated
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Your move: thepastaclaw left review threads unresolved; resolve them.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Sep 28, 2026
Keep only the output fix in this change. The service address, masternode
type and platform fields come from the synced masternode list as on
v4.2-dev, and the FFI entry points and Swift methods are back to their
v4.2-dev form.

Every ProUpServTx gets a zero-value OP_RETURN output, as Dash Core's
FundSpecialTx adds for protx update_service, and the payload finalizer
refuses a transaction without outputs before signing. The comment on
reading the registration transaction now says the fetched transaction
must hash to the requested proTxHash, that only the operator reward is
read from it, and that the service values come from the synced list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@QuantumExplorer QuantumExplorer changed the title fix(platform-wallet)!: caller-confirmed service values and an output on every ProUpServTx fix(platform-wallet): a ProUpServTx always carries an output Sep 28, 2026
@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed labels Sep 28, 2026
@QuantumExplorer
QuantumExplorer merged commit 1368fbb into v4.2-dev Sep 28, 2026
23 of 24 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/masternode-update-service-confirmed-address branch September 28, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants