Replace the badly-named `PaymentSource` with `ZTXOSelector`
The ZTXOSelector type allows selection of previous Zcash transaction outputs (both transparent outputs and shielded notes) on the basis of either a (legacy) bare address, or for a BIP-44 account.
This commit is contained in:
parent
c4a7f5c95c
commit
f850e89449
|
@ -86,7 +86,7 @@ class WalletShieldingCoinbaseTest (BitcoinTestFramework):
|
|||
myopid = self.nodes[3].z_sendmany(mytaddr, recipients)
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Invalid from address, no spending key found for address", errorString);
|
||||
assert_equal("Invalid from address, no payment source found for address.", errorString);
|
||||
|
||||
# This send will fail because our consensus does not allow transparent change when
|
||||
# shielding a coinbase utxo.
|
||||
|
|
|
@ -45,13 +45,13 @@ using namespace libzcash;
|
|||
|
||||
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||
TransactionBuilder builder,
|
||||
PaymentSource paymentSource,
|
||||
ZTXOSelector ztxoSelector,
|
||||
std::vector<SendManyRecipient> recipients,
|
||||
int minDepth,
|
||||
CAmount fee,
|
||||
bool allowRevealedAmounts,
|
||||
UniValue contextInfo) :
|
||||
builder_(builder), paymentSource_(paymentSource), recipients_(recipients),
|
||||
builder_(builder), ztxoSelector_(ztxoSelector), recipients_(recipients),
|
||||
mindepth_(minDepth), fee_(fee), allowRevealedAmounts_(allowRevealedAmounts),
|
||||
contextinfo_(contextInfo)
|
||||
{
|
||||
|
@ -60,8 +60,8 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
|||
assert(!recipients_.empty());
|
||||
|
||||
std::visit(match {
|
||||
[&](const FromAnyTaddr& any) {
|
||||
isfromtaddr_ = true;
|
||||
[&](const AccountZTXOSelector& acct) {
|
||||
isfromtaddr_ = (acct.GetAccountId() == ZCASH_LEGACY_ACCOUNT);
|
||||
},
|
||||
[&](const PaymentAddress& addr) {
|
||||
// We don't need to lock on the wallet as spending key related methods are thread-safe
|
||||
|
@ -91,7 +91,7 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
|||
}
|
||||
}, addr);
|
||||
}
|
||||
}, paymentSource);
|
||||
}, ztxoSelector);
|
||||
|
||||
if ((isfromsprout_ || isfromsapling_) && minDepth == 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be zero when sending from a shielded address");
|
||||
|
@ -197,15 +197,15 @@ void AsyncRPCOperation_sendmany::main() {
|
|||
LogPrintf("%s",s);
|
||||
}
|
||||
|
||||
bool IsFromAnyTaddr(const PaymentSource& paymentSource) {
|
||||
bool IsFromAnyTaddr(const ZTXOSelector& ztxoSelector) {
|
||||
return std::visit(match {
|
||||
[&](const FromAnyTaddr& fromAny) {
|
||||
return true;
|
||||
[&](const AccountZTXOSelector& fa) {
|
||||
return fa.GetAccountId() == ZCASH_LEGACY_ACCOUNT;
|
||||
},
|
||||
[&](const PaymentAddress& addr) {
|
||||
[&](const auto& other) {
|
||||
return false;
|
||||
}
|
||||
}, paymentSource);
|
||||
}, ztxoSelector);
|
||||
}
|
||||
|
||||
// Construct and send the transaction, returning the resulting txid.
|
||||
|
@ -234,7 +234,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
|
||||
// Only select coinbase if we are spending from at most a single t-address.
|
||||
bool allowTransparentCoinbase =
|
||||
!IsFromAnyTaddr(paymentSource_) && // allow coinbase inputs from at most a single t-addr
|
||||
!IsFromAnyTaddr(ztxoSelector_) && // allow coinbase inputs from at most a single t-addr
|
||||
transparentRecipients_ == 0; // cannot send transparent coinbase to transparent recipients
|
||||
|
||||
// Set the dust threshold so that we can select enough inputs to avoid
|
||||
|
@ -243,7 +243,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
|
||||
// Find spendable inputs, and select a minimal set of them that
|
||||
// can supply the required target amount.
|
||||
auto spendable = pwalletMain->FindSpendableInputs(paymentSource_, allowTransparentCoinbase, mindepth_);
|
||||
auto spendable = pwalletMain->FindSpendableInputs(ztxoSelector_, allowTransparentCoinbase, mindepth_);
|
||||
if (!spendable.LimitToAmount(targetAmount, dustThreshold)) {
|
||||
CAmount changeAmount{spendable.Total() - targetAmount};
|
||||
if (changeAmount > 0 && changeAmount < dustThreshold) {
|
||||
|
@ -368,34 +368,26 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
builder_.SendChangeTo(changeAddr);
|
||||
};
|
||||
|
||||
// FIXME: it would be better to use the most recent shielded pool change
|
||||
// address for the wallet's default unified address account, and the
|
||||
// FIXME: use the appropriate shielded pool change address for the
|
||||
// source unified address account (or the legacy account), and the
|
||||
// associated OVK
|
||||
std::visit(match {
|
||||
[&](const FromAnyTaddr& fromAny) {
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
ovk = getDefaultOVK();
|
||||
builder_.SendChangeTo(addr);
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
libzcash::SaplingExtendedSpendingKey saplingKey;
|
||||
assert(pwalletMain->GetSaplingExtendedSpendingKey(addr, saplingKey));
|
||||
|
||||
ovk = saplingKey.expsk.full_viewing_key().ovk;
|
||||
builder_.SendChangeTo(addr, ovk);
|
||||
},
|
||||
[&](const auto& other) {
|
||||
ovk = getDefaultOVK();
|
||||
setTransparentChangeRecipient();
|
||||
},
|
||||
[&](const PaymentAddress& addr) {
|
||||
std::visit(match {
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
ovk = getDefaultOVK();
|
||||
builder_.SendChangeTo(addr);
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
libzcash::SaplingExtendedSpendingKey saplingKey;
|
||||
assert(pwalletMain->GetSaplingExtendedSpendingKey(addr, saplingKey));
|
||||
|
||||
ovk = saplingKey.expsk.full_viewing_key().ovk;
|
||||
builder_.SendChangeTo(addr, ovk);
|
||||
},
|
||||
[&](const auto& other) {
|
||||
ovk = getDefaultOVK();
|
||||
setTransparentChangeRecipient();
|
||||
}
|
||||
}, addr);
|
||||
}
|
||||
}, paymentSource_);
|
||||
}, ztxoSelector_);
|
||||
|
||||
// Track the total of notes that we've added to the builder
|
||||
CAmount sum = 0;
|
||||
|
|
|
@ -45,7 +45,7 @@ class AsyncRPCOperation_sendmany : public AsyncRPCOperation {
|
|||
public:
|
||||
AsyncRPCOperation_sendmany(
|
||||
TransactionBuilder builder,
|
||||
PaymentSource paymentSource,
|
||||
ZTXOSelector ztxoSelector,
|
||||
std::vector<SendManyRecipient> recipients,
|
||||
int minDepth,
|
||||
CAmount fee = DEFAULT_FEE,
|
||||
|
@ -69,7 +69,7 @@ private:
|
|||
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
|
||||
|
||||
TransactionBuilder builder_;
|
||||
PaymentSource paymentSource_;
|
||||
ZTXOSelector ztxoSelector_;
|
||||
std::vector<SendManyRecipient> recipients_;
|
||||
int mindepth_{1};
|
||||
CAmount fee_;
|
||||
|
|
|
@ -4156,7 +4156,7 @@ UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedO
|
|||
#define CTXOUT_REGULAR_SIZE 34
|
||||
|
||||
size_t EstimateTxSize(
|
||||
const PaymentSource& paymentSource,
|
||||
const ZTXOSelector& ztxoSelector,
|
||||
const std::vector<SendManyRecipient>& recipients,
|
||||
int nextBlockHeight) {
|
||||
CMutableTransaction mtx;
|
||||
|
@ -4165,31 +4165,23 @@ size_t EstimateTxSize(
|
|||
mtx.nVersion = SAPLING_TX_VERSION;
|
||||
|
||||
bool fromTaddr = std::visit(match {
|
||||
[&](const FromAnyTaddr& any) {
|
||||
[&](const AccountZTXOSelector& acct) {
|
||||
return acct.GetReceiverTypes().count(ReceiverType::P2PKH) > 0 ||
|
||||
acct.GetReceiverTypes().count(ReceiverType::P2SH) > 0;
|
||||
},
|
||||
[&](const CKeyID& keyId) {
|
||||
return true;
|
||||
},
|
||||
[&](const PaymentAddress& addr) {
|
||||
return std::visit(match {
|
||||
[&](const CKeyID& keyId) {
|
||||
return true;
|
||||
},
|
||||
[&](const CScriptID& scriptId) {
|
||||
return true;
|
||||
},
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
return false;
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
return false;
|
||||
},
|
||||
[&](const libzcash::UnifiedAddress& addr) {
|
||||
// TODO UA
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unified addresses not yet supported.");
|
||||
return false; // compiler is dumb
|
||||
}
|
||||
}, addr);
|
||||
[&](const CScriptID& scriptId) {
|
||||
return true;
|
||||
},
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
return false;
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
return false;
|
||||
}
|
||||
}, paymentSource);
|
||||
}, ztxoSelector);
|
||||
|
||||
// As a sanity check, estimate and verify that the size of the transaction will be valid.
|
||||
// Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid.
|
||||
|
@ -4283,25 +4275,25 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
// Check that the from address is valid.
|
||||
// Unified address (UA) allowed here (#5185)
|
||||
auto fromaddress = params[0].get_str();
|
||||
PaymentSource paymentSource;
|
||||
ZTXOSelector ztxoSelector;
|
||||
if (fromaddress == "ANY_TADDR") {
|
||||
paymentSource = FromAnyTaddr();
|
||||
ztxoSelector = AccountZTXOSelector(ZCASH_LEGACY_ACCOUNT, {ReceiverType::P2PKH, ReceiverType::P2SH});
|
||||
} else {
|
||||
auto addr = keyIO.DecodePaymentAddress(fromaddress);
|
||||
if (!addr.has_value()) {
|
||||
auto decoded = keyIO.DecodePaymentAddress(fromaddress);
|
||||
if (!decoded.has_value()) {
|
||||
throw JSONRPCError(
|
||||
RPC_INVALID_ADDRESS_OR_KEY,
|
||||
"Invalid from address: should be a taddr, a zaddr, or the string 'ANY_TADDR'.");
|
||||
}
|
||||
|
||||
// Unified addresses are not yet supported.
|
||||
if (std::holds_alternative<libzcash::UnifiedAddress>(addr.value())) {
|
||||
auto ztxoSelectorOpt = pwalletMain->ToZTXOSelector(decoded.value(), true);
|
||||
if (!ztxoSelectorOpt.has_value()) {
|
||||
throw JSONRPCError(
|
||||
RPC_INVALID_ADDRESS_OR_KEY,
|
||||
"Invalid from address: unified addresses are not yet supported.");
|
||||
"Invalid from address, no payment source found for address.");
|
||||
}
|
||||
|
||||
paymentSource = addr.value();
|
||||
ztxoSelector = ztxoSelectorOpt.value();
|
||||
}
|
||||
|
||||
UniValue outputs = params[1].get_array();
|
||||
|
@ -4374,7 +4366,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
|
||||
// Sanity check for transaction size
|
||||
// TODO: move this to the builder?
|
||||
auto txsize = EstimateTxSize(paymentSource, recipients, nextBlockHeight);
|
||||
auto txsize = EstimateTxSize(ztxoSelector, recipients, nextBlockHeight);
|
||||
if (txsize > MAX_TX_SIZE_AFTER_SAPLING) {
|
||||
throw JSONRPCError(
|
||||
RPC_INVALID_PARAMETER,
|
||||
|
@ -4436,7 +4428,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
// Create operation and add to global queue
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
std::shared_ptr<AsyncRPCOperation> operation(
|
||||
new AsyncRPCOperation_sendmany(builder, paymentSource, recipients, nMinDepth, nFee, allowRevealedAmounts, contextInfo)
|
||||
new AsyncRPCOperation_sendmany(builder, ztxoSelector, recipients, nMinDepth, nFee, allowRevealedAmounts, contextInfo)
|
||||
);
|
||||
q->addOperation(operation);
|
||||
AsyncRPCOperationId operationId = operation->getId();
|
||||
|
|
|
@ -1191,19 +1191,11 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters)
|
|||
int nHeight = retValue.get_int();
|
||||
TransactionBuilder builder(Params().GetConsensus(), nHeight + 1, pwalletMain);
|
||||
|
||||
try {
|
||||
libzcash::UnifiedAddress ua; //dummy
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(ua, 1*COIN, std::nullopt) };
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, ua, recipients, 1));
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK( find_error(objError, "Invalid from address"));
|
||||
}
|
||||
|
||||
// Note: The following will crash as a google test because AsyncRPCOperation_sendmany
|
||||
// invokes a method on pwalletMain, which is undefined in the google test environment.
|
||||
try {
|
||||
KeyIO keyIO(Params());
|
||||
auto sender = keyIO.DecodePaymentAddress("ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP").value();
|
||||
auto sender = std::get<libzcash::SproutPaymentAddress>(keyIO.DecodePaymentAddress("ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP").value());
|
||||
libzcash::UnifiedAddress ua; //dummy
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(ua, 1*COIN, std::nullopt) };
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, sender, recipients, 1));
|
||||
|
@ -1242,7 +1234,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
|
||||
// add keys manually
|
||||
BOOST_CHECK_NO_THROW(retValue = CallRPC("getnewaddress"));
|
||||
auto taddr1 = keyIO.DecodePaymentAddress(retValue.get_str()).value();
|
||||
auto taddr1 = std::get<CKeyID>(keyIO.DecodePaymentAddress(retValue.get_str()).value());
|
||||
|
||||
if (!pwalletMain->HaveMnemonicSeed()) {
|
||||
pwalletMain->GenerateNewSeed();
|
||||
|
|
|
@ -1359,34 +1359,65 @@ void CWallet::SyncMetaData(pair<typename TxSpendMap<T>::iterator, typename TxSpe
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<ZTXOSelector> CWallet::ToZTXOSelector(const libzcash::PaymentAddress& addr, bool requireSpendingKey) const {
|
||||
auto self = this;
|
||||
std::optional<ZTXOSelector> result;
|
||||
std::visit(match {
|
||||
[&](const CKeyID& addr) {
|
||||
if (!requireSpendingKey || self->HaveKey(addr)) {
|
||||
result = addr;
|
||||
}
|
||||
},
|
||||
[&](const CScriptID& addr) {
|
||||
if (!requireSpendingKey || self->HaveCScript(addr)) {
|
||||
result = addr;
|
||||
}
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
if (!requireSpendingKey || self->HaveSaplingSpendingKeyForAddress(addr)) {
|
||||
result = addr;
|
||||
}
|
||||
},
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
if (!requireSpendingKey || self->HaveSproutSpendingKey(addr)) {
|
||||
result = addr;
|
||||
}
|
||||
},
|
||||
[&](const libzcash::UnifiedAddress& ua) {
|
||||
// TODO: Find the unified account corresponding to this UA
|
||||
}
|
||||
}, addr);
|
||||
return result;
|
||||
}
|
||||
|
||||
SpendableInputs CWallet::FindSpendableInputs(
|
||||
PaymentSource paymentSource,
|
||||
ZTXOSelector selector,
|
||||
bool allowTransparentCoinbase,
|
||||
uint32_t minDepth) {
|
||||
uint32_t minDepth) const {
|
||||
SpendableInputs unspent;
|
||||
|
||||
auto filters = std::visit(match {
|
||||
[&](const PaymentAddress& addr) {
|
||||
return std::visit(match {
|
||||
[&](const CKeyID& keyId) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::set<CTxDestination>({keyId});
|
||||
return std::make_pair(t_filter, AddrSet::Empty());
|
||||
},
|
||||
[&](const CScriptID& scriptId) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::set<CTxDestination>({scriptId});
|
||||
return std::make_pair(t_filter, AddrSet::Empty());
|
||||
},
|
||||
[&](const auto& other) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::nullopt;
|
||||
return std::make_pair(t_filter, AddrSet::ForPaymentAddresses({addr}));
|
||||
}
|
||||
}, addr);
|
||||
[&](const CKeyID& keyId) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::set<CTxDestination>({keyId});
|
||||
return std::make_pair(t_filter, AddrSet::Empty());
|
||||
},
|
||||
[&](const FromAnyTaddr& taddr) {
|
||||
[&](const CScriptID& scriptId) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::set<CTxDestination>({scriptId});
|
||||
return std::make_pair(t_filter, AddrSet::Empty());
|
||||
},
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::nullopt;
|
||||
return std::make_pair(t_filter, AddrSet::ForPaymentAddresses({addr}));
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::nullopt;
|
||||
return std::make_pair(t_filter, AddrSet::ForPaymentAddresses({addr}));
|
||||
},
|
||||
[&](const AccountZTXOSelector& acct) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::set<CTxDestination>({});
|
||||
return std::make_pair(t_filter, AddrSet::Empty());
|
||||
}
|
||||
}, paymentSource);
|
||||
}, selector);
|
||||
|
||||
if (filters.first.has_value()) {
|
||||
this->AvailableCoins(
|
||||
|
|
|
@ -686,13 +686,47 @@ public:
|
|||
std::string ToString() const;
|
||||
};
|
||||
|
||||
class FromAnyTaddr {
|
||||
/**
|
||||
* A class representing a BIP-44 account's spend authority, which can be used
|
||||
* to choose prior Zcash transaction outputs, including both transparent UTXOs
|
||||
* and shielded notes.
|
||||
*
|
||||
* If the set of receiver types provided is non-empty, only outputs for
|
||||
* protocols corresponding to the provided set of receiver types may be used.
|
||||
* If the set of receiver types is empty, no restrictions are placed upon what
|
||||
* protocols outputs are selected for.
|
||||
*/
|
||||
class AccountZTXOSelector {
|
||||
libzcash::AccountId accountId;
|
||||
std::set<libzcash::ReceiverType> receiverTypes;
|
||||
public:
|
||||
friend bool operator==(const FromAnyTaddr &a, const FromAnyTaddr &b) { return true; }
|
||||
AccountZTXOSelector(libzcash::AccountId accountIdIn, std::set<libzcash::ReceiverType> receiverTypesIn):
|
||||
accountId(accountIdIn), receiverTypes(receiverTypesIn) {}
|
||||
|
||||
libzcash::AccountId GetAccountId() const {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
const std::set<libzcash::ReceiverType>& GetReceiverTypes() const {
|
||||
return receiverTypes;
|
||||
}
|
||||
|
||||
friend bool operator==(const AccountZTXOSelector &a, const AccountZTXOSelector &b) {
|
||||
return a.accountId == b.accountId && a.receiverTypes == b.receiverTypes;
|
||||
}
|
||||
};
|
||||
|
||||
/** A source from which the zcashd wallet can spend funds */
|
||||
typedef std::variant<FromAnyTaddr, libzcash::PaymentAddress> PaymentSource;
|
||||
/**
|
||||
* A selector which can be used to choose prior Zcash transaction outputs,
|
||||
* including both transparent UTXOs and shielded notes.
|
||||
*/
|
||||
typedef std::variant<
|
||||
CKeyID,
|
||||
CScriptID,
|
||||
libzcash::SproutPaymentAddress,
|
||||
libzcash::SaplingPaymentAddress,
|
||||
AccountZTXOSelector> ZTXOSelector;
|
||||
|
||||
|
||||
class SpendableInputs {
|
||||
public:
|
||||
|
@ -1144,10 +1178,19 @@ public:
|
|||
*/
|
||||
static bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet);
|
||||
|
||||
/**
|
||||
* Obtain the ZTXO selector for the specified payment address. If the
|
||||
* `requireSpendingKey` flag is set, this will only return a selector
|
||||
* that will choose outputs for which this wallet holds the spending keys.
|
||||
*/
|
||||
std::optional<ZTXOSelector> ToZTXOSelector(
|
||||
const libzcash::PaymentAddress& addr,
|
||||
bool requireSpendingKey) const;
|
||||
|
||||
SpendableInputs FindSpendableInputs(
|
||||
PaymentSource paymentSource,
|
||||
ZTXOSelector paymentSource,
|
||||
bool allowTransparentCoinbase,
|
||||
uint32_t minDepth);
|
||||
uint32_t minDepth) const;
|
||||
|
||||
bool IsSpent(const uint256& hash, unsigned int n) const;
|
||||
bool IsSproutSpent(const uint256& nullifier) const;
|
||||
|
|
Loading…
Reference in New Issue