Unify requireTransparentCoinbase handling

Eliminates SelectsTransparentCoinbase in favor of a Disallow/Allow/Require ternary. Also asserts if
a selector requires transparent coinbase but doesn’t select transparent.
This commit is contained in:
Greg Pfeil 2023-02-13 22:50:41 -07:00
parent d2d0378943
commit 820af47019
No known key found for this signature in database
GPG Key ID: 1193ACD196ED61F2
13 changed files with 165 additions and 158 deletions

View File

@ -112,7 +112,7 @@ class WalletSendManyAnyTaddr(BitcoinTestFramework):
'ANY_TADDR',
[{'address': recipient, 'amount': 20}],
1, DEFAULT_FEE, 'AllowRevealedSenders')
wait_and_assert_operationid_status(self.nodes[3], myopid, "failed", "Insufficient funds: have 14.99998, need 20.00001; note that coinbase outputs will not be selected if you specify ANY_TADDR.")
wait_and_assert_operationid_status(self.nodes[3], myopid, "failed", "Insufficient funds: have 14.99998, need 20.00001; note that coinbase outputs will not be selected if you specify ANY_TADDR, any transparent recipients are included, or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.")
# Create an expired transaction on node 3.
self.split_network()
@ -144,7 +144,7 @@ class WalletSendManyAnyTaddr(BitcoinTestFramework):
'ANY_TADDR',
[{'address': recipient, 'amount': 13}],
1, DEFAULT_FEE, 'AllowRevealedSenders'),
"failed", "Insufficient funds: have 0.00, need 13.00001; note that coinbase outputs will not be selected if you specify ANY_TADDR.")
"failed", "Insufficient funds: have 0.00, need 13.00001; note that coinbase outputs will not be selected if you specify ANY_TADDR, any transparent recipients are included, or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.")
if __name__ == '__main__':
WalletSendManyAnyTaddr().main()

View File

@ -233,7 +233,7 @@ class WalletShieldingCoinbaseTest (BitcoinTestFramework):
amount = Decimal('10.0') - DEFAULT_FEE - Decimal('0.00000001') # this leaves change at 1 zatoshi less than dust threshold
recipients.append({"address":self.nodes[0].getnewaddress(), "amount":amount })
myopid = self.nodes[0].z_sendmany(mytaddr, recipients, 1)
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient funds: have 10.00, need 0.00000053 more to avoid creating invalid change output 0.00000001 (dust threshold is 0.00000054); note that coinbase outputs will not be selected if any transparent recipients are included or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.")
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient funds: have 10.00, need 0.00000053 more to avoid creating invalid change output 0.00000001 (dust threshold is 0.00000054); note that coinbase outputs will not be selected if you specify ANY_TADDR, any transparent recipients are included, or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.")
# Send will fail because send amount is too big, even when including coinbase utxos
errorString = ""
@ -247,9 +247,9 @@ class WalletShieldingCoinbaseTest (BitcoinTestFramework):
recipients = []
recipients.append({"address":self.nodes[1].getnewaddress(), "amount":Decimal('10000.0')})
myopid = self.nodes[0].z_sendmany(mytaddr, recipients, 1)
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient funds: have 10.00, need 10000.00001; note that coinbase outputs will not be selected if any transparent recipients are included or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.")
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient funds: have 10.00, need 10000.00001; note that coinbase outputs will not be selected if you specify ANY_TADDR, any transparent recipients are included, or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.")
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1, DEFAULT_FEE, 'AllowRevealedRecipients')
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient funds: have 9.99998, need 10000.00001; note that coinbase outputs will not be selected if any transparent recipients are included or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker; note that coinbase outputs will not be selected if you specify ANY_TADDR.")
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient funds: have 9.99998, need 10000.00001; note that coinbase outputs will not be selected if you specify ANY_TADDR, any transparent recipients are included, or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.")
# Send will fail because of insufficient funds unless sender uses coinbase utxos
try:

