Skip to content

Commit 3740cdd

Browse files
committed
Merge bitcoin/bitcoin#24091: wallet: Consolidate CInputCoin and COutput
049003f coinselection: Remove COutput operators == and != (Andrew Chow) f6c39c6 coinselection: Remove CInputCoin (Andrew Chow) 70f31f1 coinselection: Use COutput instead of CInputCoin (Andrew Chow) 14fbb57 coinselection: Add effective value and fees to COutput (Andrew Chow) f082123 moveonly: move COutput to coinselection.h (Andrew Chow) 42e974e wallet: Remove CWallet and CWalletTx from COutput's constructor (Andrew Chow) 14d04d5 wallet: Replace CWalletTx in COutput with COutPoint and CTxOut (Andrew Chow) 0ba4d19 wallet: Provide input bytes to COutput (Andrew Chow) d51f27d wallet: Store whether a COutput is from the wallet (Andrew Chow) b799814 wallet: Store tx time in COutput (Andrew Chow) 4602295 wallet: Remove use_max_sig default value (Andrew Chow) 10379f0 scripted-diff: Rename COutput member variables (Andrew Chow) c7c64db wallet: cleanup COutput constructor (Andrew Chow) Pull request description: While working on coin selection code, it occurred to me that `CInputCoin` is really a subset of `COutput` and the conversion of a `COutput` to a `CInputCoin` does not appear to be all that useful. So this PR adds fields that are present in `CInputCoin` to `COutput` and replaces the usage of `CInputCoin` with `COutput`. `COutput` is also moved to coinselection.h. As part of this move, the usage of `CWalletTx` is removed from `COutput`. It is instead replaced by storing a `COutPoint` and the `CTxOut` rather than the entire `CWalletTx` as coin selection does not really need the full `CWalletTx`. The `CWalletTx` was only used for figuring out whether the transaction containing the output was from the current wallet, and for the transaction's time. These are now parameters to `COutput`'s constructor. ACKs for top commit: ryanofsky: Code review ACK 049003f, just adding comments and removing == operators since last review w0xlt: reACK 049003f Xekyo: reACK 049003f Tree-SHA512: 048b4cd620a0415e1d9fe8597257ee4bc64656566e1d28a9bdd147d6d72dc87c3f34a3339fa9ab6acf42c388df7901fc4ee900ccaabc3de790ffad162b544c15
2 parents f0c9ba2 + 049003f commit 3740cdd

File tree

9 files changed

+183
-219
lines changed

9 files changed

+183
-219
lines changed

src/bench/coin_selection.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
using node::NodeContext;
1515
using wallet::AttemptSelection;
16-
using wallet::CInputCoin;
1716
using wallet::COutput;
1817
using wallet::CWallet;
1918
using wallet::CWalletTx;
@@ -58,7 +57,7 @@ static void CoinSelection(benchmark::Bench& bench)
5857
// Create coins
5958
std::vector<COutput> coins;
6059
for (const auto& wtx : wtxs) {
61-
coins.emplace_back(wallet, *wtx, 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */);
60+
coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/ 6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx->GetTxTime(), /*from_me=*/ true);
6261
}
6362

6463
const CoinEligibilityFilter filter_standard(1, 6, 0);
@@ -81,17 +80,15 @@ static void CoinSelection(benchmark::Bench& bench)
8180
});
8281
}
8382

84-
typedef std::set<CInputCoin> CoinSet;
85-
8683
// Copied from src/wallet/test/coinselector_tests.cpp
8784
static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>& set)
8885
{
8986
CMutableTransaction tx;
9087
tx.vout.resize(nInput + 1);
9188
tx.vout[nInput].nValue = nValue;
92-
CInputCoin coin(MakeTransactionRef(tx), nInput);
89+
COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 0, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ true);
9390
set.emplace_back();
94-
set.back().Insert(coin, 0, true, 0, 0, false);
91+
set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
9592
}
9693
// Copied from src/wallet/test/coinselector_tests.cpp
9794
static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool)

src/wallet/coinselection.cpp

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ struct {
5050
* The Branch and Bound algorithm is described in detail in Murch's Master Thesis:
5151
* https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
5252
*
53-
* @param const std::vector<CInputCoin>& utxo_pool The set of UTXOs that we are choosing from.
54-
* These UTXOs will be sorted in descending order by effective value and the CInputCoins'
53+
* @param const std::vector<OutputGroup>& utxo_pool The set of UTXO groups that we are choosing from.
54+
* These UTXO groups will be sorted in descending order by effective value and the OutputGroups'
5555
* values are their effective values.
5656
* @param const CAmount& selection_target This is the value that we want to select. It is the lower
5757
* bound of the range.
@@ -309,29 +309,29 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
309309
310310
******************************************************************************/
311311

312-
void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only) {
312+
void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) {
313313
// Compute the effective value first
314-
const CAmount coin_fee = output.m_input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.m_input_bytes);
314+
const CAmount coin_fee = output.input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.input_bytes);
315315
const CAmount ev = output.txout.nValue - coin_fee;
316316

