aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorfanquake <fanquake@gmail.com>2022-04-26 19:13:48 +0100
committerfanquake <fanquake@gmail.com>2022-04-26 19:16:27 +0100
commit260ede1d998b7d3af0b5026c38612ffdf55473c2 (patch)
treeedaa6b6422c7e7ce7a0408c2cb7a705334b0e808 /src
parent833add0f48b0fad84d7b8cf9373a349e7aef20b4 (diff)
parentab5af9ca7293239ffc24ea7e23159b8184543f94 (diff)
downloadbitcoin-260ede1d998b7d3af0b5026c38612ffdf55473c2.tar.xz
Merge bitcoin/bitcoin#24644: wallet: add tracepoints and algorithm information to coin selection
ab5af9ca7293239ffc24ea7e23159b8184543f94 test: Add test for coinselection tracepoints (Andrew Chow) ca02b68e8a7147f80cbe84b0742908b0b0faa04d doc: document coin selection tracepoints (Andrew Chow) 8e3f39e4fa2d8c63bc697c9ebd303965fcccea55 wallet: Add some tracepoints for coin selection (Andrew Chow) 15b58383d0029c4ae7b487e03cd451e1580eb91d wallet: compute waste for SelectionResults of preset inputs (Andrew Chow) 912f1ed181161b0365776cd490b63137aaad708a wallet: track which coin selection algorithm produced a SelectionResult (Andrew Chow) Pull request description: Tracepoints can be useful for coin selection as they would allow us to observe what is being selected, selection parameters, and calculation results. So this PR adds 4 new tracepoints: 1. After `SelectCoins` returns in order to observe the `SelectionResult` 2. After the first `CreateTransactionInternal` to observe the created transaction 3. Prior to the second `CreateTransactionInternal` to notify that the optimistic avoid partial spends selection is occurring 4. After the second `CreateTransactionInternal` to observe the created transaction and inform which solution is being used. This PR also adds an algorithm enum to `SelectionResult` so that the first tracepoint will be able to report which algorithm was used to produce that result. The primary use case for these tracepoints is in running coin selection simulations. The script I use to run these simulations use these tracepoints in order to gather data on the algorithm used and the calculated waste. ACKs for top commit: jb55: crACK ab5af9ca7293239ffc24ea7e23159b8184543f94 josibake: crACK https://github.com/bitcoin/bitcoin/pull/24644/commits/ab5af9ca7293239ffc24ea7e23159b8184543f94 0xB10C: ACK ab5af9ca7293239ffc24ea7e23159b8184543f94. Code reviewed, ran the `interface_usdt_coinselection.py` test, and tested with the above bpftrace script (updated `%d` -> `%ld` where necessary, ty achow101). Tree-SHA512: a4bf7a910cdf464622f2f3b5d44c15b891f24852df6e7f8c5b177fe3d8aaa4a1164593a24c3960eb22b16544fa7140e5c745345367b9e291b78395084c0ac8ff
Diffstat (limited to 'src')
-rw-r--r--src/wallet/coinselection.cpp19
-rw-r--r--src/wallet/coinselection.h21
-rw-r--r--src/wallet/spend.cpp13
-rw-r--r--src/wallet/test/coinselector_tests.cpp2
4 files changed, 45 insertions, 10 deletions
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 433759e086..0b236e2e48 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -64,7 +64,7 @@ static const size_t TOTAL_TRIES = 100000;
std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change)
{
- SelectionResult result(selection_target);
+ SelectionResult result(selection_target, SelectionAlgorithm::BNB);
CAmount curr_value = 0;
std::vector<size_t> curr_selection; // selected utxo indexes
@@ -167,7 +167,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng)
{
- SelectionResult result(target_value);
+ SelectionResult result(target_value, SelectionAlgorithm::SRD);
std::vector<size_t> indexes;
indexes.resize(utxo_pool.size());
@@ -249,7 +249,7 @@ static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::v
std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
CAmount change_target, FastRandomContext& rng)
{
- SelectionResult result(nTargetValue);
+ SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK);
// List of values less than target
std::optional<OutputGroup> lowest_larger;
@@ -460,4 +460,17 @@ std::string COutput::ToString() const
{
return strprintf("COutput(%s, %d, %d) [%s]", outpoint.hash.ToString(), outpoint.n, depth, FormatMoney(txout.nValue));
}
+
+std::string GetAlgorithmName(const SelectionAlgorithm algo)
+{
+ switch (algo)
+ {
+ case SelectionAlgorithm::BNB: return "bnb";
+ case SelectionAlgorithm::KNAPSACK: return "knapsack";
+ case SelectionAlgorithm::SRD: return "srd";
+ case SelectionAlgorithm::MANUAL: return "manual";
+ // No default case to allow for compiler to warn
+ }
+ assert(false);
+}
} // namespace wallet
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index c1484c0a57..25c672eda0 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -239,21 +239,34 @@ struct OutputGroup
*/
[[nodiscard]] CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng);
+enum class SelectionAlgorithm : uint8_t
+{
+ BNB = 0,
+ KNAPSACK = 1,
+ SRD = 2,
+ MANUAL = 3,
+};
+
+std::string GetAlgorithmName(const SelectionAlgorithm algo);
+
struct SelectionResult
{
private:
/** Set of inputs selected by the algorithm to use in the transaction */
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) */
bool m_use_effective{false};
/** The computed waste */
std::optional<CAmount> m_waste;
public:
- explicit SelectionResult(const CAmount target)
- : m_target(target) {}
+ /** 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;
+ /** The algorithm used to produce this result */
+ const SelectionAlgorithm m_algo;
+
+ explicit SelectionResult(const CAmount target, SelectionAlgorithm algo)
+ : m_target(target), m_algo(algo) {}
SelectionResult() = delete;
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 9e508f3a32..55c0a2cb7f 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -11,6 +11,7 @@
#include <util/fees.h>
#include <util/moneystr.h>
#include <util/rbf.h>
+#include <util/trace.h>
#include <util/translation.h>
#include <wallet/coincontrol.h>
#include <wallet/fees.h>
@@ -435,9 +436,10 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
*/
preset_inputs.Insert(out, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
- SelectionResult result(nTargetValue);
+ SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL);
result.AddInput(preset_inputs);
if (result.GetSelectedValue() < nTargetValue) return std::nullopt;
+ result.ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
return result;
}
@@ -519,7 +521,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
// permissive CoinEligibilityFilter.
std::optional<SelectionResult> res = [&] {
// Pre-selected inputs already cover the target amount.
- if (value_to_select <= 0) return std::make_optional(SelectionResult(nTargetValue));
+ if (value_to_select <= 0) return std::make_optional(SelectionResult(nTargetValue, SelectionAlgorithm::MANUAL));
// If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
// confirmations on outputs received from other wallets and only spend confirmed change.
@@ -573,6 +575,9 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
// Add preset inputs to result
res->AddInput(preset_inputs);
+ if (res->m_algo == SelectionAlgorithm::MANUAL) {
+ res->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
+ }
return res;
}
@@ -788,6 +793,7 @@ static bool CreateTransactionInternal(
error = _("Insufficient funds");
return false;
}
+ TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue());
// Always make a change output
// We will reduce the fee from this change output later, and remove the output if it is too small.
@@ -978,8 +984,10 @@ bool CreateTransaction(
int nChangePosIn = nChangePosInOut;
Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr)
bool res = CreateTransactionInternal(wallet, vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
+ TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), res, nFeeRet, nChangePosInOut);
// try with avoidpartialspends unless it's enabled already
if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
+ TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str());
CCoinControl tmp_cc = coin_control;
tmp_cc.m_avoid_partial_spends = true;
CAmount nFeeRet2;
@@ -990,6 +998,7 @@ bool CreateTransaction(
// if fee of this alternative one is within the range of the max fee, we use this one
const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee;
wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
+ TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, res, nFeeRet2, nChangePosInOut2);
if (use_aps) {
tx = tx2;
nFeeRet = nFeeRet2;
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 2a08c8ab57..72e749477b 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -168,7 +168,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
FastRandomContext rand{};
// Setup
std::vector<COutput> utxo_pool;
- SelectionResult expected_result(CAmount(0));
+ SelectionResult expected_result(CAmount(0), SelectionAlgorithm::BNB);
/////////////////////////
// Known Outcome tests //