View File

@ -183,7 +183,7 @@ class WalletZSendmanyTest(BitcoinTestFramework):
# If we attempt to spend with the default privacy policy, z_sendmany
# fails because it needs to spend transparent coins in a transaction
# involving a Unified Address.
revealed_senders_msg = 'Insufficient funds: have 0.00, need 10.00; note that coinbase outputs will not be selected if any transparent recipients are included or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.'
revealed_senders_msg = 'Insufficient funds: have 0.00, need 10.00; note that coinbase outputs will not be selected if you specify ANY_TADDR, any transparent recipients are included, or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.'
opid = self.nodes[2].z_sendmany(source, recipients, 1, 0, 'AllowRevealedAmounts')
wait_and_assert_operationid_status(self.nodes[2], opid, 'failed', revealed_senders_msg)
@ -322,7 +322,7 @@ class WalletZSendmanyTest(BitcoinTestFramework):
# If we try to send 3 ZEC from n1ua0, it will fail with too-few funds.
recipients = [{"address":n0ua0, "amount":3}]
linked_addrs_msg = 'Insufficient funds: have 2.00, need 3.00; note that coinbase outputs will not be selected if any transparent recipients are included or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker. (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.)'
linked_addrs_msg = 'Insufficient funds: have 2.00, need 3.00; note that coinbase outputs will not be selected if you specify ANY_TADDR, any transparent recipients are included, or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker. (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.)'
opid = self.nodes[1].z_sendmany(n1ua0, recipients, 1, 0, 'AllowRevealedAmounts')
wait_and_assert_operationid_status(self.nodes[1], opid, 'failed', linked_addrs_msg)

View File

@ -1889,7 +1889,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (!zaddr.has_value()) {
return InitError(_("-mineraddress is not a valid " PACKAGE_NAME " address."));
}
auto ztxoSelector = pwalletMain->ZTXOSelectorForAddress(zaddr.value(), true, false, TransactionStrategy(PrivacyPolicy::FullPrivacy));
auto ztxoSelector = pwalletMain->ZTXOSelectorForAddress(
zaddr.value(),
true,
TransparentCoinbasePolicy::Allow,
TransactionStrategy(PrivacyPolicy::FullPrivacy));
minerAddressInLocalWallet = ztxoSelector.has_value();
}
if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) {

View File

@ -119,15 +119,11 @@ void ThrowInputSelectionError(
}
},
err.reason))
+ (err.transparentCoinbasePermitted
? "" :
"; note that coinbase outputs will not be selected if any transparent "
"recipients are included or if the `privacyPolicy` parameter is not set to "
"`AllowRevealedSenders` or weaker")
+ (selector.SelectsTransparentCoinbase()
+ (selector.TransparentCoinbasePolicy() != TransparentCoinbasePolicy::Disallow
? "" :
"; note that coinbase outputs will not be selected if you specify "
"ANY_TADDR")
"ANY_TADDR, any transparent recipients are included, or if the "
"`privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker")
+ (!isFromUa || strategy.AllowLinkingAccountAddresses() ? "." :
". (This transaction may require selecting transparent coins that were sent "
"to multiple Unified Addresses, which is not enabled by default because "

View File

@ -138,10 +138,7 @@ void AsyncRPCOperation_sendmany::main() {
//
// At least #4 differs from the Rust transaction builder.
uint256 AsyncRPCOperation_sendmany::main_impl() {
auto spendable = builder_.FindAllSpendableInputs(
ztxoSelector_,
WalletTxBuilder::AllowTransparentCoinbase(recipients_, strategy_),
mindepth_);
auto spendable = builder_.FindAllSpendableInputs(ztxoSelector_, mindepth_);
auto preparedTx = builder_.PrepareTransaction(
ztxoSelector_,

View File

@ -290,7 +290,11 @@ TEST(WalletRPCTests, RPCZsendmanyTaddrToSapling)
// we need AllowFullyTransparent because the transaction will result
// in transparent change as a consequence of sending from a legacy taddr
TransactionStrategy strategy(PrivacyPolicy::AllowFullyTransparent);
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr, true, false, strategy).value();
auto selector = pwalletMain->ZTXOSelectorForAddress(
taddr,
true,
TransparentCoinbasePolicy::Disallow,
strategy).value();
std::vector<Payment> recipients = { Payment(pa, 1*COIN, Memo::FromHexOrThrow("ABCD")) };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0, 0, strategy));
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);

View File

@ -3869,13 +3869,13 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
nBalance = getBalanceZaddr(addr, std::nullopt, nMinDepth, INT_MAX, false);
},
[&](const libzcash::UnifiedAddress& addr) {
auto selector = pwalletMain->ZTXOSelectorForAddress(addr, true, false, TransactionStrategy(PrivacyPolicy::FullPrivacy));
auto selector = pwalletMain->ZTXOSelectorForAddress(addr, true, TransparentCoinbasePolicy::Allow, TransactionStrategy(PrivacyPolicy::FullPrivacy));
if (!selector.has_value()) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Unified address does not correspond to an account in the wallet");
}
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, nMinDepth, std::nullopt);
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), nMinDepth, std::nullopt);
for (const auto& t : spendableInputs.utxos) {
nBalance += t.Value();
@ -3960,14 +3960,14 @@ UniValue z_getbalanceforviewingkey(const UniValue& params, bool fHelp)
// FVKs make it possible to correctly determine balance without having the
// spending key, so we permit that here.
bool requireSpendingKey = std::holds_alternative<libzcash::SproutViewingKey>(fvk);
auto selector = pwalletMain->ZTXOSelectorForViewingKey(fvk, requireSpendingKey, false);
auto selector = pwalletMain->ZTXOSelectorForViewingKey(fvk, requireSpendingKey, TransparentCoinbasePolicy::Allow);
if (!selector.has_value()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Error: the wallet does not recognize the specified viewing key.");
}
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, minconf, asOfHeight);
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), minconf, asOfHeight);
CAmount transparentBalance = 0;
CAmount sproutBalance = 0;
@ -4058,14 +4058,14 @@ UniValue z_getbalanceforaccount(const UniValue& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet);
// Get the receivers for this account.
auto selector = pwalletMain->ZTXOSelectorForAccount(account, false, false);
auto selector = pwalletMain->ZTXOSelectorForAccount(account, false, TransparentCoinbasePolicy::Allow);
if (!selector.has_value()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
tfm::format("Error: account %d has not been generated by z_getnewaccount.", account));
}
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, minconf, asOfHeight);
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), minconf, asOfHeight);
// Accounts never contain Sprout notes.
assert(spendableInputs.sproutNoteEntries.empty());
@ -4795,62 +4795,16 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
}
}
bool involvesUnifiedAddress = false;
// Check that the from address is valid.
// Unified address (UA) allowed here (#5185)
auto fromaddress = params[0].get_str();
ZTXOSelector ztxoSelector = [&]() {
if (fromaddress == "ANY_TADDR") {
return CWallet::LegacyTransparentZTXOSelector(true, false);
} else {
auto decoded = keyIO.DecodePaymentAddress(fromaddress);
if (!decoded.has_value()) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Invalid from address: should be a taddr, zaddr, UA, or the string 'ANY_TADDR'.");
}
auto ztxoSelectorOpt = pwalletMain->ZTXOSelectorForAddress(
decoded.value(),
true,
false,
// LegacyCompat does not include AllowLinkingAccountAddresses.
maybeStrategy.value_or(TransactionStrategy(PrivacyPolicy::FullPrivacy)));
if (!ztxoSelectorOpt.has_value()) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Invalid from address, no payment source found for address.");
}
auto selectorAccount = pwalletMain->FindAccountForSelector(ztxoSelectorOpt.value());
std::visit(match {
[&](const libzcash::UnifiedAddress& ua) {
if (!selectorAccount.has_value() || selectorAccount.value() == ZCASH_LEGACY_ACCOUNT) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Invalid from address, UA does not correspond to a known account.");
}
involvesUnifiedAddress = true;
},
[&](const auto& other) {
if (selectorAccount.has_value() && selectorAccount.value() != ZCASH_LEGACY_ACCOUNT) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Invalid from address: is a bare receiver from a Unified Address in this wallet. Provide the UA as returned by z_getaddressforaccount instead.");
}
}
}, decoded.value());
return ztxoSelectorOpt.value();
}
}();
UniValue outputs = params[1].get_array();
if (outputs.size() == 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty.");
}
bool involvesUnifiedAddress = false;
auto tcoinbasePolicy =
maybeStrategy.has_value() && maybeStrategy.value().AllowRevealedSenders()
? TransparentCoinbasePolicy::Allow
: TransparentCoinbasePolicy::Disallow;
std::set<PaymentAddress> recipientAddrs;
std::vector<Payment> recipients;
CAmount nTotalOut = 0;
@ -4914,6 +4868,32 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive");
}
std::visit(match {
[&](const CKeyID &) {
tcoinbasePolicy = TransparentCoinbasePolicy::Disallow;
},
[&](const CScriptID &) {
tcoinbasePolicy = TransparentCoinbasePolicy::Disallow;
},
[&](const UnifiedAddress &ua) {
involvesUnifiedAddress = true;
auto preferredRecipient =
ua.GetPreferredRecipientAddress(chainparams.GetConsensus(), nextBlockHeight);
if (preferredRecipient.has_value()) {
std::visit(match {
[&](const CKeyID &) {
tcoinbasePolicy = TransparentCoinbasePolicy::Disallow;
},
[&](const CScriptID &) {
tcoinbasePolicy = TransparentCoinbasePolicy::Disallow;
},
[](const auto &) { }
}, preferredRecipient.value());
}
},
[](const auto &) { }
}, addr.value());
recipients.push_back(Payment(addr.value(), nAmount, memo));
nTotalOut += nAmount;
}
@ -4921,6 +4901,55 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_INVALID_PARAMETER, "No recipients");
}
// Check that the from address is valid.
// Unified address (UA) allowed here (#5185)
auto fromaddress = params[0].get_str();
ZTXOSelector ztxoSelector = [&]() {
if (fromaddress == "ANY_TADDR") {
return CWallet::LegacyTransparentZTXOSelector(true, TransparentCoinbasePolicy::Disallow);
} else {
auto decoded = keyIO.DecodePaymentAddress(fromaddress);
if (!decoded.has_value()) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Invalid from address: should be a taddr, zaddr, UA, or the string 'ANY_TADDR'.");
}
auto ztxoSelectorOpt = pwalletMain->ZTXOSelectorForAddress(
decoded.value(),
true,
tcoinbasePolicy,
// LegacyCompat does not include AllowLinkingAccountAddresses.
maybeStrategy.value_or(TransactionStrategy(PrivacyPolicy::FullPrivacy)));
if (!ztxoSelectorOpt.has_value()) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Invalid from address, no payment source found for address.");
}
auto selectorAccount = pwalletMain->FindAccountForSelector(ztxoSelectorOpt.value());
std::visit(match {
[&](const libzcash::UnifiedAddress& ua) {
if (!selectorAccount.has_value() || selectorAccount.value() == ZCASH_LEGACY_ACCOUNT) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Invalid from address, UA does not correspond to a known account.");
}
involvesUnifiedAddress = true;
},
[&](const auto& other) {
if (selectorAccount.has_value() && selectorAccount.value() != ZCASH_LEGACY_ACCOUNT) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Invalid from address: is a bare receiver from a Unified Address in this wallet. Provide the UA as returned by z_getaddressforaccount instead.");
}
}
}, decoded.value());
return ztxoSelectorOpt.value();
}
}();
// Now that we've set involvesUnifiedAddress correctly, we can finish
// evaluating the strategy.
TransactionStrategy strategy = maybeStrategy.value_or(

View File

@ -803,7 +803,11 @@ void CheckHaveAddr(const std::optional<libzcash::PaymentAddress>& addr) {
BOOST_ASSERT(addr_of_type != nullptr);
TransactionStrategy strategy(PrivacyPolicy::FullPrivacy);
BOOST_CHECK(pwalletMain->ZTXOSelectorForAddress(*addr_of_type, true, false, strategy).has_value());
BOOST_CHECK(pwalletMain->ZTXOSelectorForAddress(
*addr_of_type,
true,
TransparentCoinbasePolicy::Allow,
strategy).has_value());
}
BOOST_AUTO_TEST_CASE(rpc_wallet_z_getnewaddress) {
@ -1238,7 +1242,11 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
// there are no utxos to spend
{
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedSenders);
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr1, true, false, strategy).value();
auto selector = pwalletMain->ZTXOSelectorForAddress(
taddr1,
true,
TransparentCoinbasePolicy::Require,
strategy).value();
WalletTxBuilder builder(Params(), *pwalletMain, minRelayTxFee);
std::vector<Payment> recipients = { Payment(zaddr1, 100*COIN, Memo::FromHexOrThrow("DEADBEEF")) };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy));
@ -1251,7 +1259,11 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
// there are no unspent notes to spend
{
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedRecipients);
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true, false, strategy).value();
auto selector = pwalletMain->ZTXOSelectorForAddress(
zaddr1,
true,
TransparentCoinbasePolicy::Disallow,
strategy).value();
WalletTxBuilder builder(Params(), *pwalletMain, minRelayTxFee);
std::vector<Payment> recipients = { Payment(taddr1, 100*COIN, Memo::FromHexOrThrow("DEADBEEF")) };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy));

View File

@ -1871,7 +1871,7 @@ void CWallet::SyncMetaData(pair<typename TxSpendMap<T>::iterator, typename TxSpe
std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAccount(
libzcash::AccountId account,
bool requireSpendingKey,
bool requireTransparentCoinbase,
TransparentCoinbasePolicy transparentCoinbasePolicy,
std::set<libzcash::ReceiverType> receiverTypes) const
{
if (mnemonicHDChain.has_value() &&
@ -1882,7 +1882,7 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAccount(
return ZTXOSelector(
AccountZTXOPattern(account, receiverTypes),
requireSpendingKey,
requireTransparentCoinbase);
transparentCoinbasePolicy);
} else {
return std::nullopt;
}
@ -1891,7 +1891,7 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAccount(
std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAddress(
const libzcash::PaymentAddress& addr,
bool requireSpendingKey,
bool requireTransparentCoinbase,
TransparentCoinbasePolicy transparentCoinbasePolicy,
const TransactionStrategy& strategy) const
{
auto self = this;
@ -1942,7 +1942,7 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAddress(
}, addr);
if (pattern.has_value()) {
return ZTXOSelector(pattern.value(), requireSpendingKey, requireTransparentCoinbase);
return ZTXOSelector(pattern.value(), requireSpendingKey, transparentCoinbasePolicy);
} else {
return std::nullopt;
}
@ -1951,7 +1951,7 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAddress(
std::optional<ZTXOSelector> CWallet::ZTXOSelectorForViewingKey(
const libzcash::ViewingKey& vk,
bool requireSpendingKey,
bool requireTransparentCoinbase) const
TransparentCoinbasePolicy transparentCoinbasePolicy) const
{
auto self = this;
std::optional<ZTXOPattern> pattern = std::nullopt;
@ -1978,17 +1978,17 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForViewingKey(
}, vk);
if (pattern.has_value()) {
return ZTXOSelector(pattern.value(), requireSpendingKey, requireTransparentCoinbase);
return ZTXOSelector(pattern.value(), requireSpendingKey, transparentCoinbasePolicy);
} else {
return std::nullopt;
}
}
ZTXOSelector CWallet::LegacyTransparentZTXOSelector(bool requireSpendingKey, bool coinbaseOnly) {
ZTXOSelector CWallet::LegacyTransparentZTXOSelector(bool requireSpendingKey, TransparentCoinbasePolicy transparentCoinbasePolicy) {
return ZTXOSelector(
AccountZTXOPattern(ZCASH_LEGACY_ACCOUNT, {ReceiverType::P2PKH, ReceiverType::P2SH}),
requireSpendingKey,
coinbaseOnly);
transparentCoinbasePolicy);
}
std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSelector& selector) const {
@ -2216,7 +2216,6 @@ std::optional<RecipientAddress> CWallet::GenerateChangeAddressForAccount(
SpendableInputs CWallet::FindSpendableInputs(
ZTXOSelector selector,
bool allowTransparentCoinbase,
uint32_t minDepth,
const std::optional<int>& asOfHeight) const {
AssertLockHeld(cs_main);
@ -2225,7 +2224,6 @@ 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()};
@ -2241,9 +2239,11 @@ SpendableInputs CWallet::FindSpendableInputs(
if (selectTransparent &&
// skip transparent utxo selection if coinbase spend restrictions are not met
(!isCoinbase || (selectTransparentCoinbase && allowTransparentCoinbase && wtx.GetBlocksToMaturity(asOfHeight) <= 0)) &&
(!isCoinbase
|| (selector.transparentCoinbasePolicy != TransparentCoinbasePolicy::Disallow
&& wtx.GetBlocksToMaturity(asOfHeight) <= 0))
// select only transparent utxos if RequireTransparentCoinbase
(isCoinbase || !selector.RequireTransparentCoinbase())) {
&& (isCoinbase || selector.transparentCoinbasePolicy != TransparentCoinbasePolicy::Require)) {
for (int i = 0; i < wtx.vout.size(); i++) {
const auto& output = wtx.vout[i];
@ -2273,7 +2273,7 @@ SpendableInputs CWallet::FindSpendableInputs(
}
}
if (selectSprout && !selector.RequireTransparentCoinbase()) {
if (selectSprout) {
for (auto const& [jsop, nd] : wtx.mapSproutNoteData) {
SproutPaymentAddress pa = nd.address;
@ -2328,7 +2328,7 @@ SpendableInputs CWallet::FindSpendableInputs(
}
}
if (selectSapling && !selector.RequireTransparentCoinbase()) {
if (selectSapling) {
for (auto const& [op, nd] : wtx.mapSaplingNoteData) {
auto optDeserialized = SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(wtx.vShieldedOutput[op.n].encCiphertext, nd.ivk, wtx.vShieldedOutput[op.n].ephemeralKey);
@ -2357,7 +2357,7 @@ SpendableInputs CWallet::FindSpendableInputs(
}
}
if (selectOrchard && !selector.RequireTransparentCoinbase()) {
if (selectOrchard) {
// for Orchard, we select both the internal and external IVKs.
auto orchardIvks = std::visit(match {
[&](const libzcash::UnifiedAddress& selectorUA) -> std::vector<OrchardIncomingViewingKey> {
@ -7869,32 +7869,15 @@ 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 {
return transparentCoinbasePolicy != TransparentCoinbasePolicy::Require && std::visit(match {
[](const libzcash::SproutViewingKey& addr) { return true; },
[](const libzcash::SproutPaymentAddress& extfvk) { return true; },
[](const auto& addr) { return false; }
}, this->pattern);
}
bool ZTXOSelector::SelectsSapling() const {
return std::visit(match {
return transparentCoinbasePolicy != TransparentCoinbasePolicy::Require && std::visit(match {
[](const libzcash::SaplingPaymentAddress& addr) { return true; },
[](const libzcash::SaplingExtendedSpendingKey& extfvk) { return true; },
[](const libzcash::UnifiedAddress& ua) { return ua.GetSaplingReceiver().has_value(); },
@ -7904,7 +7887,7 @@ bool ZTXOSelector::SelectsSapling() const {
}, this->pattern);
}
bool ZTXOSelector::SelectsOrchard() const {
return std::visit(match {
return transparentCoinbasePolicy != TransparentCoinbasePolicy::Require && std::visit(match {
[](const libzcash::UnifiedAddress& ua) { return ua.GetOrchardReceiver().has_value(); },
[](const libzcash::UnifiedFullViewingKey& ufvk) { return ufvk.GetOrchardKey().has_value(); },
[](const AccountZTXOPattern& acct) { return acct.IncludesOrchard(); },

View File

@ -897,14 +897,27 @@ typedef std::variant<
libzcash::UnifiedFullViewingKey,
AccountZTXOPattern> ZTXOPattern;
/**
* For transactions, either `Disallow` or `Require` must be used, but `Allow` is generally used when
* calculating balances.
*/
enum class TransparentCoinbasePolicy {
Disallow, //!< Do not select transparent coinbase
Allow, //!< Select all transparent UTXOs, whether or not theyre coinbase
Require //!< Only select transparent coinbase
};
class ZTXOSelector {
private:
ZTXOPattern pattern;
bool requireSpendingKeys;
bool requireTransparentCoinbase;
TransparentCoinbasePolicy transparentCoinbasePolicy;
ZTXOSelector(ZTXOPattern patternIn, bool requireSpendingKeysIn, bool requireCoinbaseIn):
pattern(patternIn), requireSpendingKeys(requireSpendingKeysIn), requireTransparentCoinbase(requireCoinbaseIn) {}
ZTXOSelector(ZTXOPattern patternIn, bool requireSpendingKeysIn, TransparentCoinbasePolicy transparentCoinbasePolicy):
pattern(patternIn), requireSpendingKeys(requireSpendingKeysIn), transparentCoinbasePolicy(transparentCoinbasePolicy) {
// We cant require transparent coinbase unless were selecting transparent funds.
assert(SelectsTransparent() || transparentCoinbasePolicy != TransparentCoinbasePolicy::Require);
}
friend class CWallet;
public:
@ -916,12 +929,11 @@ public:
return requireSpendingKeys;
}
bool RequireTransparentCoinbase() const {
return requireTransparentCoinbase;
TransparentCoinbasePolicy TransparentCoinbasePolicy() const {
return transparentCoinbasePolicy;
}
bool SelectsTransparent() const;
bool SelectsTransparentCoinbase() const;
bool SelectsSprout() const;
bool SelectsSapling() const;
bool SelectsOrchard() const;
@ -1545,7 +1557,7 @@ public:
std::optional<ZTXOSelector> ZTXOSelectorForAccount(
libzcash::AccountId account,
bool requireSpendingKey,
bool requireTransparentCoinbase,
TransparentCoinbasePolicy transparentCoinbasePolicy,
std::set<libzcash::ReceiverType> receiverTypes={}) const;
/**
@ -1557,7 +1569,7 @@ public:
std::optional<ZTXOSelector> ZTXOSelectorForAddress(
const libzcash::PaymentAddress& addr,
bool requireSpendingKey,
bool requireTransparentCoinbase,
TransparentCoinbasePolicy transparentCoinbasePolicy,
const TransactionStrategy& strategy) const;
/**
@ -1569,13 +1581,13 @@ public:
std::optional<ZTXOSelector> ZTXOSelectorForViewingKey(
const libzcash::ViewingKey& vk,
bool requireSpendingKey,
bool requireTransparentCoinbase) const;
TransparentCoinbasePolicy transparentCoinbasePolicy) const;
/**
* Returns the ZTXO selector that will select UTXOs sent to legacy
* transparent addresses managed by this wallet.
*/
static ZTXOSelector LegacyTransparentZTXOSelector(bool requireSpendingKey, bool requireTransparentCoinbase);
static ZTXOSelector LegacyTransparentZTXOSelector(bool requireSpendingKey, TransparentCoinbasePolicy transparentCoinbasePolicy);
/**
* Look up the account for a given selector. This resolves the account ID
@ -1605,7 +1617,6 @@ public:
SpendableInputs FindSpendableInputs(
ZTXOSelector paymentSource,
bool allowTransparentCoinbase,
uint32_t minDepth,
const std::optional<int>& asOfHeight) const;

View File

@ -35,7 +35,6 @@ PrepareTransactionResult WalletTxBuilder::PrepareTransaction(
if (spendable.Total() < resolvedPayments.Total() + fee) {
return InvalidFundsError(
spendable.Total(),
AllowTransparentCoinbase(payments, strategy),
InsufficientFundsError(resolvedPayments.Total() + fee));
}
@ -207,31 +206,10 @@ CAmount WalletTxBuilder::DefaultDustThreshold() const {
SpendableInputs WalletTxBuilder::FindAllSpendableInputs(
const ZTXOSelector& selector,
bool allowTransparentCoinbase,
int32_t minDepth) const
{
LOCK2(cs_main, wallet.cs_wallet);
return wallet.FindSpendableInputs(selector, allowTransparentCoinbase, minDepth, std::nullopt);
}
bool WalletTxBuilder::AllowTransparentCoinbase(
const std::vector<Payment>& payments,
TransactionStrategy strategy)
{
bool allowed = strategy.AllowRevealedSenders();
for (const auto& payment : payments) {
if (!allowed) break;
allowed &= std::visit(match {
[](const CKeyID& p2pkh) { return false; },
[](const CScriptID& p2sh) { return false; },
[](const SproutPaymentAddress& addr) { return false; },
[](const SaplingPaymentAddress& addr) { return true; },
[](const UnifiedAddress& ua) {
return ua.GetSaplingReceiver().has_value() || ua.GetOrchardReceiver().has_value();
}
}, payment.GetAddress());
}
return allowed;
return wallet.FindSpendableInputs(selector, minDepth, std::nullopt);
}
InputSelectionResult WalletTxBuilder::ResolveInputsAndPayments(
@ -369,7 +347,6 @@ InputSelectionResult WalletTxBuilder::ResolveInputsAndPayments(
CAmount changeAmount{spendableMut.Total() - targetAmount};
return InvalidFundsError(
spendableMut.Total(),
AllowTransparentCoinbase(payments, strategy),
changeAmount > 0 && changeAmount < dustThreshold
// TODO: we should provide the option for the caller to explicitly
// forego change (definitionally an amount below the dust amount)

View File

@ -240,11 +240,10 @@ typedef std::variant<
class InvalidFundsError {
public:
CAmount available;
bool transparentCoinbasePermitted;
const InvalidFundsReason reason;
InvalidFundsError(CAmount available, bool transparentCoinbasePermitted, const InvalidFundsReason reason):
available(available), transparentCoinbasePermitted(transparentCoinbasePermitted), reason(reason) { }
InvalidFundsError(CAmount available, const InvalidFundsReason reason):
available(available), reason(reason) { }
};
class ChangeNotAllowedError {
@ -327,13 +326,8 @@ public:
WalletTxBuilder(const CChainParams& params, const CWallet& wallet, CFeeRate minRelayFee):
params(params), wallet(wallet), minRelayFee(minRelayFee), maxOrchardActions(nOrchardActionLimit) {}
static bool AllowTransparentCoinbase(
const std::vector<Payment>& payments,
TransactionStrategy strategy);
SpendableInputs FindAllSpendableInputs(
const ZTXOSelector& selector,
bool allowTransparentCoinbase,
int32_t minDepth) const;
PrepareTransactionResult PrepareTransaction(