317317
// Filter for positive only here before adding the coin
318318
if (positive_only && ev <= 0) return;
319319

320320
m_outputs.push_back(output);
321-
CInputCoin& coin = m_outputs.back();
321+
COutput& coin = m_outputs.back();
322322

323-
coin.m_fee = coin_fee;
324-
fee += coin.m_fee;
323+
coin.fee = coin_fee;
324+
fee += coin.fee;
325325

326-
coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes);
327-
long_term_fee += coin.m_long_term_fee;
326+
coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
327+
long_term_fee += coin.long_term_fee;
328328

329329
coin.effective_value = ev;
330330
effective_value += coin.effective_value;
331331

332-
m_from_me &= from_me;
333-
m_value += output.txout.nValue;
334-
m_depth = std::min(m_depth, depth);
332+
m_from_me &= coin.from_me;
333+
m_value += coin.txout.nValue;
334+
m_depth = std::min(m_depth, coin.depth);
335335
// ancestors here express the number of ancestors the new coin will end up having, which is
336336
// the sum, rather than the max; this will overestimate in the cases where multiple inputs
337337
// have common ancestors
@@ -353,16 +353,16 @@ CAmount OutputGroup::GetSelectionAmount() const
353353
return m_subtract_fee_outputs ? m_value : effective_value;
354354
}
355355

356-
CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
356+
CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
357357
{
358358
// This function should not be called with empty inputs as that would mean the selection failed
359359
assert(!inputs.empty());
360360

361361
// Always consider the cost of spending an input now vs in the future.
362362
CAmount waste = 0;
363363
CAmount selected_effective_value = 0;
364-
for (const CInputCoin& coin : inputs) {
365-
waste += coin.m_fee - coin.m_long_term_fee;
364+
for (const COutput& coin : inputs) {
365+
waste += coin.fee - coin.long_term_fee;
366366
selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
367367
}
368368

@@ -407,14 +407,14 @@ void SelectionResult::AddInput(const OutputGroup& group)
407407
m_use_effective = !group.m_subtract_fee_outputs;
408408
}
409409

410-
const std::set<CInputCoin>& SelectionResult::GetInputSet() const
410+
const std::set<COutput>& SelectionResult::GetInputSet() const
411411
{
412412
return m_selected_inputs;
413413
}
414414

415-
std::vector<CInputCoin> SelectionResult::GetShuffledInputVector() const
415+
std::vector<COutput> SelectionResult::GetShuffledInputVector() const
416416
{
417-
std::vector<CInputCoin> coins(m_selected_inputs.begin(), m_selected_inputs.end());
417+
std::vector<COutput> coins(m_selected_inputs.begin(), m_selected_inputs.end());
418418
Shuffle(coins.begin(), coins.end(), FastRandomContext());
419419
return coins;
420420
}
@@ -426,4 +426,9 @@ bool SelectionResult::operator<(SelectionResult other) const
426426
// As this operator is only used in std::min_element, we want the result that has more inputs when waste are equal.
427427
return *m_waste < *other.m_waste || (*m_waste == *other.m_waste && m_selected_inputs.size() > other.m_selected_inputs.size());
428428
}
429+
430+
std::string COutput::ToString() const
431+
{
432+
return strprintf("COutput(%s, %d, %d) [%s]", outpoint.hash.ToString(), outpoint.n, depth, FormatMoney(txout.nValue));
433+
}
429434
} // namespace wallet

src/wallet/coinselection.h

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,56 +19,70 @@ static constexpr CAmount MIN_CHANGE{COIN / 100};
1919
static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2;
2020

