mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-11 01:26:10 +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 wallet::AttemptSelection;
|
||||
using wallet::CInputCoin;
|
||||
using wallet::COutput;
|
||||
using wallet::CWallet;
|
||||
using wallet::CWalletTx;
|
||||
|
@ -58,7 +57,7 @@ static void CoinSelection(benchmark::Bench& bench)
|
|||
// Create coins
|
||||
std::vector<COutput> coins;
|
||||
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);
|
||||
|
@ -81,17 +80,15 @@ static void CoinSelection(benchmark::Bench& bench)
|
|||
});
|
||||
}
|
||||
|
||||
typedef std::set<CInputCoin> CoinSet;
|
||||
|
||||
// Copied from src/wallet/test/coinselector_tests.cpp
|
||||
static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>& set)
|
||||
{
|
||||
CMutableTransaction tx;
|
||||
tx.vout.resize(nInput + 1);
|
||||
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.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
|
||||
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:
|
||||
* 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.
|
||||
* These UTXOs will be sorted in descending order by effective value and the CInputCoins'
|
||||
* @param const std::vector<OutputGroup>& utxo_pool The set of UTXO groups that we are choosing from.
|
||||
* These UTXO groups will be sorted in descending order by effective value and the OutputGroups'
|
||||
* values are their effective values.
|
||||
* @param const CAmount& selection_target This is the value that we want to select. It is the lower
|
||||
* 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
|
||||
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;
|
||||
|
||||
// Filter for positive only here before adding the coin
|
||||
if (positive_only && ev <= 0) return;
|
||||
|
||||
m_outputs.push_back(output);
|
||||
CInputCoin& coin = m_outputs.back();
|
||||
COutput& coin = m_outputs.back();
|
||||
|
||||
coin.m_fee = coin_fee;
|
||||
fee += coin.m_fee;
|
||||
coin.fee = coin_fee;
|
||||
fee += coin.fee;
|
||||
|
||||
coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes);
|
||||
long_term_fee += coin.m_long_term_fee;
|
||||
coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
|
||||
long_term_fee += coin.long_term_fee;
|
||||
|
||||
coin.effective_value = ev;
|
||||
effective_value += coin.effective_value;
|
||||
|
||||
m_from_me &= from_me;
|
||||
m_value += output.txout.nValue;
|
||||
m_depth = std::min(m_depth, depth);
|
||||
m_from_me &= coin.from_me;
|
||||
m_value += coin.txout.nValue;
|
||||
m_depth = std::min(m_depth, coin.depth);
|
||||
// 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
|
||||
// have common ancestors
|
||||
|
@ -353,7 +353,7 @@ CAmount OutputGroup::GetSelectionAmount() const
|
|||
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
|
||||
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.
|
||||
CAmount waste = 0;
|
||||
CAmount selected_effective_value = 0;
|
||||
for (const CInputCoin& coin : inputs) {
|
||||
waste += coin.m_fee - coin.m_long_term_fee;
|
||||
for (const COutput& coin : inputs) {
|
||||
waste += coin.fee - coin.long_term_fee;
|
||||
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;
|
||||
}
|
||||
|
||||
const std::set<CInputCoin>& SelectionResult::GetInputSet() const
|
||||
const std::set<COutput>& SelectionResult::GetInputSet() const
|
||||
{
|
||||
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());
|
||||
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.
|
||||
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
|
||||
|
|
|
@ -19,57 +19,71 @@ static constexpr CAmount MIN_CHANGE{COIN / 100};
|
|||
static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2;
|
||||
|
||||
/** A UTXO under consideration for use in funding a new transaction. */
|
||||
class CInputCoin {
|
||||
class COutput
|
||||
{
|
||||
public:
|
||||
CInputCoin(const CTransactionRef& tx, unsigned int i)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/** The outpoint identifying this UTXO */
|
||||
COutPoint outpoint;
|
||||
|
||||
/** The output itself */
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
|
||||
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. */
|
||||
|
@ -143,7 +157,7 @@ struct CoinEligibilityFilter
|
|||
struct OutputGroup
|
||||
{
|
||||
/** 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
|
||||
* least a certain number of confirmations on UTXOs received from outside wallets while trusting
|
||||
* our own UTXOs more. */
|
||||
|
@ -180,7 +194,7 @@ struct OutputGroup
|
|||
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;
|
||||
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).
|
||||
* @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
|
||||
{
|
||||
private:
|
||||
/** 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 */
|
||||
const CAmount m_target;
|
||||
/** 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;
|
||||
|
||||
/** Get m_selected_inputs */
|
||||
const std::set<CInputCoin>& GetInputSet() const;
|
||||
/** Get the vector of CInputCoins that will be used to fill in a CTransaction's vin */
|
||||
std::vector<CInputCoin> GetShuffledInputVector() const;
|
||||
const std::set<COutput>& GetInputSet() const;
|
||||
/** Get the vector of COutputs that will be used to fill in a CTransaction's vin */
|
||||
std::vector<COutput> GetShuffledInputVector() const;
|
||||
|
||||
bool operator<(SelectionResult other) const;
|
||||
};
|
||||
|
|
|
@ -111,6 +111,17 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
@ -419,8 +430,8 @@ public:
|
|||
for (const auto& entry : ListCoins(*m_wallet)) {
|
||||
auto& group = result[entry.first];
|
||||
for (const auto& coin : entry.second) {
|
||||
group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i),
|
||||
MakeWalletTxOut(*m_wallet, *coin.tx, coin.i, coin.nDepth));
|
||||
group.emplace_back(coin.outpoint,
|
||||
MakeWalletTxOut(*m_wallet, coin));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -648,16 +648,16 @@ RPCHelpMan listunspent()
|
|||
|
||||
for (const COutput& out : vecOutputs) {
|
||||
CTxDestination address;
|
||||
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
|
||||
const CScript& scriptPubKey = out.txout.scriptPubKey;
|
||||
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)))
|
||||
continue;
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.pushKV("txid", out.tx->GetHash().GetHex());
|
||||
entry.pushKV("vout", out.i);
|
||||
entry.pushKV("txid", out.outpoint.hash.GetHex());
|
||||
entry.pushKV("vout", (int)out.outpoint.n);
|
||||
|
||||
if (fValidAddress) {
|
||||
entry.pushKV("address", EncodeDestination(address));
|
||||
|
@ -702,21 +702,21 @@ RPCHelpMan listunspent()
|
|||
}
|
||||
|
||||
entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
|
||||
entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
|
||||
entry.pushKV("confirmations", out.nDepth);
|
||||
if (!out.nDepth) {
|
||||
entry.pushKV("amount", ValueFromAmount(out.txout.nValue));
|
||||
entry.pushKV("confirmations", out.depth);
|
||||
if (!out.depth) {
|
||||
size_t ancestor_count, descendant_count, ancestor_size;
|
||||
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) {
|
||||
entry.pushKV("ancestorcount", uint64_t(ancestor_count));
|
||||
entry.pushKV("ancestorsize", uint64_t(ancestor_size));
|
||||
entry.pushKV("ancestorfees", uint64_t(ancestor_fees));
|
||||
}
|
||||
}
|
||||
entry.pushKV("spendable", out.fSpendable);
|
||||
entry.pushKV("solvable", out.fSolvable);
|
||||
if (out.fSolvable) {
|
||||
entry.pushKV("spendable", out.spendable);
|
||||
entry.pushKV("solvable", out.solvable);
|
||||
if (out.solvable) {
|
||||
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
|
||||
if (provider) {
|
||||
auto descriptor = InferDescriptor(scriptPubKey, *provider);
|
||||
|
@ -724,7 +724,7 @@ RPCHelpMan listunspent()
|
|||
}
|
||||
}
|
||||
if (avoid_reuse) entry.pushKV("reused", reused);
|
||||
entry.pushKV("safe", out.fSafe);
|
||||
entry.pushKV("safe", out.safe);
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
CMutableTransaction txn;
|
||||
|
@ -158,6 +153,8 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
|
|||
continue;
|
||||
}
|
||||
|
||||
bool tx_from_me = CachedTxIsFromMe(wallet, wtx, ISMINE_ALL);
|
||||
|
||||
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
|
||||
// Only consider selected coins if add_inputs is false
|
||||
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 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.
|
||||
if (nMinimumSumAmount != MAX_MONEY) {
|
||||
|
@ -218,8 +216,8 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr
|
|||
std::vector<COutput> vCoins;
|
||||
AvailableCoins(wallet, vCoins, coinControl);
|
||||
for (const COutput& out : vCoins) {
|
||||
if (out.fSpendable) {
|
||||
balance += out.tx->tx->vout[out.i].nValue;
|
||||
if (out.spendable) {
|
||||
balance += out.txout.nValue;
|
||||
}
|
||||
}
|
||||
return balance;
|
||||
|
@ -243,6 +241,12 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransactio
|
|||
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)
|
||||
{
|
||||
AssertLockHeld(wallet.cs_wallet);
|
||||
|
@ -254,8 +258,8 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
|
|||
|
||||
for (const COutput& coin : availableCoins) {
|
||||
CTxDestination address;
|
||||
if ((coin.fSpendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
|
||||
ExtractDestination(FindNonChangeParentOutput(wallet, *coin.tx->tx, coin.i).scriptPubKey, address)) {
|
||||
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
|
||||
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
|
||||
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) {
|
||||
auto it = wallet.mapWallet.find(output.hash);
|
||||
if (it != wallet.mapWallet.end()) {
|
||||
int depth = wallet.GetTxDepthInMainChain(it->second);
|
||||
if (depth >= 0 && output.n < it->second.tx->vout.size() &&
|
||||
wallet.IsMine(it->second.tx->vout[output.n]) == is_mine_filter
|
||||
const auto& wtx = it->second;
|
||||
int depth = wallet.GetTxDepthInMainChain(wtx);
|
||||
if (depth >= 0 && output.n < wtx.tx->vout.size() &&
|
||||
wallet.IsMine(wtx.tx->vout[output.n]) == is_mine_filter
|
||||
) {
|
||||
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(
|
||||
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.
|
||||
for (const COutput& output : outputs) {
|
||||
// Skip outputs we cannot spend
|
||||
if (!output.fSpendable) continue;
|
||||
if (!output.spendable) continue;
|
||||
|
||||
size_t ancestors, descendants;
|
||||
wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
|
||||
CInputCoin input_coin = output.GetInputCoin();
|
||||
wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
|
||||
|
||||
// Make an OutputGroup containing just this output
|
||||
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.
|
||||
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
|
||||
// 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.
|
||||
// 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
|
||||
// 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;
|
||||
for (const auto& output : outputs) {
|
||||
// Skip outputs we cannot spend
|
||||
if (!output.fSpendable) continue;
|
||||
if (!output.spendable) continue;
|
||||
|
||||
size_t ancestors, descendants;
|
||||
wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
|
||||
CInputCoin input_coin = output.GetInputCoin();
|
||||
CScript spk = input_coin.txout.scriptPubKey;
|
||||
wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
|
||||
CScript spk = output.txout.scriptPubKey;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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.
|
||||
OutputGroup* group = &groups.back();
|
||||
|
||||
|
@ -344,8 +347,8 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
|
|||
group = &groups.back();
|
||||
}
|
||||
|
||||
// Add the input_coin to group
|
||||
group->Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only);
|
||||
// Add the output to group
|
||||
group->Insert(output, ancestors, descendants, positive_only);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
for (const COutput& out : vCoins) {
|
||||
if (!out.fSpendable) 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.
|
||||
if (!out.spendable) continue;
|
||||
/* 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.
|
||||
*/
|
||||
preset_inputs.Insert(out.GetInputCoin(), 0, false, 0, 0, false);
|
||||
preset_inputs.Insert(out, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
|
||||
}
|
||||
SelectionResult result(nTargetValue);
|
||||
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
|
||||
std::set<CInputCoin> setPresetCoins;
|
||||
std::set<COutPoint> preset_coins;
|
||||
|
||||
std::vector<COutPoint> 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);
|
||||
}
|
||||
|
||||
CInputCoin coin(outpoint, txout, input_bytes);
|
||||
if (coin.m_input_bytes == -1) {
|
||||
if (input_bytes == -1) {
|
||||
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) {
|
||||
value_to_select -= coin.txout.nValue;
|
||||
value_to_select -= output.txout.nValue;
|
||||
} else {
|
||||
value_to_select -= coin.effective_value;
|
||||
value_to_select -= output.effective_value;
|
||||
}
|
||||
setPresetCoins.insert(coin);
|
||||
/* 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.
|
||||
preset_coins.insert(outpoint);
|
||||
/* 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.
|
||||
*/
|
||||
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.
|
||||
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);
|
||||
else
|
||||
++it;
|
||||
|
@ -798,7 +803,7 @@ static bool CreateTransactionInternal(
|
|||
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
|
||||
|
||||
// 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
|
||||
// works.
|
||||
|
|
|
@ -11,61 +11,11 @@
|
|||
#include <wallet/wallet.h>
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
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);
|
||||
|
@ -93,6 +43,7 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr
|
|||
* 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 COutPoint& outpoint) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
|
||||
|
||||
/**
|
||||
* 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
|
||||
#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_confirmed(1, 1, 0);
|
||||
static const CoinEligibilityFilter filter_standard_extra(6, 6, 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;
|
||||
tx.vout.resize(nInput + 1);
|
||||
tx.vout[nInput].nValue = nValue;
|
||||
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)
|
||||
|
@ -50,9 +50,9 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
|
|||
tx.vout.resize(nInput + 1);
|
||||
tx.vout[nInput].nValue = nValue;
|
||||
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;
|
||||
group.Insert(coin, 1, false, 0, 0, true);
|
||||
group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true);
|
||||
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[nInput].nValue = nValue;
|
||||
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.m_fee = fee;
|
||||
coin.m_long_term_fee = long_term_fee;
|
||||
coin.fee = fee;
|
||||
coin.long_term_fee = long_term_fee;
|
||||
set.insert(coin);
|
||||
}
|
||||
|
||||
|
@ -82,24 +82,13 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
|
|||
assert(destination_ok);
|
||||
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();
|
||||
|
||||
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{}));
|
||||
assert(ret.second);
|
||||
CWalletTx& wtx = (*ret.first).second;
|
||||
if (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);
|
||||
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);
|
||||
}
|
||||
|
||||
/** 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) */
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
CAmount target = 0;
|
||||
|
@ -140,24 +132,13 @@ static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
|
|||
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)
|
||||
{
|
||||
static std::vector<OutputGroup> static_groups;
|
||||
static_groups.clear();
|
||||
for (auto& coin : coins) {
|
||||
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;
|
||||
}
|
||||
|
@ -185,7 +166,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
|||
{
|
||||
FastRandomContext rand{};
|
||||
// Setup
|
||||
std::vector<CInputCoin> utxo_pool;
|
||||
std::vector<COutput> utxo_pool;
|
||||
SelectionResult expected_result(CAmount(0));
|
||||
|
||||
/////////////////////////
|
||||
|
@ -329,13 +310,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
|||
std::vector<COutput> coins;
|
||||
|
||||
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));
|
||||
|
||||
// Test fees subtracted from output:
|
||||
coins.clear();
|
||||
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;
|
||||
const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
|
||||
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);
|
||||
CCoinControl coin_control;
|
||||
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);
|
||||
const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||
BOOST_CHECK(result10);
|
||||
|
|
|
@ -588,7 +588,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
|
|||
for (const auto& group : list) {
|
||||
for (const auto& coin : group.second) {
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
|
||||
wallet->LockCoin(coin.outpoint);
|
||||
}
|
||||
}
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue