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:
parent
d2d0378943
commit
820af47019
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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_,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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(); },
|
||||
|
|
|
@ -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 they’re 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 can’t require transparent coinbase unless we’re 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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue