mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-12 10:30:08 +01:00
Merge bitcoin/bitcoin#24091: wallet: Consolidate CInputCoin and COutput
049003fe68
coinselection: Remove COutput operators == and != (Andrew Chow)f6c39c6adb
coinselection: Remove CInputCoin (Andrew Chow)70f31f1a81
coinselection: Use COutput instead of CInputCoin (Andrew Chow)14fbb57b79
coinselection: Add effective value and fees to COutput (Andrew Chow)f0821230b8
moveonly: move COutput to coinselection.h (Andrew Chow)42e974e15c
wallet: Remove CWallet and CWalletTx from COutput's constructor (Andrew Chow)14d04d5ad1
wallet: Replace CWalletTx in COutput with COutPoint and CTxOut (Andrew Chow)0ba4d1916e
wallet: Provide input bytes to COutput (Andrew Chow)d51f27d3bb
wallet: Store whether a COutput is from the wallet (Andrew Chow)b799814bbd
wallet: Store tx time in COutput (Andrew Chow)46022953ee
wallet: Remove use_max_sig default value (Andrew Chow)10379f007f
scripted-diff: Rename COutput member variables (Andrew Chow)c7c64db41e
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 ACK049003fe68
, just adding comments and removing == operators since last review w0xlt: reACK049003f
Xekyo: reACK049003fe68
Tree-SHA512: 048b4cd620a0415e1d9fe8597257ee4bc64656566e1d28a9bdd147d6d72dc87c3f34a3339fa9ab6acf42c388df7901fc4ee900ccaabc3de790ffad162b544c15
This commit is contained in:
commit
3740cdd125
9 changed files with 186 additions and 222 deletions
|
@ -13,7 +13,6 @@
|
||||||
|
|
||||||
using node::NodeContext;
|
using node::NodeContext;
|
||||||
using wallet::AttemptSelection;
|
using wallet::AttemptSelection;
|
||||||
using wallet::CInputCoin;
|
|
||||||
using wallet::COutput;
|
using wallet::COutput;
|
||||||
using wallet::CWallet;
|
using wallet::CWallet;
|
||||||
using wallet::CWalletTx;
|
using wallet::CWalletTx;
|
||||||
|
@ -58,7 +57,7 @@ static void CoinSelection(benchmark::Bench& bench)
|
||||||
// Create coins
|
// Create coins
|
||||||
std::vector<COutput> coins;
|
std::vector<COutput> coins;
|
||||||
for (const auto& wtx : wtxs) {
|
for (const auto& wtx : wtxs) {
|
||||||
coins.emplace_back(wallet, *wtx, 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CoinEligibilityFilter filter_standard(1, 6, 0);
|
const CoinEligibilityFilter filter_standard(1, 6, 0);
|
||||||
|
@ -81,17 +80,15 @@ static void CoinSelection(benchmark::Bench& bench)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef std::set<CInputCoin> CoinSet;
|
|
||||||
|
|
||||||
// Copied from src/wallet/test/coinselector_tests.cpp
|
// Copied from src/wallet/test/coinselector_tests.cpp
|
||||||
static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>& set)
|
static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>& set)
|
||||||
{
|
{
|
||||||
CMutableTransaction tx;
|
CMutableTransaction tx;
|
||||||
tx.vout.resize(nInput + 1);
|
tx.vout.resize(nInput + 1);
|
||||||
tx.vout[nInput].nValue = nValue;
|
tx.vout[nInput].nValue = nValue;
|
||||||
CInputCoin coin(MakeTransactionRef(tx), nInput);
|
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);
|
||||||
set.emplace_back();
|
set.emplace_back();
|
||||||
set.back().Insert(coin, 0, true, 0, 0, false);
|
set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
|
||||||
}
|
}
|
||||||
// Copied from src/wallet/test/coinselector_tests.cpp
|
// Copied from src/wallet/test/coinselector_tests.cpp
|
||||||
static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool)
|
static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool)
|
||||||
|
|
|
@ -50,8 +50,8 @@ struct {
|
||||||
* The Branch and Bound algorithm is described in detail in Murch's Master Thesis:
|
* The Branch and Bound algorithm is described in detail in Murch's Master Thesis:
|
||||||
* https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
|
* https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
|
||||||
*
|
*
|
||||||
* @param const std::vector<CInputCoin>& utxo_pool The set of UTXOs that we are choosing from.
|
* @param const std::vector<OutputGroup>& utxo_pool The set of UTXO groups that we are choosing from.
|
||||||
* These UTXOs will be sorted in descending order by effective value and the CInputCoins'
|
* These UTXO groups will be sorted in descending order by effective value and the OutputGroups'
|
||||||
* values are their effective values.
|
* values are their effective values.
|
||||||
* @param const CAmount& selection_target This is the value that we want to select. It is the lower
|
* @param const CAmount& selection_target This is the value that we want to select. It is the lower
|
||||||
* bound of the range.
|
* bound of the range.
|
||||||
|
@ -309,29 +309,29 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
|
||||||
|
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only) {
|
void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) {
|
||||||
// Compute the effective value first
|
// Compute the effective value first
|
||||||
const CAmount coin_fee = output.m_input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.m_input_bytes);
|
const CAmount coin_fee = output.input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.input_bytes);
|
||||||
const CAmount ev = output.txout.nValue - coin_fee;
|
const CAmount ev = output.txout.nValue - coin_fee;
|
||||||
|
|
||||||
// Filter for positive only here before adding the coin
|
// Filter for positive only here before adding the coin
|
||||||
if (positive_only && ev <= 0) return;
|
if (positive_only && ev <= 0) return;
|
||||||
|
|
||||||
m_outputs.push_back(output);
|
m_outputs.push_back(output);
|
||||||
CInputCoin& coin = m_outputs.back();
|
COutput& coin = m_outputs.back();
|
||||||
|
|
||||||
coin.m_fee = coin_fee;
|
coin.fee = coin_fee;
|
||||||
fee += coin.m_fee;
|
fee += coin.fee;
|
||||||
|
|
||||||
coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes);
|
coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
|
||||||
long_term_fee += coin.m_long_term_fee;
|
long_term_fee += coin.long_term_fee;
|
||||||
|
|
||||||
coin.effective_value = ev;
|
coin.effective_value = ev;
|
||||||
effective_value += coin.effective_value;
|
effective_value += coin.effective_value;
|
||||||
|
|
||||||
m_from_me &= from_me;
|
m_from_me &= coin.from_me;
|
||||||
m_value += output.txout.nValue;
|
m_value += coin.txout.nValue;
|
||||||
m_depth = std::min(m_depth, depth);
|
m_depth = std::min(m_depth, coin.depth);
|
||||||
// ancestors here express the number of ancestors the new coin will end up having, which is
|
// ancestors here express the number of ancestors the new coin will end up having, which is
|
||||||
// the sum, rather than the max; this will overestimate in the cases where multiple inputs
|
// the sum, rather than the max; this will overestimate in the cases where multiple inputs
|
||||||
// have common ancestors
|
// have common ancestors
|
||||||
|
@ -353,7 +353,7 @@ CAmount OutputGroup::GetSelectionAmount() const
|
||||||
return m_subtract_fee_outputs ? m_value : effective_value;
|
return m_subtract_fee_outputs ? m_value : effective_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
|
CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
|
||||||
{
|
{
|
||||||
// This function should not be called with empty inputs as that would mean the selection failed
|
// This function should not be called with empty inputs as that would mean the selection failed
|
||||||
assert(!inputs.empty());
|
assert(!inputs.empty());
|
||||||
|
@ -361,8 +361,8 @@ CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cos
|
||||||
// Always consider the cost of spending an input now vs in the future.
|
// Always consider the cost of spending an input now vs in the future.
|
||||||
CAmount waste = 0;
|
CAmount waste = 0;
|
||||||
CAmount selected_effective_value = 0;
|
CAmount selected_effective_value = 0;
|
||||||
for (const CInputCoin& coin : inputs) {
|
for (const COutput& coin : inputs) {
|
||||||
waste += coin.m_fee - coin.m_long_term_fee;
|
waste += coin.fee - coin.long_term_fee;
|
||||||
selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
|
selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,14 +407,14 @@ void SelectionResult::AddInput(const OutputGroup& group)
|
||||||
m_use_effective = !group.m_subtract_fee_outputs;
|
m_use_effective = !group.m_subtract_fee_outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::set<CInputCoin>& SelectionResult::GetInputSet() const
|
const std::set<COutput>& SelectionResult::GetInputSet() const
|
||||||
{
|
{
|
||||||
return m_selected_inputs;
|
return m_selected_inputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CInputCoin> SelectionResult::GetShuffledInputVector() const
|
std::vector<COutput> SelectionResult::GetShuffledInputVector() const
|
||||||
{
|
{
|
||||||
std::vector<CInputCoin> coins(m_selected_inputs.begin(), m_selected_inputs.end());
|
std::vector<COutput> coins(m_selected_inputs.begin(), m_selected_inputs.end());
|
||||||
Shuffle(coins.begin(), coins.end(), FastRandomContext());
|
Shuffle(coins.begin(), coins.end(), FastRandomContext());
|
||||||
return coins;
|
return coins;
|
||||||
}
|
}
|
||||||
|
@ -426,4 +426,9 @@ bool SelectionResult::operator<(SelectionResult other) const
|
||||||
// As this operator is only used in std::min_element, we want the result that has more inputs when waste are equal.
|
// As this operator is only used in std::min_element, we want the result that has more inputs when waste are equal.
|
||||||
return *m_waste < *other.m_waste || (*m_waste == *other.m_waste && m_selected_inputs.size() > other.m_selected_inputs.size());
|
return *m_waste < *other.m_waste || (*m_waste == *other.m_waste && m_selected_inputs.size() > other.m_selected_inputs.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string COutput::ToString() const
|
||||||
|
{
|
||||||
|
return strprintf("COutput(%s, %d, %d) [%s]", outpoint.hash.ToString(), outpoint.n, depth, FormatMoney(txout.nValue));
|
||||||
|
}
|
||||||
} // namespace wallet
|
} // namespace wallet
|
||||||
|
|
|
@ -19,57 +19,71 @@ static constexpr CAmount MIN_CHANGE{COIN / 100};
|
||||||
static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2;
|
static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2;
|
||||||
|
|
||||||
/** A UTXO under consideration for use in funding a new transaction. */
|
/** A UTXO under consideration for use in funding a new transaction. */
|
||||||
class CInputCoin {
|
class COutput
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
CInputCoin(const CTransactionRef& tx, unsigned int i)
|
/** The outpoint identifying this UTXO */
|
||||||
{
|
|
||||||
if (!tx)
|
|
||||||
throw std::invalid_argument("tx should not be null");
|
|
||||||
if (i >= tx->vout.size())
|
|
||||||
throw std::out_of_range("The output index is out of range");
|
|
||||||
|
|
||||||
outpoint = COutPoint(tx->GetHash(), i);
|
|
||||||
txout = tx->vout[i];
|
|
||||||
effective_value = txout.nValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
CInputCoin(const CTransactionRef& tx, unsigned int i, int input_bytes) : CInputCoin(tx, i)
|
|
||||||
{
|
|
||||||
m_input_bytes = input_bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in)
|
|
||||||
{
|
|
||||||
outpoint = outpoint_in;
|
|
||||||
txout = txout_in;
|
|
||||||
effective_value = txout.nValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in)
|
|
||||||
{
|
|
||||||
m_input_bytes = input_bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
COutPoint outpoint;
|
COutPoint outpoint;
|
||||||
|
|
||||||
|
/** The output itself */
|
||||||
CTxOut txout;
|
CTxOut txout;
|
||||||
CAmount effective_value;
|
|
||||||
CAmount m_fee{0};
|
/**
|
||||||
CAmount m_long_term_fee{0};
|
* Depth in block chain.
|
||||||
|
* If > 0: the tx is on chain and has this many confirmations.
|
||||||
|
* If = 0: the tx is waiting confirmation.
|
||||||
|
* If < 0: a conflicting tx is on chain and has this many confirmations. */
|
||||||
|
int depth;
|
||||||
|
|
||||||
/** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
|
/** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
|
||||||
int m_input_bytes{-1};
|
int input_bytes;
|
||||||
|
|
||||||
bool operator<(const CInputCoin& rhs) const {
|
/** Whether we have the private keys to spend this output */
|
||||||
|
bool spendable;
|
||||||
|
|
||||||
|
/** Whether we know how to spend this output, ignoring the lack of keys */
|
||||||
|
bool solvable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this output is considered safe to spend. Unconfirmed transactions
|
||||||
|
* from outside keys and unconfirmed replacement transactions are considered
|
||||||
|
* unsafe and will not be used to fund new spending transactions.
|
||||||
|
*/
|
||||||
|
bool safe;
|
||||||
|
|
||||||
|
/** The time of the transaction containing this output as determined by CWalletTx::nTimeSmart */
|
||||||
|
int64_t time;
|
||||||
|
|
||||||
|
/** Whether the transaction containing this output is sent from the owning wallet */
|
||||||
|
bool from_me;
|
||||||
|
|
||||||
|
/** The output's value minus fees required to spend it. Initialized as the output's absolute value. */
|
||||||
|
CAmount effective_value;
|
||||||
|
|
||||||
|
/** The fee required to spend this output at the transaction's target feerate. */
|
||||||
|
CAmount fee{0};
|
||||||
|
|
||||||
|
/** The fee required to spend this output at the consolidation feerate. */
|
||||||
|
CAmount long_term_fee{0};
|
||||||
|
|
||||||
|
COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me)
|
||||||
|
: outpoint(outpoint),
|
||||||
|
txout(txout),
|
||||||
|
depth(depth),
|
||||||
|
input_bytes(input_bytes),
|
||||||
|
spendable(spendable),
|
||||||
|
solvable(solvable),
|
||||||
|
safe(safe),
|
||||||
|
time(time),
|
||||||
|
from_me(from_me),
|
||||||
|
effective_value(txout.nValue)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::string ToString() const;
|
||||||
|
|
||||||
|
bool operator<(const COutput& rhs) const {
|
||||||
return outpoint < rhs.outpoint;
|
return outpoint < rhs.outpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const CInputCoin& rhs) const {
|
|
||||||
return outpoint != rhs.outpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const CInputCoin& rhs) const {
|
|
||||||
return outpoint == rhs.outpoint;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Parameters for one iteration of Coin Selection. */
|
/** Parameters for one iteration of Coin Selection. */
|
||||||
|
@ -143,7 +157,7 @@ struct CoinEligibilityFilter
|
||||||
struct OutputGroup
|
struct OutputGroup
|
||||||
{
|
{
|
||||||
/** The list of UTXOs contained in this output group. */
|
/** The list of UTXOs contained in this output group. */
|
||||||
std::vector<CInputCoin> m_outputs;
|
std::vector<COutput> m_outputs;
|
||||||
/** Whether the UTXOs were sent by the wallet to itself. This is relevant because we may want at
|
/** Whether the UTXOs were sent by the wallet to itself. This is relevant because we may want at
|
||||||
* least a certain number of confirmations on UTXOs received from outside wallets while trusting
|
* least a certain number of confirmations on UTXOs received from outside wallets while trusting
|
||||||
* our own UTXOs more. */
|
* our own UTXOs more. */
|
||||||
|
@ -180,7 +194,7 @@ struct OutputGroup
|
||||||
m_subtract_fee_outputs(params.m_subtract_fee_outputs)
|
m_subtract_fee_outputs(params.m_subtract_fee_outputs)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only);
|
void Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only);
|
||||||
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
|
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
|
||||||
CAmount GetSelectionAmount() const;
|
CAmount GetSelectionAmount() const;
|
||||||
};
|
};
|
||||||
|
@ -202,13 +216,13 @@ struct OutputGroup
|
||||||
* @param[in] use_effective_value Whether to use the input's effective value (when true) or the real value (when false).
|
* @param[in] use_effective_value Whether to use the input's effective value (when true) or the real value (when false).
|
||||||
* @return The waste
|
* @return The waste
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
|
[[nodiscard]] CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
|
||||||
|
|
||||||
struct SelectionResult
|
struct SelectionResult
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
/** Set of inputs selected by the algorithm to use in the transaction */
|
/** Set of inputs selected by the algorithm to use in the transaction */
|
||||||
std::set<CInputCoin> m_selected_inputs;
|
std::set<COutput> m_selected_inputs;
|
||||||
/** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */
|
/** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */
|
||||||
const CAmount m_target;
|
const CAmount m_target;
|
||||||
/** Whether the input values for calculations should be the effective value (true) or normal value (false) */
|
/** Whether the input values for calculations should be the effective value (true) or normal value (false) */
|
||||||
|
@ -234,9 +248,9 @@ public:
|
||||||
[[nodiscard]] CAmount GetWaste() const;
|
[[nodiscard]] CAmount GetWaste() const;
|
||||||
|
|
||||||
/** Get m_selected_inputs */
|
/** Get m_selected_inputs */
|
||||||
const std::set<CInputCoin>& GetInputSet() const;
|
const std::set<COutput>& GetInputSet() const;
|
||||||
/** Get the vector of CInputCoins that will be used to fill in a CTransaction's vin */
|
/** Get the vector of COutputs that will be used to fill in a CTransaction's vin */
|
||||||
std::vector<CInputCoin> GetShuffledInputVector() const;
|
std::vector<COutput> GetShuffledInputVector() const;
|
||||||
|
|
||||||
bool operator<(SelectionResult other) const;
|
bool operator<(SelectionResult other) const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -111,6 +111,17 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
||||||
|
const COutput& output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
||||||
|
{
|
||||||
|
WalletTxOut result;
|
||||||
|
result.txout = output.txout;
|
||||||
|
result.time = output.time;
|
||||||
|
result.depth_in_main_chain = output.depth;
|
||||||
|
result.is_spent = wallet.IsSpent(output.outpoint.hash, output.outpoint.n);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
class WalletImpl : public Wallet
|
class WalletImpl : public Wallet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -419,8 +430,8 @@ public:
|
||||||
for (const auto& entry : ListCoins(*m_wallet)) {
|
for (const auto& entry : ListCoins(*m_wallet)) {
|
||||||
auto& group = result[entry.first];
|
auto& group = result[entry.first];
|
||||||
for (const auto& coin : entry.second) {
|
for (const auto& coin : entry.second) {
|
||||||
group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i),
|
group.emplace_back(coin.outpoint,
|
||||||
MakeWalletTxOut(*m_wallet, *coin.tx, coin.i, coin.nDepth));
|
MakeWalletTxOut(*m_wallet, coin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -648,16 +648,16 @@ RPCHelpMan listunspent()
|
||||||
|
|
||||||
for (const COutput& out : vecOutputs) {
|
for (const COutput& out : vecOutputs) {
|
||||||
CTxDestination address;
|
CTxDestination address;
|
||||||
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
|
const CScript& scriptPubKey = out.txout.scriptPubKey;
|
||||||
bool fValidAddress = ExtractDestination(scriptPubKey, address);
|
bool fValidAddress = ExtractDestination(scriptPubKey, address);
|
||||||
bool reused = avoid_reuse && pwallet->IsSpentKey(out.tx->GetHash(), out.i);
|
bool reused = avoid_reuse && pwallet->IsSpentKey(out.outpoint.hash, out.outpoint.n);
|
||||||
|
|
||||||
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
|
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
UniValue entry(UniValue::VOBJ);
|
UniValue entry(UniValue::VOBJ);
|
||||||
entry.pushKV("txid", out.tx->GetHash().GetHex());
|
entry.pushKV("txid", out.outpoint.hash.GetHex());
|
||||||
entry.pushKV("vout", out.i);
|
entry.pushKV("vout", (int)out.outpoint.n);
|
||||||
|
|
||||||
if (fValidAddress) {
|
if (fValidAddress) {
|
||||||
entry.pushKV("address", EncodeDestination(address));
|
entry.pushKV("address", EncodeDestination(address));
|
||||||
|
@ -702,21 +702,21 @@ RPCHelpMan listunspent()
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
|
entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
|
||||||
entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
|
entry.pushKV("amount", ValueFromAmount(out.txout.nValue));
|
||||||
entry.pushKV("confirmations", out.nDepth);
|
entry.pushKV("confirmations", out.depth);
|
||||||
if (!out.nDepth) {
|
if (!out.depth) {
|
||||||
size_t ancestor_count, descendant_count, ancestor_size;
|
size_t ancestor_count, descendant_count, ancestor_size;
|
||||||
CAmount ancestor_fees;
|
CAmount ancestor_fees;
|
||||||
pwallet->chain().getTransactionAncestry(out.tx->GetHash(), ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
|
pwallet->chain().getTransactionAncestry(out.outpoint.hash, ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
|
||||||
if (ancestor_count) {
|
if (ancestor_count) {
|
||||||
entry.pushKV("ancestorcount", uint64_t(ancestor_count));
|
entry.pushKV("ancestorcount", uint64_t(ancestor_count));
|
||||||
entry.pushKV("ancestorsize", uint64_t(ancestor_size));
|
entry.pushKV("ancestorsize", uint64_t(ancestor_size));
|
||||||
entry.pushKV("ancestorfees", uint64_t(ancestor_fees));
|
entry.pushKV("ancestorfees", uint64_t(ancestor_fees));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entry.pushKV("spendable", out.fSpendable);
|
entry.pushKV("spendable", out.spendable);
|
||||||
entry.pushKV("solvable", out.fSolvable);
|
entry.pushKV("solvable", out.solvable);
|
||||||
if (out.fSolvable) {
|
if (out.solvable) {
|
||||||
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
|
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
|
||||||
if (provider) {
|
if (provider) {
|
||||||
auto descriptor = InferDescriptor(scriptPubKey, *provider);
|
auto descriptor = InferDescriptor(scriptPubKey, *provider);
|
||||||
|
@ -724,7 +724,7 @@ RPCHelpMan listunspent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (avoid_reuse) entry.pushKV("reused", reused);
|
if (avoid_reuse) entry.pushKV("reused", reused);
|
||||||
entry.pushKV("safe", out.fSafe);
|
entry.pushKV("safe", out.safe);
|
||||||
results.push_back(entry);
|
results.push_back(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,6 @@ int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out
|
||||||
return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig);
|
return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string COutput::ToString() const
|
|
||||||
{
|
|
||||||
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig)
|
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig)
|
||||||
{
|
{
|
||||||
CMutableTransaction txn;
|
CMutableTransaction txn;
|
||||||
|
@ -158,6 +153,8 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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++) {
|
||||||
// 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(COutPoint(entry.first, i))) {
|
||||||
|
@ -190,8 +187,9 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
|
||||||
|
|
||||||
bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
|
bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].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.push_back(COutput(wallet, wtx, i, nDepth, spendable, solvable, safeTx, (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);
|
||||||
|
|
||||||
// Checks the sum amount of all UTXO's.
|
// Checks the sum amount of all UTXO's.
|
||||||
if (nMinimumSumAmount != MAX_MONEY) {
|
if (nMinimumSumAmount != MAX_MONEY) {
|
||||||
|
@ -218,8 +216,8 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr
|
||||||
std::vector<COutput> vCoins;
|
std::vector<COutput> vCoins;
|
||||||
AvailableCoins(wallet, vCoins, coinControl);
|
AvailableCoins(wallet, vCoins, coinControl);
|
||||||
for (const COutput& out : vCoins) {
|
for (const COutput& out : vCoins) {
|
||||||
if (out.fSpendable) {
|
if (out.spendable) {
|
||||||
balance += out.tx->tx->vout[out.i].nValue;
|
balance += out.txout.nValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return balance;
|
return balance;
|
||||||
|
@ -243,6 +241,12 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransactio
|
||||||
return ptx->vout[n];
|
return ptx->vout[n];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint)
|
||||||
|
{
|
||||||
|
AssertLockHeld(wallet.cs_wallet);
|
||||||
|
return FindNonChangeParentOutput(wallet, *wallet.GetWalletTx(outpoint.hash)->tx, outpoint.n);
|
||||||
|
}
|
||||||
|
|
||||||
std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
|
std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
|
||||||
{
|
{
|
||||||
AssertLockHeld(wallet.cs_wallet);
|
AssertLockHeld(wallet.cs_wallet);
|
||||||
|
@ -254,8 +258,8 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
|
||||||
|
|
||||||
for (const COutput& coin : availableCoins) {
|
for (const COutput& coin : availableCoins) {
|
||||||
CTxDestination address;
|
CTxDestination address;
|
||||||
if ((coin.fSpendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
|
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
|
||||||
ExtractDestination(FindNonChangeParentOutput(wallet, *coin.tx->tx, coin.i).scriptPubKey, address)) {
|
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
|
||||||
result[address].emplace_back(std::move(coin));
|
result[address].emplace_back(std::move(coin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,14 +272,15 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
|
||||||
for (const COutPoint& output : lockedCoins) {
|
for (const COutPoint& output : lockedCoins) {
|
||||||
auto it = wallet.mapWallet.find(output.hash);
|
auto it = wallet.mapWallet.find(output.hash);
|
||||||
if (it != wallet.mapWallet.end()) {
|
if (it != wallet.mapWallet.end()) {
|
||||||
int depth = wallet.GetTxDepthInMainChain(it->second);
|
const auto& wtx = it->second;
|
||||||
if (depth >= 0 && output.n < it->second.tx->vout.size() &&
|
int depth = wallet.GetTxDepthInMainChain(wtx);
|
||||||
wallet.IsMine(it->second.tx->vout[output.n]) == is_mine_filter
|
if (depth >= 0 && output.n < wtx.tx->vout.size() &&
|
||||||
|
wallet.IsMine(wtx.tx->vout[output.n]) == is_mine_filter
|
||||||
) {
|
) {
|
||||||
CTxDestination address;
|
CTxDestination address;
|
||||||
if (ExtractDestination(FindNonChangeParentOutput(wallet, *it->second.tx, output.n).scriptPubKey, address)) {
|
if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) {
|
||||||
result[address].emplace_back(
|
result[address].emplace_back(
|
||||||
wallet, it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */);
|
COutPoint(wtx.GetHash(), output.n), wtx.tx->vout.at(output.n), depth, GetTxSpendSize(wallet, wtx, output.n), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,15 +297,14 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
|
||||||
// Allowing partial spends means no grouping. Each COutput gets its own OutputGroup.
|
// Allowing partial spends means no grouping. Each COutput gets its own OutputGroup.
|
||||||
for (const COutput& output : outputs) {
|
for (const COutput& output : outputs) {
|
||||||
// Skip outputs we cannot spend
|
// Skip outputs we cannot spend
|
||||||
if (!output.fSpendable) continue;
|
if (!output.spendable) continue;
|
||||||
|
|
||||||
size_t ancestors, descendants;
|
size_t ancestors, descendants;
|
||||||
wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
|
wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
|
||||||
CInputCoin input_coin = output.GetInputCoin();
|
|
||||||
|
|
||||||
// Make an OutputGroup containing just this output
|
// Make an OutputGroup containing just this output
|
||||||
OutputGroup group{coin_sel_params};
|
OutputGroup group{coin_sel_params};
|
||||||
group.Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only);
|
group.Insert(output, ancestors, descendants, positive_only);
|
||||||
|
|
||||||
// Check the OutputGroup's eligibility. Only add the eligible ones.
|
// Check the OutputGroup's eligibility. Only add the eligible ones.
|
||||||
if (positive_only && group.GetSelectionAmount() <= 0) continue;
|
if (positive_only && group.GetSelectionAmount() <= 0) continue;
|
||||||
|
@ -312,18 +316,17 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
|
||||||
// We want to combine COutputs that have the same scriptPubKey into single OutputGroups
|
// We want to combine COutputs that have the same scriptPubKey into single OutputGroups
|
||||||
// except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
|
// except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
|
||||||
// To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
|
// To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
|
||||||
// For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added
|
// For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput is added
|
||||||
// to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
|
// to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
|
||||||
// OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector.
|
// OUTPUT_GROUP_MAX_ENTRIES COutputs, a new OutputGroup is added to the end of the vector.
|
||||||
std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
|
std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
|
||||||
for (const auto& output : outputs) {
|
for (const auto& output : outputs) {
|
||||||
// Skip outputs we cannot spend
|
// Skip outputs we cannot spend
|
||||||
if (!output.fSpendable) continue;
|
if (!output.spendable) continue;
|
||||||
|
|
||||||
size_t ancestors, descendants;
|
size_t ancestors, descendants;
|
||||||
wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
|
wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
|
||||||
CInputCoin input_coin = output.GetInputCoin();
|
CScript spk = output.txout.scriptPubKey;
|
||||||
CScript spk = input_coin.txout.scriptPubKey;
|
|
||||||
|
|
||||||
std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
|
std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
|
||||||
|
|
||||||
|
@ -332,7 +335,7 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
|
||||||
groups.emplace_back(coin_sel_params);
|
groups.emplace_back(coin_sel_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the last OutputGroup in the vector so that we can add the CInputCoin to it
|
// Get the last OutputGroup in the vector so that we can add the COutput to it
|
||||||
// A pointer is used here so that group can be reassigned later if it is full.
|
// A pointer is used here so that group can be reassigned later if it is full.
|
||||||
OutputGroup* group = &groups.back();
|
OutputGroup* group = &groups.back();
|
||||||
|
|
||||||
|
@ -344,8 +347,8 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
|
||||||
group = &groups.back();
|
group = &groups.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the input_coin to group
|
// Add the output to group
|
||||||
group->Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only);
|
group->Insert(output, ancestors, descendants, positive_only);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we go through the entire map and pull out the OutputGroups
|
// Now we go through the entire map and pull out the OutputGroups
|
||||||
|
@ -421,11 +424,11 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
|
||||||
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
|
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
|
||||||
{
|
{
|
||||||
for (const COutput& out : vCoins) {
|
for (const COutput& out : vCoins) {
|
||||||
if (!out.fSpendable) continue;
|
if (!out.spendable) continue;
|
||||||
/* Set depth, from_me, ancestors, and descendants to 0 or false as these don't matter for preset inputs as no actual selection is being done.
|
/* Set ancestors and descendants to 0 as these don't matter for preset inputs as no actual selection is being done.
|
||||||
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
|
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
|
||||||
*/
|
*/
|
||||||
preset_inputs.Insert(out.GetInputCoin(), 0, false, 0, 0, false);
|
preset_inputs.Insert(out, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
|
||||||
}
|
}
|
||||||
SelectionResult result(nTargetValue);
|
SelectionResult result(nTargetValue);
|
||||||
result.AddInput(preset_inputs);
|
result.AddInput(preset_inputs);
|
||||||
|
@ -434,7 +437,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate value from preset inputs and store them
|
// calculate value from preset inputs and store them
|
||||||
std::set<CInputCoin> setPresetCoins;
|
std::set<COutPoint> preset_coins;
|
||||||
|
|
||||||
std::vector<COutPoint> vPresetInputs;
|
std::vector<COutPoint> vPresetInputs;
|
||||||
coin_control.ListSelected(vPresetInputs);
|
coin_control.ListSelected(vPresetInputs);
|
||||||
|
@ -462,27 +465,29 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
|
||||||
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
|
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
CInputCoin coin(outpoint, txout, input_bytes);
|
if (input_bytes == -1) {
|
||||||
if (coin.m_input_bytes == -1) {
|
|
||||||
return std::nullopt; // Not solvable, can't estimate size for fee
|
return std::nullopt; // Not solvable, can't estimate size for fee
|
||||||
}
|
}
|
||||||
coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
|
|
||||||
|
/* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
|
||||||
|
COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
|
||||||
|
output.effective_value = output.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(output.input_bytes);
|
||||||
if (coin_selection_params.m_subtract_fee_outputs) {
|
if (coin_selection_params.m_subtract_fee_outputs) {
|
||||||
value_to_select -= coin.txout.nValue;
|
value_to_select -= output.txout.nValue;
|
||||||
} else {
|
} else {
|
||||||
value_to_select -= coin.effective_value;
|
value_to_select -= output.effective_value;
|
||||||
}
|
}
|
||||||
setPresetCoins.insert(coin);
|
preset_coins.insert(outpoint);
|
||||||
/* Set depth, from_me, ancestors, and descendants to 0 or false as don't matter for preset inputs as no actual selection is being done.
|
/* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done.
|
||||||
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
|
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
|
||||||
*/
|
*/
|
||||||
preset_inputs.Insert(coin, 0, false, 0, 0, false);
|
preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove preset inputs from vCoins so that Coin Selection doesn't pick them.
|
// remove preset inputs from vCoins so that Coin Selection doesn't pick them.
|
||||||
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
|
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
|
||||||
{
|
{
|
||||||
if (setPresetCoins.count(it->GetInputCoin()))
|
if (preset_coins.count(it->outpoint))
|
||||||
it = vCoins.erase(it);
|
it = vCoins.erase(it);
|
||||||
else
|
else
|
||||||
++it;
|
++it;
|
||||||
|
@ -798,7 +803,7 @@ static bool CreateTransactionInternal(
|
||||||
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
|
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
|
||||||
|
|
||||||
// Shuffle selected coins and fill in final vin
|
// Shuffle selected coins and fill in final vin
|
||||||
std::vector<CInputCoin> selected_coins = result->GetShuffledInputVector();
|
std::vector<COutput> selected_coins = result->GetShuffledInputVector();
|
||||||
|
|
||||||
// The sequence number is set to non-maxint so that DiscourageFeeSniping
|
// The sequence number is set to non-maxint so that DiscourageFeeSniping
|
||||||
// works.
|
// works.
|
||||||
|
|
|
@ -11,61 +11,11 @@
|
||||||
#include <wallet/wallet.h>
|
#include <wallet/wallet.h>
|
||||||
|
|
||||||
namespace wallet {
|
namespace wallet {
|
||||||
/** Get the marginal bytes if spending the specified output from this transaction */
|
/** Get the marginal bytes if spending the specified output from this transaction.
|
||||||
|
* use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the
|
||||||
|
* size of the input spend. This should only be set when watch-only outputs are allowed */
|
||||||
int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false);
|
int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false);
|
||||||
|
|
||||||
class COutput
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
const CWalletTx *tx;
|
|
||||||
|
|
||||||
/** Index in tx->vout. */
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Depth in block chain.
|
|
||||||
* If > 0: the tx is on chain and has this many confirmations.
|
|
||||||
* If = 0: the tx is waiting confirmation.
|
|
||||||
* If < 0: a conflicting tx is on chain and has this many confirmations. */
|
|
||||||
int nDepth;
|
|
||||||
|
|
||||||
/** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
|
|
||||||
int nInputBytes;
|
|
||||||
|
|
||||||
/** Whether we have the private keys to spend this output */
|
|
||||||
bool fSpendable;
|
|
||||||
|
|
||||||
/** Whether we know how to spend this output, ignoring the lack of keys */
|
|
||||||
bool fSolvable;
|
|
||||||
|
|
||||||
/** Whether to use the maximum sized, 72 byte signature when calculating the size of the input spend. This should only be set when watch-only outputs are allowed */
|
|
||||||
bool use_max_sig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this output is considered safe to spend. Unconfirmed transactions
|
|
||||||
* from outside keys and unconfirmed replacement transactions are considered
|
|
||||||
* unsafe and will not be used to fund new spending transactions.
|
|
||||||
*/
|
|
||||||
bool fSafe;
|
|
||||||
|
|
||||||
COutput(const CWallet& wallet, const CWalletTx& wtx, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false)
|
|
||||||
{
|
|
||||||
tx = &wtx; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in;
|
|
||||||
// If known and signable by the given wallet, compute nInputBytes
|
|
||||||
// Failure will keep this value -1
|
|
||||||
if (fSpendable) {
|
|
||||||
nInputBytes = GetTxSpendSize(wallet, wtx, i, use_max_sig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ToString() const;
|
|
||||||
|
|
||||||
inline CInputCoin GetInputCoin() const
|
|
||||||
{
|
|
||||||
return CInputCoin(tx->tx, i, nInputBytes);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Get the marginal bytes of spending the specified output
|
//Get the marginal bytes of spending the specified output
|
||||||
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
|
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
|
||||||
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false);
|
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false);
|
||||||
|
@ -93,6 +43,7 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr
|
||||||
* Find non-change parent output.
|
* Find non-change parent output.
|
||||||
*/
|
*/
|
||||||
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
|
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
|
||||||
|
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of available coins and locked coins grouped by non-change output address.
|
* Return list of available coins and locked coins grouped by non-change output address.
|
||||||
|
|
|
@ -28,20 +28,20 @@ BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
|
||||||
// we repeat those tests this many times and only complain if all iterations of the test fail
|
// we repeat those tests this many times and only complain if all iterations of the test fail
|
||||||
#define RANDOM_REPEATS 5
|
#define RANDOM_REPEATS 5
|
||||||
|
|
||||||
typedef std::set<CInputCoin> CoinSet;
|
typedef std::set<COutput> CoinSet;
|
||||||
|
|
||||||
static const CoinEligibilityFilter filter_standard(1, 6, 0);
|
static const CoinEligibilityFilter filter_standard(1, 6, 0);
|
||||||
static const CoinEligibilityFilter filter_confirmed(1, 1, 0);
|
static const CoinEligibilityFilter filter_confirmed(1, 1, 0);
|
||||||
static const CoinEligibilityFilter filter_standard_extra(6, 6, 0);
|
static const CoinEligibilityFilter filter_standard_extra(6, 6, 0);
|
||||||
static int nextLockTime = 0;
|
static int nextLockTime = 0;
|
||||||
|
|
||||||
static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
|
static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& set)
|
||||||
{
|
{
|
||||||
CMutableTransaction tx;
|
CMutableTransaction tx;
|
||||||
tx.vout.resize(nInput + 1);
|
tx.vout.resize(nInput + 1);
|
||||||
tx.vout[nInput].nValue = nValue;
|
tx.vout[nInput].nValue = nValue;
|
||||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||||
set.emplace_back(MakeTransactionRef(tx), nInput);
|
set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
|
static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
|
||||||
|
@ -50,9 +50,9 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
|
||||||
tx.vout.resize(nInput + 1);
|
tx.vout.resize(nInput + 1);
|
||||||
tx.vout[nInput].nValue = nValue;
|
tx.vout[nInput].nValue = nValue;
|
||||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||||
CInputCoin coin(MakeTransactionRef(tx), nInput);
|
COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
|
||||||
OutputGroup group;
|
OutputGroup group;
|
||||||
group.Insert(coin, 1, false, 0, 0, true);
|
group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true);
|
||||||
result.AddInput(group);
|
result.AddInput(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,10 +62,10 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe
|
||||||
tx.vout.resize(nInput + 1);
|
tx.vout.resize(nInput + 1);
|
||||||
tx.vout[nInput].nValue = nValue;
|
tx.vout[nInput].nValue = nValue;
|
||||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||||
CInputCoin coin(MakeTransactionRef(tx), nInput);
|
COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
|
||||||
coin.effective_value = nValue - fee;
|
coin.effective_value = nValue - fee;
|
||||||
coin.m_fee = fee;
|
coin.fee = fee;
|
||||||
coin.m_long_term_fee = long_term_fee;
|
coin.long_term_fee = long_term_fee;
|
||||||
set.insert(coin);
|
set.insert(coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,24 +82,13 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
|
||||||
assert(destination_ok);
|
assert(destination_ok);
|
||||||
tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
|
tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
|
||||||
}
|
}
|
||||||
if (fIsFromMe) {
|
|
||||||
// IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
|
|
||||||
// so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
|
|
||||||
tx.vin.resize(1);
|
|
||||||
}
|
|
||||||
uint256 txid = tx.GetHash();
|
uint256 txid = tx.GetHash();
|
||||||
|
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
|
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
|
||||||
assert(ret.second);
|
assert(ret.second);
|
||||||
CWalletTx& wtx = (*ret.first).second;
|
CWalletTx& wtx = (*ret.first).second;
|
||||||
if (fIsFromMe)
|
coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe);
|
||||||
{
|
|
||||||
wtx.m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1);
|
|
||||||
wtx.m_is_cache_empty = false;
|
|
||||||
}
|
|
||||||
COutput output(wallet, wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
|
|
||||||
coins.push_back(output);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if SelectionResult a is equivalent to SelectionResult b.
|
/** Check if SelectionResult a is equivalent to SelectionResult b.
|
||||||
|
@ -124,11 +113,14 @@ static bool EquivalentResult(const SelectionResult& a, const SelectionResult& b)
|
||||||
/** Check if this selection is equal to another one. Equal means same inputs (i.e same value and prevout) */
|
/** Check if this selection is equal to another one. Equal means same inputs (i.e same value and prevout) */
|
||||||
static bool EqualResult(const SelectionResult& a, const SelectionResult& b)
|
static bool EqualResult(const SelectionResult& a, const SelectionResult& b)
|
||||||
{
|
{
|
||||||
std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin());
|
std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin(),
|
||||||
|
[](const COutput& a, const COutput& b) {
|
||||||
|
return a.outpoint == b.outpoint;
|
||||||
|
});
|
||||||
return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end();
|
return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end();
|
||||||
}
|
}
|
||||||
|
|
||||||
static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
|
static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
|
||||||
{
|
{
|
||||||
utxo_pool.clear();
|
utxo_pool.clear();
|
||||||
CAmount target = 0;
|
CAmount target = 0;
|
||||||
|
@ -140,24 +132,13 @@ static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins)
|
|
||||||
{
|
|
||||||
static std::vector<OutputGroup> static_groups;
|
|
||||||
static_groups.clear();
|
|
||||||
for (auto& coin : coins) {
|
|
||||||
static_groups.emplace_back();
|
|
||||||
static_groups.back().Insert(coin, 0, true, 0, 0, false);
|
|
||||||
}
|
|
||||||
return static_groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
|
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
|
||||||
{
|
{
|
||||||
static std::vector<OutputGroup> static_groups;
|
static std::vector<OutputGroup> static_groups;
|
||||||
static_groups.clear();
|
static_groups.clear();
|
||||||
for (auto& coin : coins) {
|
for (auto& coin : coins) {
|
||||||
static_groups.emplace_back();
|
static_groups.emplace_back();
|
||||||
static_groups.back().Insert(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0, false);
|
static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
|
||||||
}
|
}
|
||||||
return static_groups;
|
return static_groups;
|
||||||
}
|
}
|
||||||
|
@ -185,7 +166,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||||
{
|
{
|
||||||
FastRandomContext rand{};
|
FastRandomContext rand{};
|
||||||
// Setup
|
// Setup
|
||||||
std::vector<CInputCoin> utxo_pool;
|
std::vector<COutput> utxo_pool;
|
||||||
SelectionResult expected_result(CAmount(0));
|
SelectionResult expected_result(CAmount(0));
|
||||||
|
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
|
@ -329,13 +310,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||||
std::vector<COutput> coins;
|
std::vector<COutput> coins;
|
||||||
|
|
||||||
add_coin(coins, *wallet, 1);
|
add_coin(coins, *wallet, 1);
|
||||||
coins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
|
coins.at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
|
||||||
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
|
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
|
||||||
|
|
||||||
// Test fees subtracted from output:
|
// Test fees subtracted from output:
|
||||||
coins.clear();
|
coins.clear();
|
||||||
add_coin(coins, *wallet, 1 * CENT);
|
add_coin(coins, *wallet, 1 * CENT);
|
||||||
coins.at(0).nInputBytes = 40;
|
coins.at(0).input_bytes = 40;
|
||||||
coin_selection_params_bnb.m_subtract_fee_outputs = true;
|
coin_selection_params_bnb.m_subtract_fee_outputs = true;
|
||||||
const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
|
const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
|
||||||
BOOST_CHECK(result9);
|
BOOST_CHECK(result9);
|
||||||
|
@ -356,7 +337,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||||
add_coin(coins, *wallet, 2 * CENT, 6 * 24, false, 0, true);
|
add_coin(coins, *wallet, 2 * CENT, 6 * 24, false, 0, true);
|
||||||
CCoinControl coin_control;
|
CCoinControl coin_control;
|
||||||
coin_control.fAllowOtherInputs = true;
|
coin_control.fAllowOtherInputs = true;
|
||||||
coin_control.Select(COutPoint(coins.at(0).tx->GetHash(), coins.at(0).i));
|
coin_control.Select(coins.at(0).outpoint);
|
||||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
|
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
|
||||||
const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||||
BOOST_CHECK(result10);
|
BOOST_CHECK(result10);
|
||||||
|
|
|
@ -588,7 +588,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
|
||||||
for (const auto& group : list) {
|
for (const auto& group : list) {
|
||||||
for (const auto& coin : group.second) {
|
for (const auto& coin : group.second) {
|
||||||
LOCK(wallet->cs_wallet);
|
LOCK(wallet->cs_wallet);
|
||||||
wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
|
wallet->LockCoin(coin.outpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Reference in a new issue