aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/spend.cpp
diff options
context:
space:
mode:
authorAndrew Chow <achow101-github@achow101.com>2022-06-17 17:56:40 -0400
committerAndrew Chow <achow101-github@achow101.com>2022-06-17 18:02:33 -0400
commit8be652e43964329c1f2dadd676916b53e5c40974 (patch)
tree3b0597b0785914d4b1b4790ec86d15f646460344 /src/wallet/spend.cpp
parentf8586b25f6a4f1e30a54e58f45dd28aaf580bbc6 (diff)
parentfd5c996d1609e6f88769f6f3ef0c322e3435b3aa (diff)
downloadbitcoin-8be652e43964329c1f2dadd676916b53e5c40974.tar.xz
Merge bitcoin/bitcoin#25005: wallet: remove extra wtx lookup in 'AvailableCoins' + several code cleanups.
fd5c996d1609e6f88769f6f3ef0c322e3435b3aa wallet: GetAvailableBalance, remove double walk-through every available coin (furszy) 162d4ad10f28c5fa38551d69ce9b296ab3933c77 wallet: add 'only_spendable' filter to AvailableCoins (furszy) cdf185ccfb2085e5a4bf82d833392d74b748aeff wallet: remove unused IsSpentKey(hash, index) method (furszy) 4b83bf8dbcf6b8b1c1293575391e90ac7e21b0e0 wallet: avoid extra IsSpentKey -> GetWalletTx lookups (furszy) 3d8a2822570e3cf4d1bc4f9d59b5dcb0145920ad wallet: decouple IsSpentKey(scriptPubKey) from IsSpentKey(hash, n) (furszy) a06fa94ff81e2bccef0316ea5ec4eca0f4de5071 wallet: IsSpent, 'COutPoint' arg instead of (hash, index) (furszy) 91902b77202fc636edb3db587cb6e87d9fb9b60a wallet: IsLockedCoin, 'COutPoint' arg instead of (hash, index) (furszy) 9472ca0a65396206b3078bddf98f4c1807be2d82 wallet: AvailableCoins, don't call 'wtx.tx->vout[i]' multiple times (furszy) 4ce235ef8f9a9dddc52d7ab60c8f71bda1d38873 wallet: return 'CoinsResult' struct in `AvailableCoins` (furszy) Pull request description: This started in #24845 but grew out of scope of it. So, points tackled: 1) Avoid extra `GetWalletTx` lookups inside `AvailableCoins -> IsSpentKey`. `IsSpentKey` was receiving the tx hash and index to internally lookup the tx inside the wallet's map. As all the `IsSpentKey` function callers already have the wtx available, them can provide the `scriptPubKey` directly. 2) Most of the time, we call `Wallet::AvailableCoins`, and later on the process, skip the non-spendable coins from the result in subsequent for-loops. So to speedup the process: introduced the ability to filter by "only_spendable" coins inside `Wallet::AvailableCoins` directly. (the non-spendable coins skip examples are inside `AttemptSelection->GroupOutputs` and `GetAvailableBalance`). 4) Refactored `AvailableCoins` in several ways: a) Now it will return a new struct `CoinsResult` instead of receiving the vCoins vector reference (which was being cleared at the beginning of the method anyway). --> this is coming from #24845 but cherry-picked it here too to make the following commits look nicer. b) Unified all the 'wtx.tx->vout[I]' calls into a single call (coming from this comment https://github.com/bitcoin/bitcoin/pull/24699#discussion_r854163032). 5) The wallet `IsLockedCoin` and `IsSpent` methods now accept an `OutPoint` instead of a hash:index. Which let me cleanup a bunch of extra code. 6) Speeded up the wallet 'GetAvailableBalance': filtering `AvailableCoins` by spendable outputs only and using the 'AvailableCoins' retrieved `total_amount` instead of looping over all the retrieved coins once more. ------------------------------------------------------- Side topic, all this process will look even nicer with #25218 ACKs for top commit: achow101: ACK fd5c996d1609e6f88769f6f3ef0c322e3435b3aa brunoerg: crACK fd5c996d1609e6f88769f6f3ef0c322e3435b3aa w0xlt: Code Review ACK https://github.com/bitcoin/bitcoin/pull/25005/commits/fd5c996d1609e6f88769f6f3ef0c322e3435b3aa Tree-SHA512: 376a85476f907f4f7d1fc3de74b3dbe159b8cc24687374d8739711ad202ea07a33e86f4e66dece836da3ae6985147119fe584f6e672f11d0450ba6bd165b3220
Diffstat (limited to 'src/wallet/spend.cpp')
-rw-r--r--src/wallet/spend.cpp91
1 files changed, 52 insertions, 39 deletions
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index ce99aeefe5..60a28a22e9 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -84,12 +84,18 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
}
-void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, std::optional<CFeeRate> feerate, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
+CoinsResult AvailableCoins(const CWallet& wallet,
+ const CCoinControl* coinControl,
+ std::optional<CFeeRate> feerate,
+ const CAmount& nMinimumAmount,
+ const CAmount& nMaximumAmount,
+ const CAmount& nMinimumSumAmount,
+ const uint64_t nMaximumCount,
+ bool only_spendable)
{
AssertLockHeld(wallet.cs_wallet);
- vCoins.clear();
- CAmount nTotal = 0;
+ CoinsResult result;
// Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where
// a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses
bool allow_used_addresses = !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse);
@@ -159,76 +165,81 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
bool tx_from_me = CachedTxIsFromMe(wallet, wtx, ISMINE_ALL);
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
+ const CTxOut& output = wtx.tx->vout[i];
+ const COutPoint outpoint(wtxid, i);
+
// Only consider selected coins if add_inputs is false
- if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) {
+ if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(outpoint)) {
continue;
}
- if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount)
+ if (output.nValue < nMinimumAmount || output.nValue > nMaximumAmount)
continue;
- if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
+ if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(outpoint))
continue;
- if (wallet.IsLockedCoin(entry.first, i))
+ if (wallet.IsLockedCoin(outpoint))
continue;
- if (wallet.IsSpent(wtxid, i))
+ if (wallet.IsSpent(outpoint))
continue;
- isminetype mine = wallet.IsMine(wtx.tx->vout[i]);
+ isminetype mine = wallet.IsMine(output);
if (mine == ISMINE_NO) {
continue;
}
- if (!allow_used_addresses && wallet.IsSpentKey(wtxid, i)) {
+ if (!allow_used_addresses && wallet.IsSpentKey(output.scriptPubKey)) {
continue;
}
- std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(wtx.tx->vout[i].scriptPubKey);
+ std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(output.scriptPubKey);
- bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
+ bool solvable = provider ? IsSolvable(*provider, output.scriptPubKey) : false;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
- int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
- vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
+ // Filter by spendable outputs only
+ if (!spendable && only_spendable) continue;
+
+ int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
+ result.coins.emplace_back(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
+ result.total_amount += output.nValue;
// Checks the sum amount of all UTXO's.
if (nMinimumSumAmount != MAX_MONEY) {
- nTotal += wtx.tx->vout[i].nValue;
-
- if (nTotal >= nMinimumSumAmount) {
- return;
+ if (result.total_amount >= nMinimumSumAmount) {
+ return result;
}
}
// Checks the maximum number of UTXO's.
- if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
- return;
+ if (nMaximumCount > 0 && result.coins.size() >= nMaximumCount) {
+ return result;
}
}
}
+
+ return result;
}
-void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
+CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
{
- AvailableCoins(wallet, vCoins, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
+ return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, /*only_spendable=*/false);
}
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)
{
LOCK(wallet.cs_wallet);
-
- CAmount balance = 0;
- std::vector<COutput> vCoins;
- AvailableCoinsListUnspent(wallet, vCoins, coinControl);
- for (const COutput& out : vCoins) {
- if (out.spendable) {
- balance += out.txout.nValue;
- }
- }
- return balance;
+ return AvailableCoins(wallet,
+ coinControl,
+ std::nullopt, /*feerate=*/
+ 1, /*nMinimumAmount*/
+ MAX_MONEY, /*nMaximumAmount*/
+ MAX_MONEY, /*nMinimumSumAmount*/
+ 0 /*nMaximumCount*/
+ ).total_amount;
}
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output)
@@ -260,11 +271,8 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
AssertLockHeld(wallet.cs_wallet);
std::map<CTxDestination, std::vector<COutput>> result;
- std::vector<COutput> availableCoins;
-
- AvailableCoinsListUnspent(wallet, availableCoins);
- for (const COutput& coin : availableCoins) {
+ for (const COutput& coin : AvailableCoinsListUnspent(wallet).coins) {
CTxDestination address;
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
@@ -791,11 +799,16 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
CAmount selection_target = recipients_sum + not_input_fees;
// Get available coins
- std::vector<COutput> vAvailableCoins;
- AvailableCoins(wallet, vAvailableCoins, &coin_control, coin_selection_params.m_effective_feerate, 1, MAX_MONEY, MAX_MONEY, 0);
+ auto res_available_coins = AvailableCoins(wallet,
+ &coin_control,
+ coin_selection_params.m_effective_feerate,
+ 1, /*nMinimumAmount*/
+ MAX_MONEY, /*nMaximumAmount*/
+ MAX_MONEY, /*nMinimumSumAmount*/
+ 0); /*nMaximumCount*/
// Choose coins to use
- std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
+ std::optional<SelectionResult> result = SelectCoins(wallet, res_available_coins.coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
if (!result) {
error = _("Insufficient funds");
return std::nullopt;