Skip to content

Commit e057589

Browse files
committed
Merge #10637: Coin Selection with Murch's algorithm
73b5bf2 Add a test to make sure that negative effective values are filtered (Andrew Chow) 76d2f06 Benchmark BnB in the worst case where it exhausts (Andrew Chow) 6a34ff5 Have SelectCoinsMinConf and SelectCoins use BnB or Knapsack and use it (Andrew Chow) fab0488 Add a GetMinimumFeeRate function which is wrapped by GetMinimumFee (Andrew Chow) cd927ff Move original knapsack solver tests to coinselector_tests.cpp (Andrew Chow) fb716f7 Move current coin selection algorithm to coinselection.{cpp,h} (Andrew Chow) 4566ab7 Add tests for the Branch and Bound algorithm (Andrew Chow) 4b2716d Remove coinselection.h -> wallet.h circular dependency (Andrew Chow) 7d77eb1 Use a struct for output eligibility (Andrew Chow) ce7435c Move output eligibility to a separate function (Andrew Chow) 0185939 Implement Branch and Bound coin selection in a new file (Andrew Chow) f84fed8 Store effective value, fee, and long term fee in CInputCoin (Andrew Chow) 12ec29d Calculate and store the number of bytes required to spend an input (Andrew Chow) Pull request description: This is an implementation of the [Branch and Bound coin selection algorithm written by Murch](http://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf) (@xekyo). I have it set so this algorithm will run first and if it fails, it will fall back to the current coin selection algorithm. The coin selection algorithms and tests have been refactored to separate files instead of having them all in wallet.cpp. I have added some tests for the new algorithm and a test for all of coin selection in general. However, more tests may be needed, but I will need help with coming up with more test cases. This PR uses some code borrowed from #10360 to use effective values when selecting coins. Tree-SHA512: b0500f406bf671e74984fae78e2d0fbc5e321ddf4f06182c5855e9d1984c4ef2764c7586d03e16fa4b578c340b21710324926f9ca472d5447a0d1ed43eb4357e
2 parents e7721e6 + 73b5bf2 commit e057589

17 files changed

+1264
-626
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ BITCOIN_CORE_H = \
172172
wallet/wallet.h \
173173
wallet/walletdb.h \
174174
wallet/walletutil.h \
175+
wallet/coinselection.h \
175176
warnings.h \
176177
zmq/zmqabstractnotifier.h \
177178
zmq/zmqconfig.h\
@@ -253,6 +254,7 @@ libbitcoin_wallet_a_SOURCES = \
253254
wallet/wallet.cpp \
254255
wallet/walletdb.cpp \
255256
wallet/walletutil.cpp \
257+
wallet/coinselection.cpp \
256258
$(BITCOIN_CORE_H)
257259

258260
# crypto primitives library

src/Makefile.test.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ BITCOIN_TESTS += \
9494
wallet/test/wallet_test_fixture.h \
9595
wallet/test/accounting_tests.cpp \
9696
wallet/test/wallet_tests.cpp \
97-
wallet/test/crypto_tests.cpp
97+
wallet/test/crypto_tests.cpp \
98+
wallet/test/coinselector_tests.cpp
9899
endif
99100

100101
test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)

src/bench/coin_selection.cpp

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <bench/bench.h>
66
#include <wallet/wallet.h>
7+
#include <wallet/coinselection.h>
78

89
#include <set>
910

@@ -44,7 +45,11 @@ static void CoinSelection(benchmark::State& state)
4445

4546
std::set<CInputCoin> setCoinsRet;
4647
CAmount nValueRet;
47-
bool success = wallet.SelectCoinsMinConf(1003 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet);
48+
bool bnb_used;
49+
CoinEligibilityFilter filter_standard(1, 6, 0);
50+
CoinSelectionParams coin_selection_params(false, 34, 148, CFeeRate(0), 0);
51+
bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)
52+
|| wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used);
4853
assert(success);
4954
assert(nValueRet == 1003 * COIN);
5055
assert(setCoinsRet.size() == 2);
@@ -57,4 +62,47 @@ static void CoinSelection(benchmark::State& state)
5762
}
5863
}
5964

