Refactor RPC privacyPolicy handling

Extracted from z_sendmany to be used across multiple transaction
operations. Previously, it also checked a `bool` to decide what “LegacyCompat”
means, but bools are too easy to concoct, so it now expects the sender and
recipients to be checked internally. Also, z_sendmany was generating the bool
only from the sender, illustrating how easy it is to miss something when you try
to precompute.
This commit is contained in:
Kris Nuttycombe 2023-03-28 14:26:24 -06:00 committed by Greg Pfeil
parent 4892bf327d
commit f60d2c71e7
No known key found for this signature in database
GPG Key ID: 1193ACD196ED61F2
1 changed files with 86 additions and 27 deletions

View File

@ -298,6 +298,84 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr
}
}
// We accept any `PrivacyPolicy` constructor name, but there is also a special
// “LegacyCompat” policy that maps to a different `PrivacyPolicy` depending on
// other aspects of the transaction. Here we use `std::nullopt` for the
// “LegacyCompat” case and resolve that when we have more context.
//
// We need to know the privacy policy before we construct the ZTXOSelector, but
// we can't determine what “LegacyCompat” maps to without knowing whether any
// UAs are involved. We break this cycle by parsing the privacy policy argument
// first, and then resolving “LegacyCompat” after parsing the rest of the
// arguments. This works because all interpretations for “LegacyCompat” have the
// same effect on ZTXOSelector construction (in that they don't include
// `AllowLinkingAccountAddresses`).
std::optional<TransactionStrategy>
ReifyPrivacyPolicy(const std::optional<PrivacyPolicy>& defaultPolicy,
const std::optional<std::string>& specifiedPolicy)
{
std::optional<TransactionStrategy> strategy = std::nullopt;
if (specifiedPolicy.has_value()) {
auto strategyName = specifiedPolicy.value();
if (strategyName == "LegacyCompat") {
strategy = std::nullopt;
} else {
strategy = TransactionStrategy::FromString(strategyName);
if (!strategy.has_value()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Unknown privacy policy name '%s'", strategyName));
}
}
} else if (defaultPolicy.has_value()) {
strategy = TransactionStrategy(defaultPolicy.value());
}
return strategy;
}
// Determines which `TransactionStrategy` should be used for the “LegacyCompat”
// policy given the set of addresses involved.
PrivacyPolicy
InterpretLegacyCompat(const ZTXOSelector& sender,
const std::set<PaymentAddress>& recipients)
{
auto legacyCompatPolicy = PrivacyPolicy::FullPrivacy;
if (fEnableLegacyPrivacyStrategy) {
bool hasUASender =
std::visit(match {
[&](const UnifiedAddress& _) { return true; },
[&](const UnifiedFullViewingKey& _) { return true; },
[&](const AccountZTXOPattern& acct) {
return acct.GetAccountId() != ZCASH_LEGACY_ACCOUNT;
},
[&](auto _) { return false; }
}, sender.GetPattern());
bool hasUARecipient =
std::find_if(recipients.begin(), recipients.end(),
[](const PaymentAddress& addr) {
return std::holds_alternative<UnifiedAddress>(addr);
})
!= recipients.end();
if (!hasUASender && !hasUARecipient) {
legacyCompatPolicy = PrivacyPolicy::AllowFullyTransparent;
}
}
return legacyCompatPolicy;
}
// Provides the final `TransactionStrategy` to be used for a transaction.
TransactionStrategy
ResolveTransactionStrategy(
const std::optional<TransactionStrategy>& maybeStrategy,
PrivacyPolicy defaultPolicy)
{
return maybeStrategy.value_or(TransactionStrategy(defaultPolicy));
}
UniValue sendtoaddress(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
@ -4782,25 +4860,10 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
KeyIO keyIO(chainparams);
// We need to know the privacy policy before we construct the ZTXOSelector,
// but we can't determine the default privacy policy without knowing whether
// any UAs are involved. We break this cycle by parsing the privacy policy
// argument first, and then resolving it to the default after parsing the
// rest of the arguments. This works because all possible defaults for the
// privacy policy have the same effect on ZTXOSelector construction (in that
// they don't include AllowLinkingAccountAddresses).
std::optional<TransactionStrategy> maybeStrategy;
if (params.size() > 4) {
auto strategyName = params[4].get_str();
if (strategyName != "LegacyCompat") {
maybeStrategy = TransactionStrategy::FromString(strategyName);
if (!maybeStrategy.has_value()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Unknown privacy policy name '%s'", strategyName));
}
}
}
auto maybeStrategy =
ReifyPrivacyPolicy(
std::nullopt,
params.size() > 4 ? std::optional(params[4].get_str()) : std::nullopt);
UniValue outputs = params[1].get_array();
if (outputs.size() == 0) {
@ -4952,14 +5015,10 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
}
}();
// Now that we've set involvesUnifiedAddress correctly, we can finish
// evaluating the strategy.
TransactionStrategy strategy = maybeStrategy.value_or(
// Default privacy policy is "LegacyCompat".
(involvesUnifiedAddress || !fEnableLegacyPrivacyStrategy) ?
TransactionStrategy(PrivacyPolicy::FullPrivacy) :
TransactionStrategy(PrivacyPolicy::AllowFullyTransparent)
);
auto strategy =
ResolveTransactionStrategy(
maybeStrategy,
InterpretLegacyCompat(ztxoSelector, recipientAddrs));
// Sanity check for transaction size
// TODO: move this to the builder?