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:
parent
4a4ae4d3e2
commit
7abdade2cc
|
@ -67,12 +67,10 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||||
for (const SendManyRecipient& recipient : recipients_) {
|
for (const SendManyRecipient& recipient : recipients_) {
|
||||||
std::visit(match {
|
std::visit(match {
|
||||||
[&](const CKeyID& addr) {
|
[&](const CKeyID& addr) {
|
||||||
transparentRecipients_ += 1;
|
|
||||||
txOutputAmounts_.t_outputs_total += recipient.amount;
|
txOutputAmounts_.t_outputs_total += recipient.amount;
|
||||||
recipientPools_.insert(OutputPool::Transparent);
|
recipientPools_.insert(OutputPool::Transparent);
|
||||||
},
|
},
|
||||||
[&](const CScriptID& addr) {
|
[&](const CScriptID& addr) {
|
||||||
transparentRecipients_ += 1;
|
|
||||||
txOutputAmounts_.t_outputs_total += recipient.amount;
|
txOutputAmounts_.t_outputs_total += recipient.amount;
|
||||||
recipientPools_.insert(OutputPool::Transparent);
|
recipientPools_.insert(OutputPool::Transparent);
|
||||||
},
|
},
|
||||||
|
@ -181,7 +179,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
||||||
|
|
||||||
// Allow transparent coinbase inputs if there are no transparent
|
// Allow transparent coinbase inputs if there are no transparent
|
||||||
// recipients.
|
// recipients.
|
||||||
bool allowTransparentCoinbase = transparentRecipients_ == 0;
|
bool allowTransparentCoinbase = !recipientPools_.count(OutputPool::Transparent);
|
||||||
|
|
||||||
// Set the dust threshold so that we can select enough inputs to avoid
|
// Set the dust threshold so that we can select enough inputs to avoid
|
||||||
// creating dust change amounts.
|
// creating dust change amounts.
|
||||||
|
@ -196,6 +194,8 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
||||||
}
|
}
|
||||||
if (!spendable.LimitToAmount(targetAmount, dustThreshold, recipientPools_)) {
|
if (!spendable.LimitToAmount(targetAmount, dustThreshold, recipientPools_)) {
|
||||||
CAmount changeAmount{spendable.Total() - targetAmount};
|
CAmount changeAmount{spendable.Total() - targetAmount};
|
||||||
|
std::string insufficientFundsMessage =
|
||||||
|
strprintf("Insufficient funds: have %s", FormatMoney(spendable.Total()));
|
||||||
if (changeAmount > 0 && changeAmount < dustThreshold) {
|
if (changeAmount > 0 && changeAmount < dustThreshold) {
|
||||||
// TODO: we should provide the option for the caller to explicitly
|
// TODO: we should provide the option for the caller to explicitly
|
||||||
// forego change (definitionally an amount below the dust amount)
|
// forego change (definitionally an amount below the dust amount)
|
||||||
|
@ -203,23 +203,20 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
||||||
// creating dust change, rather than prohibit them from sending
|
// creating dust change, rather than prohibit them from sending
|
||||||
// entirely in this circumstance.
|
// entirely in this circumstance.
|
||||||
// (Daira disagrees, as this could leak information to the recipient)
|
// (Daira disagrees, as this could leak information to the recipient)
|
||||||
throw JSONRPCError(
|
insufficientFundsMessage +=
|
||||||
RPC_WALLET_INSUFFICIENT_FUNDS,
|
|
||||||
strprintf(
|
strprintf(
|
||||||
"Insufficient funds: have %s, need %s more to avoid creating invalid change output %s "
|
", need %s more to avoid creating invalid change output %s (dust threshold is %s)",
|
||||||
"(dust threshold is %s)",
|
|
||||||
FormatMoney(spendable.Total()),
|
|
||||||
FormatMoney(dustThreshold - changeAmount),
|
FormatMoney(dustThreshold - changeAmount),
|
||||||
FormatMoney(changeAmount),
|
FormatMoney(changeAmount),
|
||||||
FormatMoney(dustThreshold)));
|
FormatMoney(dustThreshold));
|
||||||
} else {
|
} else {
|
||||||
|
insufficientFundsMessage += strprintf(", need %s", FormatMoney(targetAmount));
|
||||||
|
}
|
||||||
bool isFromUa = std::holds_alternative<libzcash::UnifiedAddress>(ztxoSelector_.GetPattern());
|
bool isFromUa = std::holds_alternative<libzcash::UnifiedAddress>(ztxoSelector_.GetPattern());
|
||||||
throw JSONRPCError(
|
throw JSONRPCError(
|
||||||
RPC_WALLET_INSUFFICIENT_FUNDS,
|
RPC_WALLET_INSUFFICIENT_FUNDS,
|
||||||
strprintf(
|
insufficientFundsMessage
|
||||||
"Insufficient funds: have %s, need %s",
|
+ (allowTransparentCoinbase && ztxoSelector_.SelectsTransparentCoinbase() ? "" :
|
||||||
FormatMoney(spendable.Total()), FormatMoney(targetAmount))
|
|
||||||
+ (allowTransparentCoinbase ? "" :
|
|
||||||
"; note that coinbase outputs will not be selected if you specify "
|
"; note that coinbase outputs will not be selected if you specify "
|
||||||
"ANY_TADDR or if any transparent recipients are included.")
|
"ANY_TADDR or if any transparent recipients are included.")
|
||||||
+ ((!isFromUa || strategy_.AllowLinkingAccountAddresses()) ? "" :
|
+ ((!isFromUa || strategy_.AllowLinkingAccountAddresses()) ? "" :
|
||||||
|
@ -228,9 +225,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
||||||
"it would create a public link between the transparent receivers of these "
|
"it would create a public link between the transparent receivers of these "
|
||||||
"addresses. THIS MAY AFFECT YOUR PRIVACY. Resubmit with the `privacyPolicy` "
|
"addresses. THIS MAY AFFECT YOUR PRIVACY. Resubmit with the `privacyPolicy` "
|
||||||
"parameter set to `AllowLinkingAccountAddresses` or weaker if you wish to "
|
"parameter set to `AllowLinkingAccountAddresses` or weaker if you wish to "
|
||||||
"allow this transaction to proceed anyway.)")
|
"allow this transaction to proceed anyway.)"));
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(spendable.utxos.empty() || strategy_.AllowRevealedSenders())) {
|
if (!(spendable.utxos.empty() || strategy_.AllowRevealedSenders())) {
|
||||||
|
|
|
@ -80,7 +80,6 @@ private:
|
||||||
bool isfromsprout_{false};
|
bool isfromsprout_{false};
|
||||||
bool isfromsapling_{false};
|
bool isfromsapling_{false};
|
||||||
TransactionStrategy strategy_;
|
TransactionStrategy strategy_;
|
||||||
uint32_t transparentRecipients_{0};
|
|
||||||
AccountId sendFromAccount_;
|
AccountId sendFromAccount_;
|
||||||
std::set<OutputPool> recipientPools_;
|
std::set<OutputPool> recipientPools_;
|
||||||
TxOutputAmounts txOutputAmounts_;
|
TxOutputAmounts txOutputAmounts_;
|
||||||
|
|
|
@ -2214,6 +2214,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
||||||
KeyIO keyIO(Params());
|
KeyIO keyIO(Params());
|
||||||
|
|
||||||
bool selectTransparent{selector.SelectsTransparent()};
|
bool selectTransparent{selector.SelectsTransparent()};
|
||||||
|
bool selectTransparentCoinbase{selector.SelectsTransparentCoinbase()};
|
||||||
bool selectSprout{selector.SelectsSprout()};
|
bool selectSprout{selector.SelectsSprout()};
|
||||||
bool selectSapling{selector.SelectsSapling()};
|
bool selectSapling{selector.SelectsSapling()};
|
||||||
bool selectOrchard{selector.SelectsOrchard()};
|
bool selectOrchard{selector.SelectsOrchard()};
|
||||||
|
@ -2229,7 +2230,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
||||||
|
|
||||||
if (selectTransparent &&
|
if (selectTransparent &&
|
||||||
// skip transparent utxo selection if coinbase spend restrictions are not met
|
// 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++) {
|
for (int i = 0; i < wtx.vout.size(); i++) {
|
||||||
const auto& output = wtx.vout[i];
|
const auto& output = wtx.vout[i];
|
||||||
|
@ -7822,6 +7823,23 @@ bool ZTXOSelector::SelectsTransparent() const {
|
||||||
[](const AccountZTXOPattern& acct) { return acct.IncludesP2PKH() || acct.IncludesP2SH(); }
|
[](const AccountZTXOPattern& acct) { return acct.IncludesP2PKH() || acct.IncludesP2SH(); }
|
||||||
}, this->pattern);
|
}, 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 {
|
bool ZTXOSelector::SelectsSprout() const {
|
||||||
return std::visit(match {
|
return std::visit(match {
|
||||||
[](const libzcash::SproutViewingKey& addr) { return true; },
|
[](const libzcash::SproutViewingKey& addr) { return true; },
|
||||||
|
|
|
@ -872,6 +872,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SelectsTransparent() const;
|
bool SelectsTransparent() const;
|
||||||
|
bool SelectsTransparentCoinbase() const;
|
||||||
bool SelectsSprout() const;
|
bool SelectsSprout() const;
|
||||||
bool SelectsSapling() const;
|
bool SelectsSapling() const;
|
||||||
bool SelectsOrchard() const;
|
bool SelectsOrchard() const;
|
||||||
|
|
Loading…
Reference in New Issue