65+
typedef std::set<CInputCoin> CoinSet;
66+
67+
// Copied from src/wallet/test/coinselector_tests.cpp
68+
static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
69+
{
70+
CMutableTransaction tx;
71+
tx.vout.resize(nInput + 1);
72+
tx.vout[nInput].nValue = nValue;
73+
set.emplace_back(MakeTransactionRef(tx), nInput);
74+
}
75+
// Copied from src/wallet/test/coinselector_tests.cpp
76+
static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
77+
{
78+
utxo_pool.clear();
79+
CAmount target = 0;
80+
for (int i = 0; i < utxos; ++i) {
81+
target += (CAmount)1 << (utxos+i);
82+
add_coin((CAmount)1 << (utxos+i), 2*i, utxo_pool);
83+
add_coin(((CAmount)1 << (utxos+i)) + ((CAmount)1 << (utxos-1-i)), 2*i + 1, utxo_pool);
84+
}
85+
return target;
86+
}
87+
88+
static void BnBExhaustion(benchmark::State& state)
89+
{
90+
// Setup
91+
std::vector<CInputCoin> utxo_pool;
92+
CoinSet selection;
93+
CAmount value_ret = 0;
94+
CAmount not_input_fees = 0;
95+
96+
while (state.KeepRunning()) {
97+
// Benchmark
98+
CAmount target = make_hard_case(17, utxo_pool);
99+
SelectCoinsBnB(utxo_pool, target, 0, selection, value_ret, not_input_fees); // Should exhaust
100+
101+
// Cleanup
102+
utxo_pool.clear();
103+
selection.clear();
104+
}
105+
}
106+
60107
BENCHMARK(CoinSelection, 650);
108+
BENCHMARK(BnBExhaustion, 650);

src/consensus/validation.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,10 @@ static inline int64_t GetBlockWeight(const CBlock& block)
101101
{
102102
return ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION);
103103
}
104+
static inline int64_t GetTransationInputWeight(const CTxIn& txin)
105+
{
106+
// scriptWitness size is added here because witnesses and txins are split up in segwit serialization.
107+
return ::GetSerializeSize(txin, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(txin, SER_NETWORK, PROTOCOL_VERSION) + ::GetSerializeSize(txin.scriptWitness.stack, SER_NETWORK, PROTOCOL_VERSION);
108+
}
104109

105110
#endif // BITCOIN_CONSENSUS_VALIDATION_H

src/policy/policy.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,8 @@ int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost)
258258
{
259259
return GetVirtualTransactionSize(GetTransactionWeight(tx), nSigOpCost);
260260
}
261+
262+
int64_t GetVirtualTransactionInputSize(const CTxIn& txin, int64_t nSigOpCost)
263+
{
264+
return GetVirtualTransactionSize(GetTransationInputWeight(txin), nSigOpCost);
265+
}

src/policy/policy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,6 @@ extern unsigned int nBytesPerSigOp;
102102
/** Compute the virtual transaction size (weight reinterpreted as bytes). */
103103
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost);
104104
int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost = 0);
105+
int64_t GetVirtualTransactionInputSize(const CTxIn& tx, int64_t nSigOpCost = 0);
105106

106107
#endif // BITCOIN_POLICY_POLICY_H

src/script/sign.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,16 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI
194194
return data;
195195
}
196196

197+
void UpdateInput(CTxIn& input, const SignatureData& data)
198+
{
199+
input.scriptSig = data.scriptSig;
200+
input.scriptWitness = data.scriptWitness;
201+
}
202+
197203
void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data)
198204
{
199205
assert(tx.vin.size() > nIn);
200-
tx.vin[nIn].scriptSig = data.scriptSig;
201-
tx.vin[nIn].scriptWitness = data.scriptWitness;
206+
UpdateInput(tx.vin[nIn], data);
202207
}
203208

204209
bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType)

src/script/sign.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignature
8080
/** Extract signature data from a transaction, and insert it. */
8181
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn);
8282
void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data);
83+
void UpdateInput(CTxIn& input, const SignatureData& data);
8384

8485
/* Check whether we know how to sign for an output like this, assuming we
8586
* have all private keys. While this function does not need private keys, the passed

0 commit comments

Comments
 (0)