mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-10 09:06:15 +01:00
Merge bitcoin/bitcoin#25005: wallet: remove extra wtx lookup in 'AvailableCoins' + several code cleanups.
fd5c996d16
wallet: GetAvailableBalance, remove double walk-through every available coin (furszy)162d4ad10f
wallet: add 'only_spendable' filter to AvailableCoins (furszy)cdf185ccfb
wallet: remove unused IsSpentKey(hash, index) method (furszy)4b83bf8dbc
wallet: avoid extra IsSpentKey -> GetWalletTx lookups (furszy)3d8a282257
wallet: decouple IsSpentKey(scriptPubKey) from IsSpentKey(hash, n) (furszy)a06fa94ff8
wallet: IsSpent, 'COutPoint' arg instead of (hash, index) (furszy)91902b7720
wallet: IsLockedCoin, 'COutPoint' arg instead of (hash, index) (furszy)9472ca0a65
wallet: AvailableCoins, don't call 'wtx.tx->vout[i]' multiple times (furszy)4ce235ef8f
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: ACKfd5c996d16
brunoerg: crACKfd5c996d16
w0xlt: Code Review ACKfd5c996d16
Tree-SHA512: 376a85476f907f4f7d1fc3de74b3dbe159b8cc24687374d8739711ad202ea07a33e86f4e66dece836da3ae6985147119fe584f6e672f11d0450ba6bd165b3220
This commit is contained in:
commit
8be652e439
9 changed files with 118 additions and 107 deletions
|
@ -110,7 +110,7 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
||||||
result.txout = wtx.tx->vout[n];
|
result.txout = wtx.tx->vout[n];
|
||||||
result.time = wtx.GetTxTime();
|
result.time = wtx.GetTxTime();
|
||||||
result.depth_in_main_chain = depth;
|
result.depth_in_main_chain = depth;
|
||||||
result.is_spent = wallet.IsSpent(wtx.GetHash(), n);
|
result.is_spent = wallet.IsSpent(COutPoint(wtx.GetHash(), n));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
||||||
result.txout = output.txout;
|
result.txout = output.txout;
|
||||||
result.time = output.time;
|
result.time = output.time;
|
||||||
result.depth_in_main_chain = output.depth;
|
result.depth_in_main_chain = output.depth;
|
||||||
result.is_spent = wallet.IsSpent(output.outpoint.hash, output.outpoint.n);
|
result.is_spent = wallet.IsSpent(output.outpoint);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ public:
|
||||||
bool isLockedCoin(const COutPoint& output) override
|
bool isLockedCoin(const COutPoint& output) override
|
||||||
{
|
{
|
||||||
LOCK(m_wallet->cs_wallet);
|
LOCK(m_wallet->cs_wallet);
|
||||||
return m_wallet->IsLockedCoin(output.hash, output.n);
|
return m_wallet->IsLockedCoin(output);
|
||||||
}
|
}
|
||||||
void listLockedCoins(std::vector<COutPoint>& outputs) override
|
void listLockedCoins(std::vector<COutPoint>& outputs) override
|
||||||
{
|
{
|
||||||
|
|
|
@ -204,10 +204,9 @@ CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx,
|
||||||
bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
||||||
CAmount nCredit = 0;
|
CAmount nCredit = 0;
|
||||||
uint256 hashTx = wtx.GetHash();
|
uint256 hashTx = wtx.GetHash();
|
||||||
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
|
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
|
||||||
{
|
const CTxOut& txout = wtx.tx->vout[i];
|
||||||
if (!wallet.IsSpent(hashTx, i) && (allow_used_addresses || !wallet.IsSpentKey(hashTx, i))) {
|
if (!wallet.IsSpent(COutPoint(hashTx, i)) && (allow_used_addresses || !wallet.IsSpentKey(txout.scriptPubKey))) {
|
||||||
const CTxOut &txout = wtx.tx->vout[i];
|
|
||||||
nCredit += OutputGetCredit(wallet, txout, filter);
|
nCredit += OutputGetCredit(wallet, txout, filter);
|
||||||
if (!MoneyRange(nCredit))
|
if (!MoneyRange(nCredit))
|
||||||
throw std::runtime_error(std::string(__func__) + " : value out of range");
|
throw std::runtime_error(std::string(__func__) + " : value out of range");
|
||||||
|
@ -371,15 +370,15 @@ std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet)
|
||||||
if (nDepth < (CachedTxIsFromMe(wallet, wtx, ISMINE_ALL) ? 0 : 1))
|
if (nDepth < (CachedTxIsFromMe(wallet, wtx, ISMINE_ALL) ? 0 : 1))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
|
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
|
||||||
{
|
const auto& output = wtx.tx->vout[i];
|
||||||
CTxDestination addr;
|
CTxDestination addr;
|
||||||
if (!wallet.IsMine(wtx.tx->vout[i]))
|
if (!wallet.IsMine(output))
|
||||||
continue;
|
continue;
|
||||||
if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr))
|
if(!ExtractDestination(output.scriptPubKey, addr))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
CAmount n = wallet.IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue;
|
CAmount n = wallet.IsSpent(COutPoint(walletEntry.first, i)) ? 0 : output.nValue;
|
||||||
balances[addr] += n;
|
balances[addr] += n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,11 +341,11 @@ RPCHelpMan lockunspent()
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds");
|
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");
|
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) {
|
if (fUnlock && !is_locked) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output");
|
||||||
|
@ -638,7 +638,7 @@ RPCHelpMan listunspent()
|
||||||
cctl.m_max_depth = nMaxDepth;
|
cctl.m_max_depth = nMaxDepth;
|
||||||
cctl.m_include_unsafe_inputs = include_unsafe;
|
cctl.m_include_unsafe_inputs = include_unsafe;
|
||||||
LOCK(pwallet->cs_wallet);
|
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);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
@ -649,7 +649,7 @@ RPCHelpMan listunspent()
|
||||||
CTxDestination address;
|
CTxDestination address;
|
||||||
const CScript& scriptPubKey = out.txout.scriptPubKey;
|
const CScript& scriptPubKey = out.txout.scriptPubKey;
|
||||||
bool fValidAddress = ExtractDestination(scriptPubKey, address);
|
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)))
|
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -1367,7 +1367,6 @@ RPCHelpMan sendall()
|
||||||
|
|
||||||
CMutableTransaction rawTx{ConstructTransaction(options["inputs"], recipient_key_value_pairs, options["locktime"], rbf)};
|
CMutableTransaction rawTx{ConstructTransaction(options["inputs"], recipient_key_value_pairs, options["locktime"], rbf)};
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
std::vector<COutput> all_the_utxos;
|
|
||||||
|
|
||||||
CAmount total_input_value(0);
|
CAmount total_input_value(0);
|
||||||
bool send_max{options.exists("send_max") ? options["send_max"].get_bool() : false};
|
bool send_max{options.exists("send_max") ? options["send_max"].get_bool() : false};
|
||||||
|
@ -1375,7 +1374,7 @@ RPCHelpMan sendall()
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot combine send_max with specific inputs.");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot combine send_max with specific inputs.");
|
||||||
} else if (options.exists("inputs")) {
|
} else if (options.exists("inputs")) {
|
||||||
for (const CTxIn& input : rawTx.vin) {
|
for (const CTxIn& input : rawTx.vin) {
|
||||||
if (pwallet->IsSpent(input.prevout.hash, input.prevout.n)) {
|
if (pwallet->IsSpent(input.prevout)) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not available. UTXO (%s:%d) was already spent.", input.prevout.hash.ToString(), input.prevout.n));
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not available. UTXO (%s:%d) was already spent.", input.prevout.hash.ToString(), input.prevout.n));
|
||||||
}
|
}
|
||||||
const CWalletTx* tx{pwallet->GetWalletTx(input.prevout.hash)};
|
const CWalletTx* tx{pwallet->GetWalletTx(input.prevout.hash)};
|
||||||
|
@ -1385,8 +1384,7 @@ RPCHelpMan sendall()
|
||||||
total_input_value += tx->tx->vout[input.prevout.n].nValue;
|
total_input_value += tx->tx->vout[input.prevout.n].nValue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AvailableCoins(*pwallet, all_the_utxos, &coin_control, fee_rate, /*nMinimumAmount=*/0);
|
for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).coins) {
|
||||||
for (const COutput& output : all_the_utxos) {
|
|
||||||
CHECK_NONFATAL(output.input_bytes > 0);
|
CHECK_NONFATAL(output.input_bytes > 0);
|
||||||
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
|
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -84,12 +84,18 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
|
||||||
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
|
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);
|
AssertLockHeld(wallet.cs_wallet);
|
||||||
|
|
||||||
vCoins.clear();
|
CoinsResult result;
|
||||||
CAmount nTotal = 0;
|
|
||||||
// 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
|
// 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
|
// 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);
|
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);
|
bool tx_from_me = CachedTxIsFromMe(wallet, wtx, ISMINE_ALL);
|
||||||
|
|
||||||
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
|
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
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount)
|
if (output.nValue < nMinimumAmount || output.nValue > nMaximumAmount)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
|
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(outpoint))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (wallet.IsLockedCoin(entry.first, i))
|
if (wallet.IsLockedCoin(outpoint))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (wallet.IsSpent(wtxid, i))
|
if (wallet.IsSpent(outpoint))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
isminetype mine = wallet.IsMine(wtx.tx->vout[i]);
|
isminetype mine = wallet.IsMine(output);
|
||||||
|
|
||||||
if (mine == ISMINE_NO) {
|
if (mine == ISMINE_NO) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allow_used_addresses && wallet.IsSpentKey(wtxid, i)) {
|
if (!allow_used_addresses && wallet.IsSpentKey(output.scriptPubKey)) {
|
||||||
continue;
|
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));
|
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.
|
// Checks the sum amount of all UTXO's.
|
||||||
if (nMinimumSumAmount != MAX_MONEY) {
|
if (nMinimumSumAmount != MAX_MONEY) {
|
||||||
nTotal += wtx.tx->vout[i].nValue;
|
if (result.total_amount >= nMinimumSumAmount) {
|
||||||
|
return result;
|
||||||
if (nTotal >= nMinimumSumAmount) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks the maximum number of UTXO's.
|
// Checks the maximum number of UTXO's.
|
||||||
if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
|
if (nMaximumCount > 0 && result.coins.size() >= nMaximumCount) {
|
||||||
return;
|
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)
|
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)
|
||||||
{
|
{
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
return AvailableCoins(wallet,
|
||||||
CAmount balance = 0;
|
coinControl,
|
||||||
std::vector<COutput> vCoins;
|
std::nullopt, /*feerate=*/
|
||||||
AvailableCoinsListUnspent(wallet, vCoins, coinControl);
|
1, /*nMinimumAmount*/
|
||||||
for (const COutput& out : vCoins) {
|
MAX_MONEY, /*nMaximumAmount*/
|
||||||
if (out.spendable) {
|
MAX_MONEY, /*nMinimumSumAmount*/
|
||||||
balance += out.txout.nValue;
|
0 /*nMaximumCount*/
|
||||||
}
|
).total_amount;
|
||||||
}
|
|
||||||
return balance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output)
|
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);
|
AssertLockHeld(wallet.cs_wallet);
|
||||||
|
|
||||||
std::map<CTxDestination, std::vector<COutput>> result;
|
std::map<CTxDestination, std::vector<COutput>> result;
|
||||||
std::vector<COutput> availableCoins;
|
|
||||||
|
|
||||||
AvailableCoinsListUnspent(wallet, availableCoins);
|
for (const COutput& coin : AvailableCoinsListUnspent(wallet).coins) {
|
||||||
|
|
||||||
for (const COutput& coin : availableCoins) {
|
|
||||||
CTxDestination address;
|
CTxDestination address;
|
||||||
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
|
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
|
||||||
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
|
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
|
||||||
|
@ -791,11 +799,16 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
||||||
CAmount selection_target = recipients_sum + not_input_fees;
|
CAmount selection_target = recipients_sum + not_input_fees;
|
||||||
|
|
||||||
// Get available coins
|
// Get available coins
|
||||||
std::vector<COutput> vAvailableCoins;
|
auto res_available_coins = AvailableCoins(wallet,
|
||||||
AvailableCoins(wallet, vAvailableCoins, &coin_control, coin_selection_params.m_effective_feerate, 1, MAX_MONEY, MAX_MONEY, 0);
|
&coin_control,
|
||||||
|
coin_selection_params.m_effective_feerate,
|
||||||
|
1, /*nMinimumAmount*/
|
||||||
|
MAX_MONEY, /*nMaximumAmount*/
|
||||||
|
MAX_MONEY, /*nMinimumSumAmount*/
|
||||||
|
0); /*nMaximumCount*/
|
||||||
|
|
||||||
// Choose coins to use
|
// 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) {
|
if (!result) {
|
||||||
error = _("Insufficient funds");
|
error = _("Insufficient funds");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
|
@ -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 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);
|
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
|
||||||
|
|
||||||
|
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.
|
||||||
|
* 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
|
* 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.
|
* 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);
|
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr);
|
||||||
|
|
||||||
|
|
|
@ -583,9 +583,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
|
||||||
// Lock both coins. Confirm number of available coins drops to 0.
|
// Lock both coins. Confirm number of available coins drops to 0.
|
||||||
{
|
{
|
||||||
LOCK(wallet->cs_wallet);
|
LOCK(wallet->cs_wallet);
|
||||||
std::vector<COutput> available;
|
BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).coins.size(), 2U);
|
||||||
AvailableCoinsListUnspent(*wallet, available);
|
|
||||||
BOOST_CHECK_EQUAL(available.size(), 2U);
|
|
||||||
}
|
}
|
||||||
for (const auto& group : list) {
|
for (const auto& group : list) {
|
||||||
for (const auto& coin : group.second) {
|
for (const auto& coin : group.second) {
|
||||||
|
@ -595,9 +593,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
LOCK(wallet->cs_wallet);
|
LOCK(wallet->cs_wallet);
|
||||||
std::vector<COutput> available;
|
BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).coins.size(), 0U);
|
||||||
AvailableCoinsListUnspent(*wallet, available);
|
|
||||||
BOOST_CHECK_EQUAL(available.size(), 0U);
|
|
||||||
}
|
}
|
||||||
// Confirm ListCoins still returns same result as before, despite coins
|
// Confirm ListCoins still returns same result as before, despite coins
|
||||||
// being locked.
|
// being locked.
|
||||||
|
|
|
@ -629,14 +629,12 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
|
||||||
* Outpoint is spent if any non-conflicted transaction
|
* Outpoint is spent if any non-conflicted transaction
|
||||||
* spends it:
|
* spends it:
|
||||||
*/
|
*/
|
||||||
bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
|
bool CWallet::IsSpent(const COutPoint& outpoint) const
|
||||||
{
|
{
|
||||||
const COutPoint outpoint(hash, n);
|
|
||||||
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
|
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
|
||||||
range = mapTxSpends.equal_range(outpoint);
|
range = mapTxSpends.equal_range(outpoint);
|
||||||
|
|
||||||
for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
|
for (TxSpends::const_iterator it = range.first; it != range.second; ++it) {
|
||||||
{
|
|
||||||
const uint256& wtxid = it->second;
|
const uint256& wtxid = it->second;
|
||||||
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
|
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
|
||||||
if (mit != mapWallet.end()) {
|
if (mit != mapWallet.end()) {
|
||||||
|
@ -908,35 +906,31 @@ void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const
|
bool CWallet::IsSpentKey(const CScript& scriptPubKey) const
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
const CWalletTx* srctx = GetWalletTx(hash);
|
CTxDestination dest;
|
||||||
if (srctx) {
|
if (!ExtractDestination(scriptPubKey, dest)) {
|
||||||
assert(srctx->tx->vout.size() > n);
|
return false;
|
||||||
CTxDestination dest;
|
}
|
||||||
if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) {
|
if (IsAddressUsed(dest)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
if (IsAddressUsed(dest)) {
|
if (IsLegacy()) {
|
||||||
return true;
|
LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
|
||||||
}
|
assert(spk_man != nullptr);
|
||||||
if (IsLegacy()) {
|
for (const auto& keyid : GetAffectedKeys(scriptPubKey, *spk_man)) {
|
||||||
LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
|
WitnessV0KeyHash wpkh_dest(keyid);
|
||||||
assert(spk_man != nullptr);
|
if (IsAddressUsed(wpkh_dest)) {
|
||||||
for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) {
|
return true;
|
||||||
WitnessV0KeyHash wpkh_dest(keyid);
|
}
|
||||||
if (IsAddressUsed(wpkh_dest)) {
|
ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
|
||||||
return true;
|
if (IsAddressUsed(sh_wpkh_dest)) {
|
||||||
}
|
return true;
|
||||||
ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
|
}
|
||||||
if (IsAddressUsed(sh_wpkh_dest)) {
|
PKHash pkh_dest(keyid);
|
||||||
return true;
|
if (IsAddressUsed(pkh_dest)) {
|
||||||
}
|
return true;
|
||||||
PKHash pkh_dest(keyid);
|
|
||||||
if (IsAddressUsed(pkh_dest)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2450,12 +2444,10 @@ bool CWallet::UnlockAllCoins()
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
|
bool CWallet::IsLockedCoin(const COutPoint& output) const
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
COutPoint outpt(hash, n);
|
return setLockedCoins.count(output) > 0;
|
||||||
|
|
||||||
return (setLockedCoins.count(outpt) > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
|
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
|
||||||
|
|
|
@ -441,16 +441,16 @@ public:
|
||||||
//! check whether we support the named feature
|
//! check whether we support the named feature
|
||||||
bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); }
|
bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); }
|
||||||
|
|
||||||
bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool IsSpent(const COutPoint& outpoint) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
// Whether this or any known UTXO with the same single key has been spent.
|
// Whether this or any known scriptPubKey with the same single key has been spent.
|
||||||
bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool IsSpentKey(const CScript& scriptPubKey) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
/** Display address on an external signer. Returns false if external signer support is not compiled */
|
/** Display address on an external signer. Returns false if external signer support is not compiled */
|
||||||
bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool IsLockedCoin(const COutPoint& output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
bool LockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool LockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
bool UnlockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool UnlockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
bool UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
Loading…
Add table
Reference in a new issue