Skip to content

fix(wallet): report public-only descriptors in signing wallets as watch-only - #7510

Closed
PastaPastaPasta wants to merge 1 commit into
dashpay:developfrom
PastaPastaPasta:fix-descriptor-ismine-watch-only
Closed

fix(wallet): report public-only descriptors in signing wallets as watch-only#7510
PastaPastaPasta wants to merge 1 commit into
dashpay:developfrom
PastaPastaPasta:fix-descriptor-ismine-watch-only

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

DescriptorScriptPubKeyMan::IsMine returns ISMINE_SPENDABLE for any script tracked by one of the wallet's descriptors — including descriptors that contain only public keys. In a wallet that otherwise has private keys, outputs on such a descriptor are treated as spendable balance and can be selected by coin selection, producing transactions the wallet cannot sign. A public-only descriptor can solve a script (enough for fee estimation), but it cannot sign it.

Today this is hard to hit from the GUI/RPC because importdescriptors into a private-key-enabled wallet normally imports descriptors with private keys, but any current or future path that tracks a public-only descriptor in a signing wallet (e.g. address discovery for an external party's chain) silently corrupts the wallet's view of its spendable balance. This is a prerequisite correctness fix extracted from the Platform GUI work in PastaPastaPasta#49, where a DashPay contact's receiving chain is exactly such a descriptor.

What was done?

Report scripts from public-only descriptors as ISMINE_WATCH_ONLY when the wallet is a signing wallet. Descriptor watch-only wallets and external-signer wallets (WALLET_FLAG_DISABLE_PRIVATE_KEYS) keep their existing ISMINE_SPENDABLE semantics — those wallets deliberately operate without local private keys and their spend paths handle signing elsewhere. The dangerous case is only a public descriptor mixed into a signing wallet.

How Has This Been Tested?

New unit test wallet_tests/public_descriptor_outputs_are_watch_only: imports a public-only pkh(xpub/*) descriptor into a descriptor wallet with private keys enabled, and checks that a payment to a derived address is reported ISMINE_WATCH_ONLY, is excluded from AvailableCoins by default, and appears only when fAllowWatchOnly is set.

wallet_tests, scriptpubkeyman_tests, ismine_tests, and coinselector_tests suites pass locally (macOS, clang).

Breaking Changes

None expected. Balances previously (mis)reported as spendable on such descriptors are now reported as watch-only.

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
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

🤖 Generated with Claude Code

…ch-only

DescriptorScriptPubKeyMan::IsMine returned ISMINE_SPENDABLE for any script tracked by one of the wallet's descriptors, including descriptors that contain only public keys. In a wallet that otherwise has private keys, outputs on such a descriptor were treated as spendable balance and could be selected by coin selection, producing transactions the wallet cannot sign. A public-only descriptor can solve a script for fee estimation, but it cannot sign it.

Report such scripts as ISMINE_WATCH_ONLY instead. Descriptor watch-only wallets and external-signer wallets (WALLET_FLAG_DISABLE_PRIVATE_KEYS) keep their existing ISMINE_SPENDABLE semantics; those wallets deliberately operate without local private keys. The dangerous case is a public descriptor mixed into a signing wallet, e.g. a contact's receiving chain imported for address discovery.

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

thepastaclaw commented Aug 2, 2026

Copy link
Copy Markdown

🕓 Ready for review — 2 ahead in queue (commit 5004546)
Queue position: 3/5 · 2 reviews active
ETA: start ~02:26 UTC · complete ~02:48 UTC (median 21m across 30 recent reviews; 2 slots)
Queued 35m ago · Last checked: 2026-08-02 02:00 UTC

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 07f56924-9bea-4509-8d0d-b1f7a3bd58f0

📥 Commits

Reviewing files that changed from the base of the PR and between 862ef3c and 5004546.

📒 Files selected for processing (2)
  • src/wallet/scriptpubkeyman.cpp
  • src/wallet/test/wallet_tests.cpp

Walkthrough

DescriptorScriptPubKeyMan::IsMine now classifies descriptor-backed scripts as spendable only when private keys are available or explicitly disabled. Otherwise, it classifies them as watch-only. A wallet test imports a ranged public descriptor, derives an output, checks its ownership status, and verifies that AvailableCoins includes it only when watch-only outputs are allowed.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title clearly and concisely describes the main change: public-only descriptors are reported as watch-only in signing wallets.
Description check ✅ Passed The description directly explains the classification bug, the intended behavior, the test coverage, and preserved wallet semantics.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 5004546fc1

ℹ️ 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".

// dangerous case is a public descriptor mixed into a signing wallet.
return HavePrivateKeys() || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)
? ISMINE_SPENDABLE
: ISMINE_WATCH_ONLY;

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 Surface descriptor watch-only balances in the Qt wallet

When a private-key-enabled descriptor wallet receives an output matching one of these public-only descriptors, this branch removes the amount from the spendable balance but Dash Qt cannot display it as watch-only: interfaces::Wallet::haveWatchOnly() only queries LegacyScriptPubKeyMan (src/wallet/interfaces.cpp:248-254), and getBalances() consequently leaves all watch-only fields unset (src/wallet/interfaces.cpp:492-497); the descriptor branch of OverviewPage::setBalance also renders only the spendable fields. Thus the amount disappears entirely from the overview instead of being reported as watch-only. The descriptor-aware watch-only state and corresponding Qt balance presentation need to be updated with this classification change.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Valid finding. I implemented the descriptor-aware HaveWatchOnly() path, getbalances/interfaces propagation, Qt watch-only columns, import notification, and regression coverage in amended commit c8576edb511. wallet_tests and the focused regression test pass. I could not update this PR head because it lives on PastaPastaPasta/dash and the available thepastaclaw credential has read-only access there (force-push rejected with 403); the PR owner needs to force-push/cherry-pick this amended commit.

@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Closing after more thought about sequencing: nothing in the current DashPay/Platform series actually depends on this — the friendship receiving chain imports as a private descriptor, and the contact's chain is never imported at all. The state this guards (a public-only descriptor inside a signing wallet) is unreachable via RPC/GUI today, so this is a guard rail without an in-tree consumer. If a future iteration imports xpub-only chains (e.g. locked-wallet contact acceptance), this will return alongside that change, where its motivation is adjacent and reviewable in context.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants