Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/wallet/scriptpubkeyman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2220,7 +2220,16 @@ isminetype DescriptorScriptPubKeyMan::IsMine(const CScript& script) const
{
LOCK(cs_desc_man);
if (m_map_script_pub_keys.count(script) > 0) {
return ISMINE_SPENDABLE;
// A descriptor containing only public keys can solve an input for fee
// estimation, but it cannot sign it. This distinction matters for
// private-key-enabled descriptor wallets which also track public-only
// descriptors (for example, a DashPay contact's receiving chain).
// Preserve descriptor watch-only/external-signer wallet semantics;
// those wallets deliberately operate without local private keys. The
// 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.

}
return ISMINE_NO;
}
Expand Down
41 changes: 41 additions & 0 deletions src/wallet/test/wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,47 @@ static void AddKey(CWallet& wallet, const CKey& key)
if (!wallet.AddWalletDescriptor(w_desc, provider, "", false)) assert(false);
}

BOOST_FIXTURE_TEST_CASE(public_descriptor_outputs_are_watch_only, TestChain100Setup)
{
CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase());
CExtKey contact_key;
const std::array<std::byte, 32> seed{std::byte{1}};
contact_key.SetSeed(seed);
const CExtPubKey contact_xpub{contact_key.Neuter()};

FlatSigningProvider provider;
std::string error;
auto descriptor{Parse("pkh(" + EncodeExtPubKey(contact_xpub) + "/*)", provider, error,
/*require_checksum=*/false)};
BOOST_REQUIRE_MESSAGE(descriptor, error);

{
LOCK(wallet.cs_wallet);
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
WalletDescriptor wallet_descriptor(std::move(descriptor), /*creation_time=*/0,
/*range_start=*/0, /*range_end=*/10,
/*next_index=*/0);
BOOST_REQUIRE(wallet.AddWalletDescriptor(wallet_descriptor, provider, "DashPay contact",
/*internal=*/false));

CExtPubKey child;
BOOST_REQUIRE(contact_xpub.Derive(child, 0));
const CScript contact_script{GetScriptForDestination(PKHash{child.pubkey})};
BOOST_CHECK_EQUAL(wallet.IsMine(contact_script), ISMINE_WATCH_ONLY);

CMutableTransaction payment;
payment.vin.emplace_back(g_insecure_rand_ctx.rand256(), 0);
payment.vout.emplace_back(COIN, contact_script);
wallet.AddToWallet(MakeTransactionRef(payment), TxStateInMempool{});

CCoinControl coin_control;
coin_control.m_include_unsafe_inputs = true;
BOOST_CHECK_EQUAL(AvailableCoins(wallet, &coin_control).size(), 0U);
coin_control.fAllowWatchOnly = true;
BOOST_CHECK_EQUAL(AvailableCoins(wallet, &coin_control).size(), 1U);
}
}

BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
{
// Cap last block file size, and mine new block in a new block file.
Expand Down
Loading