2121
/** A UTXO under consideration for use in funding a new transaction. */
22-
class CInputCoin {
22+
class COutput
23+
{
2324
public:
24-
CInputCoin(const CTransactionRef& tx, unsigned int i)
25-
{
26-
if (!tx)
27-
throw std::invalid_argument("tx should not be null");
28-
if (i >= tx->vout.size())
29-
throw std::out_of_range("The output index is out of range");
30-
31-
outpoint = COutPoint(tx->GetHash(), i);
32-
txout = tx->vout[i];
33-
effective_value = txout.nValue;
34-
}
25+
/** The outpoint identifying this UTXO */
26+
COutPoint outpoint;
3527

36-
CInputCoin(const CTransactionRef& tx, unsigned int i, int input_bytes) : CInputCoin(tx, i)
37-
{
38-
m_input_bytes = input_bytes;
39-
}
28+
/** The output itself */
29+
CTxOut txout;
4030

41-
CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in)
42-
{
43-
outpoint = outpoint_in;
44-
txout = txout_in;
45-
effective_value = txout.nValue;
46-
}
31+
/**
32+
* Depth in block chain.
33+
* If > 0: the tx is on chain and has this many confirmations.
34+
* If = 0: the tx is waiting confirmation.
35+
* If < 0: a conflicting tx is on chain and has this many confirmations. */
36+
int depth;
4737

48-
CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in)
49-
{
50-
m_input_bytes = input_bytes;
51-
}
38+
/** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
39+
int input_bytes;
5240

53-
COutPoint outpoint;
54-
CTxOut txout;
41+
/** Whether we have the private keys to spend this output */
42+
bool spendable;
43+
44+
/** Whether we know how to spend this output, ignoring the lack of keys */
45+
bool solvable;
46+
47+
/**
48+
* Whether this output is considered safe to spend. Unconfirmed transactions
49+
* from outside keys and unconfirmed replacement transactions are considered
50+
* unsafe and will not be used to fund new spending transactions.
51+
*/
52+
bool safe;
53+
54+
/** The time of the transaction containing this output as determined by CWalletTx::nTimeSmart */
55+
int64_t time;
56+
57+
/** Whether the transaction containing this output is sent from the owning wallet */
58+
bool from_me;
59+
60+
/** The output's value minus fees required to spend it. Initialized as the output's absolute value. */
5561
CAmount effective_value;
56-
CAmount m_fee{0};
57-
CAmount m_long_term_fee{0};
5862

59-
/** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
60-
int m_input_bytes{-1};
63+
/** The fee required to spend this output at the transaction's target feerate. */
64+
CAmount fee{0};
6165

62-
bool operator<(const CInputCoin& rhs) const {
63-
return outpoint < rhs.outpoint;
64-
}
66+
/** The fee required to spend this output at the consolidation feerate. */
67+
CAmount long_term_fee{0};
6568

66-
bool operator!=(const CInputCoin& rhs) const {
67-
return outpoint != rhs.outpoint;
68-
}
69+
COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me)
70+
: outpoint(outpoint),
71+
txout(txout),
72+
depth(depth),
73+
input_bytes(input_bytes),
74+
spendable(spendable),
75+
solvable(solvable),
76+
safe(safe),
77+
time(time),
78+
from_me(from_me),
79+
effective_value(txout.nValue)
80+
{}
81+
82+
std::string ToString() const;
6983

70-
bool operator==(const CInputCoin& rhs) const {
71-
return outpoint == rhs.outpoint;
84+
bool operator<(const COutput& rhs) const {
85+
return outpoint < rhs.outpoint;
7286
}
7387
};
7488

@@ -143,7 +157,7 @@ struct CoinEligibilityFilter
143157
struct OutputGroup
144158
{
145159
/** The list of UTXOs contained in this output group. */
146-
std::vector<CInputCoin> m_outputs;
160+
std::vector<COutput> m_outputs;
147161
/** Whether the UTXOs were sent by the wallet to itself. This is relevant because we may want at
148162
* least a certain number of confirmations on UTXOs received from outside wallets while trusting
149163
* our own UTXOs more. */
@@ -180,7 +194,7 @@ struct OutputGroup
180194
m_subtract_fee_outputs(params.m_subtract_fee_outputs)
181195
{}
182196

183-
void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only);
197+
void Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only);
184198
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
185199
CAmount GetSelectionAmount() const;
186200
};
@@ -202,13 +216,13 @@ struct OutputGroup
202216
* @param[in] use_effective_value Whether to use the input's effective value (when true) or the real value (when false).
203217
* @return The waste
204218
*/
205-
[[nodiscard]] CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
219+
[[nodiscard]] CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
206220

