-
Notifications
You must be signed in to change notification settings - Fork 37.4k
wallet: remove extra wtx lookup in 'AvailableCoins' + several code cleanups. #25005
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4ce235e
9472ca0
91902b7
a06fa94
3d8a282
4b83bf8
cdf185c
162d4ad
fd5c996
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
8000
|
@@ -341,11 +341,11 @@ RPCHelpMan lockunspent() | |
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds"); | ||
} | ||
|
||
if (pwallet->IsSpent(outpt.hash, outpt.n)) { | ||
if (pwallet->IsSpent(outpt)) { | ||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); | ||
} | ||
|
||
const bool is_locked = pwallet->IsLockedCoin(outpt.hash, outpt.n); | ||
const bool is_locked = pwallet->IsLockedCoin(outpt); | ||
|
||
if (fUnlock && !is_locked) { | ||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output"); | ||
|
@@ -638,7 +638,7 @@ RPCHelpMan listunspent() | |
cctl.m_max_depth = nMaxDepth; | ||
cctl.m_include_unsafe_inputs = include_unsafe; | ||
LOCK(pwallet->cs_wallet); | ||
AvailableCoinsListUnspent(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); | ||
vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).coins; | ||
} | ||
|
||
LOCK(pwallet->cs_wallet); | ||
|
@@ -649,7 +649,7 @@ RPCHelpMan listunspent() | |
CTxDestination address; | ||
const CScript& scriptPubKey = out.txout.scriptPubKey; | ||
bool fValidAddress = ExtractDestination(scriptPubKey, address); | ||
bool reused = avoid_reuse && pwallet->IsSpentKey(out.outpoint.hash, out.outpoint.n); | ||
bool reused = avoid_reuse && pwallet->IsSpentKey(scriptPubKey); | ||
|
||
if (destinations.size() && (!fValidAddress || !destinations.count(address))) | ||
continue; | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally, |
||||||
// 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; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're explicitly relying on |
||||||
} | ||||||
|
||||||
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, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think the
Suggested change
|
||||||
&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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: since you're already touching this, maybe you could update |
||||||
if (!result) { | ||||||
error = _("Insufficient funds"); | ||||||
return std::nullopt; | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,16 +34,29 @@ struct TxSize { | |
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr); | ||
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); | ||
|
||
< CEB7 /td> | struct CoinsResult { | |
std::vector<COutput> coins; | ||
// Sum of all the coins amounts | ||
CAmount total_amount{0}; | ||
}; | ||
/** | ||
* populate vCoins with vector of available COutputs. | ||
* Return vector of available COutputs. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
* By default, returns only the spendable coins. | ||
*/ | ||
void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, std::optional<CFeeRate> feerate = std::nullopt, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); | ||
CoinsResult AvailableCoins(const CWallet& wallet, | ||
const CCoinControl* coinControl = nullptr, | ||
std::optional<CFeeRate> feerate = std::nullopt, | ||
const CAmount& nMinimumAmount = 1, | ||
const CAmount& nMaximumAmount = MAX_MONEY, | ||
const CAmount& nMinimumSumAmount = MAX_MONEY, | ||
const uint64_t nMaximumCount = 0, | ||
bool only_spendable = true) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); | ||
|
||
/** | ||
* Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function | ||
* to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction. | ||
*/ | ||
void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); | ||
CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); | ||
|
||
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's preferred to use {} list initialization - any reason not to? (Here and in quite a few other places)