Move FindSpendableInputs to CWallet
This commit is contained in:
parent
6b335ecb4c
commit
c4a7f5c95c
|
@ -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 = FindSpendableInputs(allowTransparentCoinbase);
|
||||
auto spendable = pwalletMain->FindSpendableInputs(paymentSource_, allowTransparentCoinbase, mindepth_);
|
||||
if (!spendable.LimitToAmount(targetAmount, dustThreshold)) {
|
||||
CAmount changeAmount{spendable.Total() - targetAmount};
|
||||
if (changeAmount > 0 && changeAmount < dustThreshold) {
|
||||
|
@ -523,158 +523,6 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
return tx.GetHash();
|
||||
}
|
||||
|
||||
SpendableInputs AsyncRPCOperation_sendmany::FindSpendableInputs(bool allowTransparentCoinbase) {
|
||||
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 FromAnyTaddr& taddr) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::set<CTxDestination>({});
|
||||
return std::make_pair(t_filter, AddrSet::Empty());
|
||||
}
|
||||
}, paymentSource_);
|
||||
|
||||
if (filters.first.has_value()) {
|
||||
pwalletMain->AvailableCoins(
|
||||
unspent.utxos,
|
||||
false, // fOnlyConfirmed
|
||||
nullptr, // coinControl
|
||||
true, // fIncludeZeroValue
|
||||
allowTransparentCoinbase, // fIncludeCoinBase
|
||||
true, // fOnlySpendable
|
||||
mindepth_, // nMinDepth
|
||||
&filters.first.value()); // onlyFilterByDests
|
||||
}
|
||||
|
||||
pwalletMain->GetFilteredNotes(
|
||||
unspent.sproutNoteEntries,
|
||||
unspent.saplingNoteEntries,
|
||||
filters.second,
|
||||
mindepth_);
|
||||
|
||||
return unspent;
|
||||
}
|
||||
|
||||
bool SpendableInputs::LimitToAmount(const CAmount amountRequired, const CAmount dustThreshold) {
|
||||
assert(amountRequired >= 0 && dustThreshold > 0);
|
||||
|
||||
CAmount totalSelected{0};
|
||||
auto haveSufficientFunds = [&]() {
|
||||
// if the total would result in change below the dust threshold,
|
||||
// we do not yet have sufficient funds
|
||||
return totalSelected == amountRequired || totalSelected - amountRequired > dustThreshold;
|
||||
};
|
||||
|
||||
// Select Sprout notes for spending first - if possible, we want users to
|
||||
// spend any notes that they still have in the Sprout pool.
|
||||
if (!haveSufficientFunds()) {
|
||||
std::sort(sproutNoteEntries.begin(), sproutNoteEntries.end(),
|
||||
[](SproutNoteEntry i, SproutNoteEntry j) -> bool {
|
||||
return i.note.value() > j.note.value();
|
||||
});
|
||||
auto sproutIt = sproutNoteEntries.begin();
|
||||
while (sproutIt != sproutNoteEntries.end() && !haveSufficientFunds()) {
|
||||
totalSelected += sproutIt->note.value();
|
||||
++sproutIt;
|
||||
}
|
||||
sproutNoteEntries.erase(sproutIt, sproutNoteEntries.end());
|
||||
}
|
||||
|
||||
// Next select transparent utxos. We preferentially spend transparent funds,
|
||||
// with the intent that we'd like to opportunistically shield whatever is
|
||||
// possible, and we will always shield change after the introduction of
|
||||
// unified addresses.
|
||||
if (!haveSufficientFunds()) {
|
||||
std::sort(utxos.begin(), utxos.end(),
|
||||
[](COutput i, COutput j) -> bool {
|
||||
return i.Value() > j.Value();
|
||||
});
|
||||
auto utxoIt = utxos.begin();
|
||||
while (utxoIt != utxos.end() && !haveSufficientFunds()) {
|
||||
totalSelected += utxoIt->Value();
|
||||
++utxoIt;
|
||||
}
|
||||
utxos.erase(utxoIt, utxos.end());
|
||||
}
|
||||
|
||||
// Finally select Sapling outputs. After the introduction of Orchard to the
|
||||
// wallet, the selection of Sapling and Orchard notes, and the
|
||||
// determination of change amounts, should be done in a fashion that
|
||||
// minimizes information leakage whenever possible.
|
||||
if (!haveSufficientFunds()) {
|
||||
std::sort(saplingNoteEntries.begin(), saplingNoteEntries.end(),
|
||||
[](SaplingNoteEntry i, SaplingNoteEntry j) -> bool {
|
||||
return i.note.value() > j.note.value();
|
||||
});
|
||||
auto saplingIt = saplingNoteEntries.begin();
|
||||
while (saplingIt != saplingNoteEntries.end() && !haveSufficientFunds()) {
|
||||
totalSelected += saplingIt->note.value();
|
||||
++saplingIt;
|
||||
}
|
||||
saplingNoteEntries.erase(saplingIt, saplingNoteEntries.end());
|
||||
}
|
||||
|
||||
return haveSufficientFunds();
|
||||
}
|
||||
|
||||
bool SpendableInputs::HasTransparentCoinbase() const {
|
||||
for (const auto& out : utxos) {
|
||||
if (out.fIsCoinbase) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SpendableInputs::LogInputs(const AsyncRPCOperationId& id) const {
|
||||
for (const auto& utxo : utxos) {
|
||||
LogPrint("zrpcunsafe", "%s: found unspent transparent UTXO (txid=%s, index=%d, amount=%s, isCoinbase=%s)\n",
|
||||
id,
|
||||
utxo.tx->GetHash().ToString(),
|
||||
utxo.i,
|
||||
FormatMoney(utxo.Value()),
|
||||
utxo.fIsCoinbase);
|
||||
}
|
||||
|
||||
for (const auto& entry : sproutNoteEntries) {
|
||||
std::string data(entry.memo.begin(), entry.memo.end());
|
||||
LogPrint("zrpcunsafe", "%s: found unspent Sprout note (txid=%s, vJoinSplit=%d, jsoutindex=%d, amount=%s, memo=%s)\n",
|
||||
id,
|
||||
entry.jsop.hash.ToString().substr(0, 10),
|
||||
entry.jsop.js,
|
||||
int(entry.jsop.n), // uint8_t
|
||||
FormatMoney(entry.note.value()),
|
||||
HexStr(data).substr(0, 10)
|
||||
);
|
||||
}
|
||||
|
||||
for (const auto& entry : saplingNoteEntries) {
|
||||
std::string data(entry.memo.begin(), entry.memo.end());
|
||||
LogPrint("zrpcunsafe", "%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n",
|
||||
id,
|
||||
entry.op.hash.ToString().substr(0, 10),
|
||||
entry.op.n,
|
||||
FormatMoney(entry.note.value()),
|
||||
HexStr(data).substr(0, 10));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a dust threshold based upon a standard p2pkh txout.
|
||||
*/
|
||||
|
|
|
@ -25,13 +25,6 @@
|
|||
|
||||
using namespace libzcash;
|
||||
|
||||
class FromAnyTaddr {
|
||||
public:
|
||||
friend bool operator==(const FromAnyTaddr &a, const FromAnyTaddr &b) { return true; }
|
||||
};
|
||||
|
||||
typedef std::variant<FromAnyTaddr, PaymentAddress> PaymentSource;
|
||||
|
||||
class SendManyRecipient {
|
||||
public:
|
||||
PaymentAddress address;
|
||||
|
@ -42,48 +35,6 @@ public:
|
|||
address(address_), amount(amount_), memo(memo_) {}
|
||||
};
|
||||
|
||||
class SpendableInputs {
|
||||
public:
|
||||
std::vector<COutput> utxos;
|
||||
std::vector<SproutNoteEntry> sproutNoteEntries;
|
||||
std::vector<SaplingNoteEntry> saplingNoteEntries;
|
||||
|
||||
/**
|
||||
* Selectively discard notes that are not required to obtain the desired
|
||||
* amount. Returns `false` if the available inputs do not add up to the
|
||||
* desired amount.
|
||||
*/
|
||||
bool LimitToAmount(CAmount amount, CAmount dustThreshold);
|
||||
|
||||
/**
|
||||
* Compute the total ZEC amount of spendable inputs.
|
||||
*/
|
||||
CAmount Total() const {
|
||||
CAmount result = 0;
|
||||
for (const auto& t : utxos) {
|
||||
result += t.Value();
|
||||
}
|
||||
for (const auto& t : sproutNoteEntries) {
|
||||
result += t.note.value();
|
||||
}
|
||||
for (const auto& t : saplingNoteEntries) {
|
||||
result += t.note.value();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not the set of selected UTXOs contains
|
||||
* coinbase outputs.
|
||||
*/
|
||||
bool HasTransparentCoinbase() const;
|
||||
|
||||
/**
|
||||
* List spendable inputs in zrpcunsafe log entries.
|
||||
*/
|
||||
void LogInputs(const AsyncRPCOperationId& id) const;
|
||||
};
|
||||
|
||||
class TxOutputAmounts {
|
||||
public:
|
||||
CAmount t_outputs_total{0};
|
||||
|
@ -131,8 +82,6 @@ private:
|
|||
uint32_t transparentRecipients_{0};
|
||||
TxOutputAmounts txOutputAmounts_;
|
||||
|
||||
SpendableInputs FindSpendableInputs(bool fAcceptCoinbase);
|
||||
|
||||
static CAmount DefaultDustThreshold();
|
||||
|
||||
static std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
|
||||
|
|
|
@ -1359,6 +1359,56 @@ void CWallet::SyncMetaData(pair<typename TxSpendMap<T>::iterator, typename TxSpe
|
|||
}
|
||||
}
|
||||
|
||||
SpendableInputs CWallet::FindSpendableInputs(
|
||||
PaymentSource paymentSource,
|
||||
bool allowTransparentCoinbase,
|
||||
uint32_t minDepth) {
|
||||
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 FromAnyTaddr& taddr) {
|
||||
std::optional<std::set<CTxDestination>> t_filter = std::set<CTxDestination>({});
|
||||
return std::make_pair(t_filter, AddrSet::Empty());
|
||||
}
|
||||
}, paymentSource);
|
||||
|
||||
if (filters.first.has_value()) {
|
||||
this->AvailableCoins(
|
||||
unspent.utxos,
|
||||
false, // fOnlyConfirmed
|
||||
nullptr, // coinControl
|
||||
true, // fIncludeZeroValue
|
||||
allowTransparentCoinbase, // fIncludeCoinBase
|
||||
true, // fOnlySpendable
|
||||
minDepth, // nMinDepth
|
||||
&filters.first.value()); // onlyFilterByDests
|
||||
}
|
||||
|
||||
this->GetFilteredNotes(
|
||||
unspent.sproutNoteEntries,
|
||||
unspent.saplingNoteEntries,
|
||||
filters.second,
|
||||
minDepth);
|
||||
|
||||
return unspent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outpoint is spent if any non-conflicted transaction
|
||||
* spends it:
|
||||
|
@ -2694,14 +2744,14 @@ void CWallet::SetMnemonicHDChain(const CHDChain& chain, bool memonly)
|
|||
mnemonicHDChain = chain;
|
||||
}
|
||||
|
||||
bool CWallet::CheckNetworkInfo(std::pair<std::string, std::string> readNetworkInfo)
|
||||
bool CWallet::CheckNetworkInfo(std::pair<std::string, std::string> readNetworkInfo) const
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
std::pair<string, string> networkInfo(PACKAGE_NAME, networkIdString);
|
||||
return readNetworkInfo == networkInfo;
|
||||
}
|
||||
|
||||
uint32_t CWallet::BIP44CoinType() {
|
||||
uint32_t CWallet::BIP44CoinType() const {
|
||||
return Params(networkIdString).BIP44CoinType();
|
||||
}
|
||||
|
||||
|
@ -5494,7 +5544,7 @@ void CWallet::GetFilteredNotes(
|
|||
int maxDepth,
|
||||
bool ignoreSpent,
|
||||
bool requireSpendingKey,
|
||||
bool ignoreLocked)
|
||||
bool ignoreLocked) const
|
||||
{
|
||||
// Don't bother to do anything if the note filter would reject all notes
|
||||
if (noteFilter.has_value() && noteFilter.value().IsEmpty())
|
||||
|
@ -6005,3 +6055,108 @@ std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const C
|
|||
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const libzcash::UnknownReceiver& receiver) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool SpendableInputs::LimitToAmount(const CAmount amountRequired, const CAmount dustThreshold) {
|
||||
assert(amountRequired >= 0 && dustThreshold > 0);
|
||||
|
||||
CAmount totalSelected{0};
|
||||
auto haveSufficientFunds = [&]() {
|
||||
// if the total would result in change below the dust threshold,
|
||||
// we do not yet have sufficient funds
|
||||
return totalSelected == amountRequired || totalSelected - amountRequired > dustThreshold;
|
||||
};
|
||||
|
||||
// Select Sprout notes for spending first - if possible, we want users to
|
||||
// spend any notes that they still have in the Sprout pool.
|
||||
if (!haveSufficientFunds()) {
|
||||
std::sort(sproutNoteEntries.begin(), sproutNoteEntries.end(),
|
||||
[](SproutNoteEntry i, SproutNoteEntry j) -> bool {
|
||||
return i.note.value() > j.note.value();
|
||||
});
|
||||
auto sproutIt = sproutNoteEntries.begin();
|
||||
while (sproutIt != sproutNoteEntries.end() && !haveSufficientFunds()) {
|
||||
totalSelected += sproutIt->note.value();
|
||||
++sproutIt;
|
||||
}
|
||||
sproutNoteEntries.erase(sproutIt, sproutNoteEntries.end());
|
||||
}
|
||||
|
||||
// Next select transparent utxos. We preferentially spend transparent funds,
|
||||
// with the intent that we'd like to opportunistically shield whatever is
|
||||
// possible, and we will always shield change after the introduction of
|
||||
// unified addresses.
|
||||
if (!haveSufficientFunds()) {
|
||||
std::sort(utxos.begin(), utxos.end(),
|
||||
[](COutput i, COutput j) -> bool {
|
||||
return i.Value() > j.Value();
|
||||
});
|
||||
auto utxoIt = utxos.begin();
|
||||
while (utxoIt != utxos.end() && !haveSufficientFunds()) {
|
||||
totalSelected += utxoIt->Value();
|
||||
++utxoIt;
|
||||
}
|
||||
utxos.erase(utxoIt, utxos.end());
|
||||
}
|
||||
|
||||
// Finally select Sapling outputs. After the introduction of Orchard to the
|
||||
// wallet, the selection of Sapling and Orchard notes, and the
|
||||
// determination of change amounts, should be done in a fashion that
|
||||
// minimizes information leakage whenever possible.
|
||||
if (!haveSufficientFunds()) {
|
||||
std::sort(saplingNoteEntries.begin(), saplingNoteEntries.end(),
|
||||
[](SaplingNoteEntry i, SaplingNoteEntry j) -> bool {
|
||||
return i.note.value() > j.note.value();
|
||||
});
|
||||
auto saplingIt = saplingNoteEntries.begin();
|
||||
while (saplingIt != saplingNoteEntries.end() && !haveSufficientFunds()) {
|
||||
totalSelected += saplingIt->note.value();
|
||||
++saplingIt;
|
||||
}
|
||||
saplingNoteEntries.erase(saplingIt, saplingNoteEntries.end());
|
||||
}
|
||||
|
||||
return haveSufficientFunds();
|
||||
}
|
||||
|
||||
bool SpendableInputs::HasTransparentCoinbase() const {
|
||||
for (const auto& out : utxos) {
|
||||
if (out.fIsCoinbase) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SpendableInputs::LogInputs(const AsyncRPCOperationId& id) const {
|
||||
for (const auto& utxo : utxos) {
|
||||
LogPrint("zrpcunsafe", "%s: found unspent transparent UTXO (txid=%s, index=%d, amount=%s, isCoinbase=%s)\n",
|
||||
id,
|
||||
utxo.tx->GetHash().ToString(),
|
||||
utxo.i,
|
||||
FormatMoney(utxo.Value()),
|
||||
utxo.fIsCoinbase);
|
||||
}
|
||||
|
||||
for (const auto& entry : sproutNoteEntries) {
|
||||
std::string data(entry.memo.begin(), entry.memo.end());
|
||||
LogPrint("zrpcunsafe", "%s: found unspent Sprout note (txid=%s, vJoinSplit=%d, jsoutindex=%d, amount=%s, memo=%s)\n",
|
||||
id,
|
||||
entry.jsop.hash.ToString().substr(0, 10),
|
||||
entry.jsop.js,
|
||||
int(entry.jsop.n), // uint8_t
|
||||
FormatMoney(entry.note.value()),
|
||||
HexStr(data).substr(0, 10)
|
||||
);
|
||||
}
|
||||
|
||||
for (const auto& entry : saplingNoteEntries) {
|
||||
std::string data(entry.memo.begin(), entry.memo.end());
|
||||
LogPrint("zrpcunsafe", "%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n",
|
||||
id,
|
||||
entry.op.hash.ToString().substr(0, 10),
|
||||
entry.op.n,
|
||||
FormatMoney(entry.note.value()),
|
||||
HexStr(data).substr(0, 10));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -686,6 +686,56 @@ public:
|
|||
std::string ToString() const;
|
||||
};
|
||||
|
||||
class FromAnyTaddr {
|
||||
public:
|
||||
friend bool operator==(const FromAnyTaddr &a, const FromAnyTaddr &b) { return true; }
|
||||
};
|
||||
|
||||
/** A source from which the zcashd wallet can spend funds */
|
||||
typedef std::variant<FromAnyTaddr, libzcash::PaymentAddress> PaymentSource;
|
||||
|
||||
class SpendableInputs {
|
||||
public:
|
||||
std::vector<COutput> utxos;
|
||||
std::vector<SproutNoteEntry> sproutNoteEntries;
|
||||
std::vector<SaplingNoteEntry> saplingNoteEntries;
|
||||
|
||||
/**
|
||||
* Selectively discard notes that are not required to obtain the desired
|
||||
* amount. Returns `false` if the available inputs do not add up to the
|
||||
* desired amount.
|
||||
*/
|
||||
bool LimitToAmount(CAmount amount, CAmount dustThreshold);
|
||||
|
||||
/**
|
||||
* Compute the total ZEC amount of spendable inputs.
|
||||
*/
|
||||
CAmount Total() const {
|
||||
CAmount result = 0;
|
||||
for (const auto& t : utxos) {
|
||||
result += t.Value();
|
||||
}
|
||||
for (const auto& t : sproutNoteEntries) {
|
||||
result += t.note.value();
|
||||
}
|
||||
for (const auto& t : saplingNoteEntries) {
|
||||
result += t.note.value();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not the set of selected UTXOs contains
|
||||
* coinbase outputs.
|
||||
*/
|
||||
bool HasTransparentCoinbase() const;
|
||||
|
||||
/**
|
||||
* List spendable inputs in zrpcunsafe log entries.
|
||||
*/
|
||||
void LogInputs(const AsyncRPCOperationId& id) const;
|
||||
};
|
||||
|
||||
/** Private key that includes an expiration date in case it never gets used. */
|
||||
class CWalletKey
|
||||
{
|
||||
|
@ -1094,6 +1144,11 @@ 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);
|
||||
|
||||
SpendableInputs FindSpendableInputs(
|
||||
PaymentSource paymentSource,
|
||||
bool allowTransparentCoinbase,
|
||||
uint32_t minDepth);
|
||||
|
||||
bool IsSpent(const uint256& hash, unsigned int n) const;
|
||||
bool IsSproutSpent(const uint256& nullifier) const;
|
||||
bool IsSaplingSpent(const uint256& nullifier) const;
|
||||
|
@ -1501,8 +1556,8 @@ public:
|
|||
void SetMnemonicHDChain(const CHDChain& chain, bool memonly);
|
||||
const std::optional<CHDChain>& GetMnemonicHDChain() const { return mnemonicHDChain; }
|
||||
|
||||
bool CheckNetworkInfo(std::pair<std::string, std::string> networkInfo);
|
||||
uint32_t BIP44CoinType();
|
||||
bool CheckNetworkInfo(std::pair<std::string, std::string> networkInfo) const;
|
||||
uint32_t BIP44CoinType() const;
|
||||
|
||||
/**
|
||||
* Check whether the wallet contains spending keys for all the addresses
|
||||
|
@ -1519,7 +1574,7 @@ public:
|
|||
int maxDepth=INT_MAX,
|
||||
bool ignoreSpent=true,
|
||||
bool requireSpendingKey=true,
|
||||
bool ignoreLocked=true);
|
||||
bool ignoreLocked=true) const;
|
||||
|
||||
/* Returns the wallets help message */
|
||||
static std::string GetWalletHelpString(bool showDebug);
|
||||
|
|
Loading…
Reference in New Issue