207221
struct SelectionResult
208222
{
209223
private:
210224
/** Set of inputs selected by the algorithm to use in the transaction */
211-
std::set<CInputCoin> m_selected_inputs;
225+
std::set<COutput> m_selected_inputs;
212226
/** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */
213227
const CAmount m_target;
214228
/** Whether the input values for calculations should be the effective value (true) or normal value (false) */
@@ -234,9 +248,9 @@ struct SelectionResult
234248
[[nodiscard]] CAmount GetWaste() const;
235249

236250
/** Get m_selected_inputs */
237-
const std::set<CInputCoin>& GetInputSet() const;
238-
/** Get the vector of CInputCoins that will be used to fill in a CTransaction's vin */
239-
std::vector<CInputCoin> GetShuffledInputVector() const;
251+
const std::set<COutput>& GetInputSet() const;
252+
/** Get the vector of COutputs that will be used to fill in a CTransaction's vin */
253+
std::vector<COutput> GetShuffledInputVector() const;
240254

241255
bool operator<(SelectionResult other) const;
242256
};

src/wallet/interfaces.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,17 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
111111
return result;
112112
}
113113

114+
WalletTxOut MakeWalletTxOut(const CWallet& wallet,
115+
const COutput& output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
116+
{
117+
WalletTxOut result;
118+
result.txout = output.txout;
119+
result.time = output.time;
120+
result.depth_in_main_chain = output.depth;
121+
result.is_spent = wallet.IsSpent(output.outpoint.hash, output.outpoint.n);
122+
return result;
123+
}
124+
114125
class WalletImpl : public Wallet
115126
{
116127
public:
@@ -419,8 +430,8 @@ class WalletImpl : public Wallet
419430
for (const auto& entry : ListCoins(*m_wallet)) {
420431
auto& group = result[entry.first];
421432
for (const auto& coin : entry.second) {
422-
group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i),
423-
MakeWalletTxOut(*m_wallet, *coin.tx, coin.i, coin.nDepth));
433+
group.emplace_back(coin.outpoint,
434+
MakeWalletTxOut(*m_wallet, coin));
424435
}
425436
}
426437
return result;

src/wallet/rpc/coins.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -648,16 +648,16 @@ RPCHelpMan listunspent()
648648

649649
for (const COutput& out : vecOutputs) {
650650
CTxDestination address;
651-
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
651+
const CScript& scriptPubKey = out.txout.scriptPubKey;
652652
bool fValidAddress = ExtractDestination(scriptPubKey, address);
653-
bool reused = avoid_reuse && pwallet->IsSpentKey(out.tx->GetHash(), out.i);
653+
bool reused = avoid_reuse && pwallet->IsSpentKey(out.outpoint.hash, out.outpoint.n);
654654

655655
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
656656
continue;
657657

658658
UniValue entry(UniValue::VOBJ);
659-
entry.pushKV("txid", out.tx->GetHash().GetHex());
660-
entry.pushKV("vout", out.i);
659+
entry.pushKV("txid", out.outpoint.hash.GetHex());
660+
entry.pushKV("vout", (int)out.outpoint.n);
661661

662662
if (fValidAddress) {
663663
entry.pushKV("address", EncodeDestination(address));
@@ -702,29 +702,29 @@ RPCHelpMan listunspent()
702702
}
703703

704704
entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
705-
entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
706-
entry.pushKV("confirmations", out.nDepth);
707-
if (!out.nDepth) {
705+
entry.pushKV("amount", ValueFromAmount(out.txout.nValue));
706+
entry.pushKV("confirmations", out.depth);
707+
if (!out.depth) {
708708
size_t ancestor_count, descendant_count, ancestor_size;
709709
CAmount ancestor_fees;
710-
pwallet->chain().getTransactionAncestry(out.tx->GetHash(), ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
710+
pwallet->chain().getTransactionAncestry(out.outpoint.hash, ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
711711
if (ancestor_count) {
712712
entry.pushKV("ancestorcount", uint64_t(ancestor_count));
713713
entry.pushKV("ancestorsize", uint64_t(ancestor_size));
714714
entry.pushKV("ancestorfees", uint64_t(ancestor_fees));
715715
}
716716
}
717-
entry.pushKV("spendable", out.fSpendable);
718-
entry.pushKV("solvable", out.fSolvable);
719-
if (out.fSolvable) {
717+
entry.pushKV("spendable", out.spendable);
718+
entry.pushKV("solvable", out.solvable);
719+
if (out.solvable) {
720720
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
721721
if (provider) {
722722
auto descriptor = InferDescriptor(scriptPubKey, *provider);
723723
entry.pushKV("desc", descriptor->ToString());
724724
}
725725
}
726726
if (avoid_reuse) entry.pushKV("reused", reused);
727-
entry.pushKV("safe", out.fSafe);
727+
entry.pushKV("safe", out.safe);
728728
results.push_back(entry);
729729
}
730730

0 commit comments

Comments
 (0)