Don’t select transparent coinbase with ANY_TADDR

This also includes the “Insufficient funds” suffixes on dust threshold
failures.

Fixes #6262
This commit is contained in:
Greg Pfeil 2022-11-23 23:15:10 -07:00
parent 4a4ae4d3e2
commit 7abdade2cc
4 changed files with 44 additions and 31 deletions

View File

@ -67,12 +67,10 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
for (const SendManyRecipient& recipient : recipients_) {
std::visit(match {
[&](const CKeyID& addr) {
transparentRecipients_ += 1;
txOutputAmounts_.t_outputs_total += recipient.amount;
recipientPools_.insert(OutputPool::Transparent);
},
[&](const CScriptID& addr) {
transparentRecipients_ += 1;
txOutputAmounts_.t_outputs_total += recipient.amount;
recipientPools_.insert(OutputPool::Transparent);
},
@ -181,7 +179,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
// Allow transparent coinbase inputs if there are no transparent
// recipients.
bool allowTransparentCoinbase = transparentRecipients_ == 0;
bool allowTransparentCoinbase = !recipientPools_.count(OutputPool::Transparent);
// Set the dust threshold so that we can select enough inputs to avoid
// creating dust change amounts.
@ -196,6 +194,8 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
}
if (!spendable.LimitToAmount(targetAmount, dustThreshold, recipientPools_)) {
CAmount changeAmount{spendable.Total() - targetAmount};
std::string insufficientFundsMessage =
strprintf("Insufficient funds: have %s", FormatMoney(spendable.Total()));
if (changeAmount > 0 && changeAmount < dustThreshold) {
// TODO: we should provide the option for the caller to explicitly
// forego change (definitionally an amount below the dust amount)
@ -203,34 +203,29 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
// creating dust change, rather than prohibit them from sending
// entirely in this circumstance.
// (Daira disagrees, as this could leak information to the recipient)
throw JSONRPCError(
RPC_WALLET_INSUFFICIENT_FUNDS,
strprintf(
"Insufficient funds: have %s, need %s more to avoid creating invalid change output %s "
"(dust threshold is %s)",
FormatMoney(spendable.Total()),
FormatMoney(dustThreshold - changeAmount),
FormatMoney(changeAmount),
FormatMoney(dustThreshold)));
insufficientFundsMessage +=
strprintf(
", need %s more to avoid creating invalid change output %s (dust threshold is %s)",
FormatMoney(dustThreshold - changeAmount),
FormatMoney(changeAmount),
FormatMoney(dustThreshold));
} else {
bool isFromUa = std::holds_alternative<libzcash::UnifiedAddress>(ztxoSelector_.GetPattern());
throw JSONRPCError(
RPC_WALLET_INSUFFICIENT_FUNDS,
strprintf(
"Insufficient funds: have %s, need %s",
FormatMoney(spendable.Total()), FormatMoney(targetAmount))
+ (allowTransparentCoinbase ? "" :
"; note that coinbase outputs will not be selected if you specify "
"ANY_TADDR or if any transparent recipients are included.")
+ ((!isFromUa || strategy_.AllowLinkingAccountAddresses()) ? "" :
" (This transaction may require selecting transparent coins that were sent "
"to multiple Unified Addresses, which is not enabled by default because "
"it would create a public link between the transparent receivers of these "
"addresses. THIS MAY AFFECT YOUR PRIVACY. Resubmit with the `privacyPolicy` "
"parameter set to `AllowLinkingAccountAddresses` or weaker if you wish to "
"allow this transaction to proceed anyway.)")
);
insufficientFundsMessage += strprintf(", need %s", FormatMoney(targetAmount));
}
bool isFromUa = std::holds_alternative<libzcash::UnifiedAddress>(ztxoSelector_.GetPattern());
throw JSONRPCError(
RPC_WALLET_INSUFFICIENT_FUNDS,
insufficientFundsMessage
+ (allowTransparentCoinbase && ztxoSelector_.SelectsTransparentCoinbase() ? "" :
"; note that coinbase outputs will not be selected if you specify "
"ANY_TADDR or if any transparent recipients are included.")
+ ((!isFromUa || strategy_.AllowLinkingAccountAddresses()) ? "" :
" (This transaction may require selecting transparent coins that were sent "
"to multiple Unified Addresses, which is not enabled by default because "
"it would create a public link between the transparent receivers of these "
"addresses. THIS MAY AFFECT YOUR PRIVACY. Resubmit with the `privacyPolicy` "
"parameter set to `AllowLinkingAccountAddresses` or weaker if you wish to "
"allow this transaction to proceed anyway.)"));
}
if (!(spendable.utxos.empty() || strategy_.AllowRevealedSenders())) {

View File

@ -80,7 +80,6 @@ private:
bool isfromsprout_{false};
bool isfromsapling_{false};
TransactionStrategy strategy_;
uint32_t transparentRecipients_{0};
AccountId sendFromAccount_;
std::set<OutputPool> recipientPools_;
TxOutputAmounts txOutputAmounts_;

View File

@ -2214,6 +2214,7 @@ SpendableInputs CWallet::FindSpendableInputs(
KeyIO keyIO(Params());
bool selectTransparent{selector.SelectsTransparent()};
bool selectTransparentCoinbase{selector.SelectsTransparentCoinbase()};
bool selectSprout{selector.SelectsSprout()};
bool selectSapling{selector.SelectsSapling()};
bool selectOrchard{selector.SelectsOrchard()};
@ -2229,7 +2230,7 @@ SpendableInputs CWallet::FindSpendableInputs(
if (selectTransparent &&
// skip transparent utxo selection if coinbase spend restrictions are not met
(!isCoinbase || (allowTransparentCoinbase && wtx.GetBlocksToMaturity(asOfHeight) <= 0))) {
(!isCoinbase || (selectTransparentCoinbase && allowTransparentCoinbase && wtx.GetBlocksToMaturity(asOfHeight) <= 0))) {
for (int i = 0; i < wtx.vout.size(); i++) {
const auto& output = wtx.vout[i];
@ -7822,6 +7823,23 @@ bool ZTXOSelector::SelectsTransparent() const {
[](const AccountZTXOPattern& acct) { return acct.IncludesP2PKH() || acct.IncludesP2SH(); }
}, this->pattern);
}
bool ZTXOSelector::SelectsTransparentCoinbase() const {
return std::visit(match {
[](const CKeyID& keyId) { return true; },
[](const CScriptID& scriptId) { return true; },
[](const libzcash::SproutPaymentAddress& addr) { return false; },
[](const libzcash::SproutViewingKey& vk) { return false; },
[](const libzcash::SaplingPaymentAddress& addr) { return false; },
[](const libzcash::SaplingExtendedFullViewingKey& vk) { return false; },
[](const libzcash::UnifiedAddress& ua) {
return ua.GetP2PKHReceiver().has_value() || ua.GetP2SHReceiver().has_value();
},
[](const libzcash::UnifiedFullViewingKey& ufvk) { return ufvk.GetTransparentKey().has_value(); },
[](const AccountZTXOPattern& acct) {
return (acct.IncludesP2PKH() || acct.IncludesP2SH()) && acct.GetAccountId() != ZCASH_LEGACY_ACCOUNT;
}
}, this->pattern);
}
bool ZTXOSelector::SelectsSprout() const {
return std::visit(match {
[](const libzcash::SproutViewingKey& addr) { return true; },

View File

@ -872,6 +872,7 @@ public:
}
bool SelectsTransparent() const;
bool SelectsTransparentCoinbase() const;
bool SelectsSprout() const;
bool SelectsSapling() const;
bool SelectsOrchard() const;