diff --git a/doc/release-notes.md b/doc/release-notes.md
index af07aa910..83b9aed41 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,28 +1,7 @@
# Bitcoin ABC 0.22.4 Release Notes
Bitcoin ABC version 0.22.4 is now available from:
This release includes the following features and fixes:
-
-Command-line options
---------------------
-
-- The `-debug=db` logging category has been renamed to `-debug=walletdb`, to distinguish it from `coindb`.
- `-debug=db` has been deprecated and will be removed in a next release.
-
-Low-level RPC Changes
----------------------
-
-- The RPC gettransaction, listtransactions and listsinceblock responses now also
-includes the height of the block that contains the wallet transaction, if any.
-
-Deprecated or removed RPCs
---------------------------
-
-- The `getaddressinfo` RPC `labels` field now returns an array of label name
- strings. Previously, it returned an array of JSON objects containing `name` and
- `purpose` key/value pairs, which is now deprecated and will be removed in a future
- release. To re-enable the previous behavior, launch bitcoind with
- `-deprecatedrpc=labelspurpose`.
diff --git a/doc/release-notes/release-notes-.md b/doc/release-notes/release-notes-.md
index cd9a31746..af07aa910 100644
--- a/doc/release-notes/release-notes-.md
+++ b/doc/release-notes/release-notes-.md
@@ -1,12 +1,28 @@
# Bitcoin ABC 0.22.4 Release Notes
Bitcoin ABC version 0.22.4 is now available from:
This release includes the following features and fixes:
-Wallet
-------
+Command-line options
+--------------------
-- The way that output trust was computed has been fixed, which impacts confirmed/unconfirmed balance status and coin selection.
+- The `-debug=db` logging category has been renamed to `-debug=walletdb`, to distinguish it from `coindb`.
+ `-debug=db` has been deprecated and will be removed in a next release.
+
+Low-level RPC Changes
+---------------------
+
+- The RPC gettransaction, listtransactions and listsinceblock responses now also
+includes the height of the block that contains the wallet transaction, if any.
+
+Deprecated or removed RPCs
+--------------------------
+
+- The `getaddressinfo` RPC `labels` field now returns an array of label name
+ strings. Previously, it returned an array of JSON objects containing `name` and
+ `purpose` key/value pairs, which is now deprecated and will be removed in a future
+ release. To re-enable the previous behavior, launch bitcoind with
+ `-deprecatedrpc=labelspurpose`.
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 58a430bea..cd361ecd8 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -1,809 +1,779 @@
// Copyright (c) 2017 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include
#include // For Params
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
// how many times to run all the tests to have a chance to catch errors that
// only show up with particular random shuffles
#define RUN_TESTS 100
// some tests fail 1% of the time due to bad luck.
// we repeat those tests this many times and only complain if all iterations of
// the test fail
#define RANDOM_REPEATS 5
std::vector> wtxn;
typedef std::set CoinSet;
static std::vector vCoins;
static NodeContext testNode;
static Amount balance = Amount::zero();
CoinEligibilityFilter filter_standard(1, 6, 0);
CoinEligibilityFilter filter_confirmed(1, 1, 0);
CoinEligibilityFilter filter_standard_extra(6, 6, 0);
CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(Amount::zero()),
0);
static void add_coin(const Amount nValue, int nInput,
std::vector &set) {
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
set.emplace_back(MakeTransactionRef(tx), nInput);
}
static void add_coin(const Amount nValue, int nInput, CoinSet &set) {
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
set.emplace(MakeTransactionRef(tx), nInput);
}
static void add_coin(CWallet &wallet, const Amount nValue, int nAge = 6 * 24,
- bool fIsFromMe = false, int nInput = 0,
- bool spendable = false) {
+ bool fIsFromMe = false, int nInput = 0) {
balance += nValue;
static int nextLockTime = 0;
CMutableTransaction tx;
// so all transactions get different hashes
tx.nLockTime = nextLockTime++;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
- if (spendable) {
- CTxDestination dest;
- std::string error;
- assert(wallet.GetNewDestination(OutputType::LEGACY, "", dest, error));
- 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);
}
auto wtx =
std::make_unique(&wallet, MakeTransactionRef(std::move(tx)));
if (fIsFromMe) {
wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, SATOSHI);
}
COutput output(wtx.get(), nInput, nAge, true /* spendable */,
true /* solvable */, true /* safe */);
vCoins.push_back(output);
wallet.AddToWallet(*wtx.get());
wtxn.emplace_back(std::move(wtx));
}
static void empty_wallet() {
vCoins.clear();
wtxn.clear();
balance = Amount::zero();
}
static bool equal_sets(CoinSet a, CoinSet b) {
std::pair ret =
mismatch(a.begin(), a.end(), b.begin());
return ret.first == a.end() && ret.second == b.end();
}
static Amount make_hard_case(int utxos, std::vector &utxo_pool) {
utxo_pool.clear();
Amount target = Amount::zero();
for (int i = 0; i < utxos; ++i) {
const Amount base = (int64_t(1) << (utxos + i)) * SATOSHI;
target += base;
add_coin(base, 2 * i, utxo_pool);
add_coin(base + (int64_t(1) << (utxos - 1 - i)) * SATOSHI, 2 * i + 1,
utxo_pool);
}
return target;
}
inline std::vector &
GroupCoins(const std::vector &coins) {
static std::vector static_groups;
static_groups.clear();
for (auto &coin : coins) {
static_groups.emplace_back(coin, 0, true, 0, 0);
}
return static_groups;
}
inline std::vector &GroupCoins(const std::vector &coins) {
static std::vector static_groups;
static_groups.clear();
for (auto &coin : coins) {
// HACK: we can't figure out the is_me flag so we use the conditions
// defined below; perhaps set safe to false for !fIsFromMe in add_coin()
const bool is_me =
coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] &&
coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] ==
SATOSHI;
static_groups.emplace_back(coin.GetInputCoin(), coin.nDepth, is_me, 0,
0);
}
return static_groups;
}
// Branch and bound coin selection tests
BOOST_AUTO_TEST_CASE(bnb_search_test) {
LOCK(m_wallet.cs_wallet);
// Setup
std::vector utxo_pool;
CoinSet selection;
CoinSet actual_selection;
Amount value_ret = Amount::zero();
Amount not_input_fees = Amount::zero();
/////////////////////////
// Known Outcome tests //
/////////////////////////
// Empty utxo pool
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, CENT / 2,
selection, value_ret, not_input_fees));
selection.clear();
// Add utxos
add_coin(1 * CENT, 1, utxo_pool);
add_coin(2 * CENT, 2, utxo_pool);
add_coin(3 * CENT, 3, utxo_pool);
add_coin(4 * CENT, 4, utxo_pool);
// Select 1 Cent
add_coin(1 * CENT, 1, actual_selection);
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, CENT / 2,
selection, value_ret, not_input_fees));
BOOST_CHECK(equal_sets(selection, actual_selection));
BOOST_CHECK_EQUAL(value_ret, 1 * CENT);
actual_selection.clear();
selection.clear();
// Select 2 Cent
add_coin(2 * CENT, 2, actual_selection);
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 2 * CENT, CENT / 2,
selection, value_ret, not_input_fees));
BOOST_CHECK(equal_sets(selection, actual_selection));
BOOST_CHECK_EQUAL(value_ret, 2 * CENT);
actual_selection.clear();
selection.clear();
// Select 5 Cent
add_coin(3 * CENT, 3, actual_selection);
add_coin(2 * CENT, 2, actual_selection);
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, CENT / 2,
selection, value_ret, not_input_fees));
BOOST_CHECK(equal_sets(selection, actual_selection));
BOOST_CHECK_EQUAL(value_ret, 5 * CENT);
actual_selection.clear();
selection.clear();
// Select 11 Cent, not possible
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 11 * CENT, CENT / 2,
selection, value_ret, not_input_fees));
actual_selection.clear();
selection.clear();
// Select 10 Cent
add_coin(5 * CENT, 5, utxo_pool);
add_coin(4 * CENT, 4, actual_selection);
add_coin(3 * CENT, 3, actual_selection);
add_coin(2 * CENT, 2, actual_selection);
add_coin(1 * CENT, 1, actual_selection);
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, CENT / 2,
selection, value_ret, not_input_fees));
BOOST_CHECK(equal_sets(selection, actual_selection));
BOOST_CHECK_EQUAL(value_ret, 10 * CENT);
actual_selection.clear();
selection.clear();
// Negative effective value
// Select 10 Cent but have 1 Cent not be possible because too small
add_coin(5 * CENT, 5, actual_selection);
add_coin(3 * CENT, 3, actual_selection);
add_coin(2 * CENT, 2, actual_selection);
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 5000 * SATOSHI,
selection, value_ret, not_input_fees));
BOOST_CHECK_EQUAL(value_ret, 10 * CENT);
// FIXME: this test is redundant with the above, because 1 Cent is selected,
// not "too small" BOOST_CHECK(equal_sets(selection, actual_selection));
// Select 0.25 Cent, not possible
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), CENT / 4, CENT / 2,
selection, value_ret, not_input_fees));
actual_selection.clear();
selection.clear();
// Iteration exhaustion test
Amount target = make_hard_case(17, utxo_pool);
// Should exhaust
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, Amount::zero(),
selection, value_ret, not_input_fees));
target = make_hard_case(14, utxo_pool);
// Should not exhaust
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), target, Amount::zero(),
selection, value_ret, not_input_fees));
// Test same value early bailout optimization
utxo_pool.clear();
add_coin(7 * CENT, 7, actual_selection);
add_coin(7 * CENT, 7, actual_selection);
add_coin(7 * CENT, 7, actual_selection);
add_coin(7 * CENT, 7, actual_selection);
add_coin(2 * CENT, 7, actual_selection);
add_coin(7 * CENT, 7, utxo_pool);
add_coin(7 * CENT, 7, utxo_pool);
add_coin(7 * CENT, 7, utxo_pool);
add_coin(7 * CENT, 7, utxo_pool);
add_coin(2 * CENT, 7, utxo_pool);
for (int i = 0; i < 50000; ++i) {
add_coin(5 * CENT, 7, utxo_pool);
}
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 30 * CENT, 5000 * SATOSHI,
selection, value_ret, not_input_fees));
BOOST_CHECK_EQUAL(value_ret, 30 * CENT);
BOOST_CHECK(equal_sets(selection, actual_selection));
////////////////////
// Behavior tests //
////////////////////
// Select 1 Cent with pool of only greater than 5 Cent
utxo_pool.clear();
for (int i = 5; i <= 20; ++i) {
add_coin(i * CENT, i, utxo_pool);
}
// Run 100 times, to make sure it is never finding a solution
for (int i = 0; i < 100; ++i) {
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT,
selection, value_ret, not_input_fees));
}
// Make sure that effective value is working in SelectCoinsMinConf when BnB
// is used
CoinSelectionParams coin_selection_params_bnb(true, 0, 0,
CFeeRate(3000 * SATOSHI), 0);
CoinSet setCoinsRet;
Amount nValueRet;
bool bnb_used;
empty_wallet();
add_coin(m_wallet, SATOSHI);
// Make sure that it has a negative effective value. The next check should
// assert if this somehow got through. Otherwise it will fail
vCoins.at(0).nInputBytes = 40;
BOOST_CHECK(!m_wallet.SelectCoinsMinConf(
1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet,
coin_selection_params_bnb, bnb_used));
- // Test fees subtracted from output:
- empty_wallet();
- add_coin(m_wallet, 1 * CENT);
- vCoins.at(0).nInputBytes = 40;
- BOOST_CHECK(!m_wallet.SelectCoinsMinConf(
- 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet,
- coin_selection_params_bnb, bnb_used));
- coin_selection_params_bnb.m_subtract_fee_outputs = true;
- BOOST_CHECK(m_wallet.SelectCoinsMinConf(
- 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet,
- coin_selection_params_bnb, bnb_used));
- BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
-
- // Make sure that can use BnB when there are preset inputs
+ // Make sure that we aren't using BnB when there are preset inputs
empty_wallet();
- {
- auto wallet =
- std::make_unique(Params(), m_chain.get(), WalletLocation(),
- WalletDatabase::CreateMock());
- bool firstRun;
- wallet->LoadWallet(firstRun);
- LOCK(wallet->cs_wallet);
- add_coin(*wallet, 5 * CENT, 6 * 24, false, 0, true);
- add_coin(*wallet, 3 * CENT, 6 * 24, false, 0, true);
- add_coin(*wallet, 2 * CENT, 6 * 24, false, 0, true);
- CCoinControl coin_control;
- coin_control.fAllowOtherInputs = true;
- coin_control.Select(
- COutPoint(vCoins.at(0).tx->GetId(), vCoins.at(0).i));
- coin_selection_params_bnb.effective_fee = CFeeRate(Amount::zero());
- BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet,
- nValueRet, coin_control,
- coin_selection_params_bnb, bnb_used));
- BOOST_CHECK(bnb_used);
- BOOST_CHECK(coin_selection_params_bnb.use_bnb);
- }
+ add_coin(m_wallet, 5 * CENT);
+ add_coin(m_wallet, 3 * CENT);
+ add_coin(m_wallet, 2 * CENT);
+ CCoinControl coin_control;
+ coin_control.fAllowOtherInputs = true;
+ coin_control.Select(COutPoint(vCoins.at(0).tx->GetId(), vCoins.at(0).i));
+ BOOST_CHECK(m_wallet.SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet,
+ coin_control, coin_selection_params_bnb,
+ bnb_used));
+ BOOST_CHECK(!bnb_used);
+ BOOST_CHECK(!coin_selection_params_bnb.use_bnb);
}
BOOST_AUTO_TEST_CASE(knapsack_solver_test) {
auto testChain = interfaces::MakeChain(testNode, Params());
CWallet testWallet(Params(), testChain.get(), WalletLocation(),
WalletDatabase::CreateDummy());
CoinSet setCoinsRet, setCoinsRet2;
Amount nValueRet;
bool bnb_used;
LOCK(testWallet.cs_wallet);
// test multiple times to allow for differences in the shuffle order
for (int i = 0; i < RUN_TESTS; i++) {
empty_wallet();
// with an empty wallet we can't even pay one cent
BOOST_CHECK(!testWallet.SelectCoinsMinConf(
1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// add a new 1 cent coin
add_coin(testWallet, 1 * CENT, 4);
// with a new 1 cent coin, we still can't find a mature 1 cent
BOOST_CHECK(!testWallet.SelectCoinsMinConf(
1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// but we can find a new 1 cent
BOOST_CHECK(testWallet.SelectCoinsMinConf(
1 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
// add a mature 2 cent coin
add_coin(testWallet, 2 * CENT);
// we can't make 3 cents of mature coins
BOOST_CHECK(!testWallet.SelectCoinsMinConf(
3 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we can make 3 cents of new coins
BOOST_CHECK(testWallet.SelectCoinsMinConf(
3 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
// add a mature 5 cent coin,
add_coin(testWallet, 5 * CENT);
// a new 10 cent coin sent from one of our own addresses
add_coin(testWallet, 10 * CENT, 3, true);
// and a mature 20 cent coin
add_coin(testWallet, 20 * CENT);
// now we have new: 1+10=11 (of which 10 was self-sent), and mature:
// 2+5+20=27. total = 38
// we can't make 38 cents only if we disallow new coins:
BOOST_CHECK(!testWallet.SelectCoinsMinConf(
38 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we can't even make 37 cents if we don't allow new coins even if
// they're from us
BOOST_CHECK(!testWallet.SelectCoinsMinConf(
38 * CENT, filter_standard_extra, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// but we can make 37 cents if we accept new coins from ourself
BOOST_CHECK(testWallet.SelectCoinsMinConf(
37 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
// and we can make 38 cents if we accept all new coins
BOOST_CHECK(testWallet.SelectCoinsMinConf(
38 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
BOOST_CHECK(testWallet.SelectCoinsMinConf(
34 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// but 35 cents is closest
BOOST_CHECK_EQUAL(nValueRet, 35 * CENT);
// the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got
// included (but possible)
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We
// should see just 2+5
BOOST_CHECK(testWallet.SelectCoinsMinConf(
7 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
// when we try making 8 cents, the smaller coins (1,2,5) are exactly
// enough.
BOOST_CHECK(testWallet.SelectCoinsMinConf(
8 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(nValueRet == 8 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// when we try making 9 cents, no subset of smaller coins is enough, and
// we get the next bigger coin (10)
BOOST_CHECK(testWallet.SelectCoinsMinConf(
9 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// now clear out the wallet and start again to test choosing between
// subsets of smaller coins and the next biggest coin
empty_wallet();
add_coin(testWallet, 6 * CENT);
add_coin(testWallet, 7 * CENT);
add_coin(testWallet, 8 * CENT);
add_coin(testWallet, 20 * CENT);
// now we have 6+7+8+20+30 = 71 cents total
add_coin(testWallet, 30 * CENT);
// check that we have 71 and not 72
BOOST_CHECK(testWallet.SelectCoinsMinConf(
71 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(!testWallet.SelectCoinsMinConf(
72 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// now try making 16 cents. the best smaller coins can do is 6+7+8 =
// 21; not as good at the next biggest coin, 20
BOOST_CHECK(testWallet.SelectCoinsMinConf(
16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we should get 20 in one coin
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// now we have 5+6+7+8+20+30 = 75 cents total
add_coin(testWallet, 5 * CENT);
// now if we try making 16 cents again, the smaller coins can make 5+6+7
// = 18 cents, better than the next biggest coin, 20
BOOST_CHECK(testWallet.SelectCoinsMinConf(
16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we should get 18 in 3 coins
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// now we have 5+6+7+8+18+20+30
add_coin(testWallet, 18 * CENT);
// and now if we try making 16 cents again, the smaller coins can make
// 5+6+7 = 18 cents, the same as the next biggest coin, 18
BOOST_CHECK(testWallet.SelectCoinsMinConf(
16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we should get 18 in 1 coin
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT);
// because in the event of a tie, the biggest coin wins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// now try making 11 cents. we should get 5+6
BOOST_CHECK(testWallet.SelectCoinsMinConf(
11 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
// check that the smallest bigger coin is used
add_coin(testWallet, 1 * COIN);
add_coin(testWallet, 2 * COIN);
add_coin(testWallet, 3 * COIN);
// now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
add_coin(testWallet, 4 * COIN);
BOOST_CHECK(testWallet.SelectCoinsMinConf(
95 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we should get 1 BCH in 1 coin
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
BOOST_CHECK(testWallet.SelectCoinsMinConf(
195 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we should get 2 BCH in 1 coin
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// empty the wallet and start again, now with fractions of a cent, to
// test small change avoidance
empty_wallet();
add_coin(testWallet, 1 * MIN_CHANGE / 10);
add_coin(testWallet, 2 * MIN_CHANGE / 10);
add_coin(testWallet, 3 * MIN_CHANGE / 10);
add_coin(testWallet, 4 * MIN_CHANGE / 10);
add_coin(testWallet, 5 * MIN_CHANGE / 10);
// try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
// we'll get change smaller than MIN_CHANGE whatever happens, so can
// expect MIN_CHANGE exactly
BOOST_CHECK(testWallet.SelectCoinsMinConf(
MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
// but if we add a bigger coin, small change is avoided
add_coin(testWallet, 1111 * MIN_CHANGE);
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
BOOST_CHECK(testWallet.SelectCoinsMinConf(
1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we should get the exact amount
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE);
// if we add more small coins:
add_coin(testWallet, 6 * MIN_CHANGE / 10);
add_coin(testWallet, 7 * MIN_CHANGE / 10);
// and try again to make 1.0 * MIN_CHANGE
BOOST_CHECK(testWallet.SelectCoinsMinConf(
1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we should get the exact amount
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE);
// run the 'mtgox' test (see
// http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
// they tried to consolidate 10 50k coins into one 500k coin, and ended
// up with 50k in change
empty_wallet();
for (int j = 0; j < 20; j++) {
add_coin(testWallet, 50000 * COIN);
}
BOOST_CHECK(testWallet.SelectCoinsMinConf(
500000 * COIN, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we should get the exact amount
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN);
// in ten coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U);
// if there's not enough in the smaller coins to make at least 1 *
// MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0), we need to try finding an
// exact subset anyway
// sometimes it will fail, and so we use the next biggest coin:
empty_wallet();
add_coin(testWallet, 5 * MIN_CHANGE / 10);
add_coin(testWallet, 6 * MIN_CHANGE / 10);
add_coin(testWallet, 7 * MIN_CHANGE / 10);
add_coin(testWallet, 1111 * MIN_CHANGE);
BOOST_CHECK(testWallet.SelectCoinsMinConf(
1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we get the bigger coin
BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 =
// 1.0)
empty_wallet();
add_coin(testWallet, 4 * MIN_CHANGE / 10);
add_coin(testWallet, 6 * MIN_CHANGE / 10);
add_coin(testWallet, 8 * MIN_CHANGE / 10);
add_coin(testWallet, 1111 * MIN_CHANGE);
BOOST_CHECK(testWallet.SelectCoinsMinConf(
MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
// we should get the exact amount
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
// in two coins 0.4+0.6
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
// test avoiding small change
empty_wallet();
add_coin(testWallet, 5 * MIN_CHANGE / 100);
add_coin(testWallet, 1 * MIN_CHANGE);
add_coin(testWallet, 100 * MIN_CHANGE);
// trying to make 100.01 from these three coins
BOOST_CHECK(testWallet.SelectCoinsMinConf(
10001 * MIN_CHANGE / 100, filter_confirmed, GroupCoins(vCoins),
setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// we should get all coins
BOOST_CHECK_EQUAL(nValueRet, 10105 * MIN_CHANGE / 100);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// but if we try to make 99.9, we should take the bigger of the two
// small coins to avoid small change
BOOST_CHECK(testWallet.SelectCoinsMinConf(
9990 * MIN_CHANGE / 100, filter_confirmed, GroupCoins(vCoins),
setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
}
// test with many inputs
for (Amount amt = 1500 * SATOSHI; amt < COIN; amt = 10 * amt) {
empty_wallet();
// Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148
// bytes per input)
for (uint16_t j = 0; j < 676; j++) {
add_coin(testWallet, amt);
}
// We only create the wallet once to save time, but we still run the
// coin selection RUN_TESTS times.
for (int i = 0; i < RUN_TESTS; i++) {
BOOST_CHECK(testWallet.SelectCoinsMinConf(
2000 * SATOSHI, filter_confirmed, GroupCoins(vCoins),
setCoinsRet, nValueRet, coin_selection_params, bnb_used));
if (amt - 2000 * SATOSHI < MIN_CHANGE) {
// needs more than one input:
uint16_t returnSize = std::ceil(
(2000.0 + (MIN_CHANGE / SATOSHI)) / (amt / SATOSHI));
Amount returnValue = returnSize * amt;
BOOST_CHECK_EQUAL(nValueRet, returnValue);
BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize);
} else {
// one input is sufficient:
BOOST_CHECK_EQUAL(nValueRet, amt);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
}
}
}
// test randomness
{
empty_wallet();
for (int i2 = 0; i2 < 100; i2++) {
add_coin(testWallet, COIN);
}
// Again, we only create the wallet once to save time, but we still run
// the coin selection RUN_TESTS times.
for (int i = 0; i < RUN_TESTS; i++) {
// picking 50 from 100 coins doesn't depend on the shuffle, but does
// depend on randomness in the stochastic approximation code
BOOST_CHECK(testWallet.SelectCoinsMinConf(
50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(testWallet.SelectCoinsMinConf(
50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
int fails = 0;
for (int j = 0; j < RANDOM_REPEATS; j++) {
// selecting 1 from 100 identical coins depends on the shuffle;
// this test will fail 1% of the time run the test
// RANDOM_REPEATS times and only complain if all of them fail
BOOST_CHECK(testWallet.SelectCoinsMinConf(
COIN, filter_standard, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(testWallet.SelectCoinsMinConf(
COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2,
nValueRet, coin_selection_params, bnb_used));
if (equal_sets(setCoinsRet, setCoinsRet2)) {
fails++;
}
}
BOOST_CHECK_NE(fails, RANDOM_REPEATS);
}
// add 75 cents in small change. not enough to make 90 cents, then
// try making 90 cents. there are multiple competing "smallest
// bigger" coins, one of which should be picked at random
add_coin(testWallet, 5 * CENT);
add_coin(testWallet, 10 * CENT);
add_coin(testWallet, 15 * CENT);
add_coin(testWallet, 20 * CENT);
add_coin(testWallet, 25 * CENT);
for (int i = 0; i < RUN_TESTS; i++) {
int fails = 0;
for (int j = 0; j < RANDOM_REPEATS; j++) {
// selecting 1 from 100 identical coins depends on the shuffle;
// this test will fail 1% of the time run the test
// RANDOM_REPEATS times and only complain if all of them fail
BOOST_CHECK(testWallet.SelectCoinsMinConf(
90 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(testWallet.SelectCoinsMinConf(
90 * CENT, filter_standard, GroupCoins(vCoins),
setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
if (equal_sets(setCoinsRet, setCoinsRet2)) {
fails++;
}
}
BOOST_CHECK_NE(fails, RANDOM_REPEATS);
}
}
empty_wallet();
}
BOOST_AUTO_TEST_CASE(ApproximateBestSubset) {
CoinSet setCoinsRet;
Amount nValueRet;
bool bnb_used;
LOCK(m_wallet.cs_wallet);
empty_wallet();
// Test vValue sort order
for (int i = 0; i < 1000; i++) {
add_coin(m_wallet, 1000 * COIN);
}
add_coin(m_wallet, 3 * COIN);
BOOST_CHECK(m_wallet.SelectCoinsMinConf(
1003 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet,
nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
empty_wallet();
}
// Tests that with the ideal conditions, the coin selector will always be able
// to find a solution that can pay the target value
BOOST_AUTO_TEST_CASE(SelectCoins_test) {
auto testChain = interfaces::MakeChain(testNode, Params());
CWallet testWallet(Params(), testChain.get(), WalletLocation(),
WalletDatabase::CreateDummy());
// Random generator stuff
std::default_random_engine generator;
std::exponential_distribution distribution(100);
FastRandomContext rand;
// Run this test 100 times
for (int i = 0; i < 100; ++i) {
empty_wallet();
// Make a wallet with 1000 exponentially distributed random inputs
for (int j = 0; j < 1000; ++j) {
add_coin(testWallet,
int64_t(10000000 * distribution(generator)) * SATOSHI);
}
// Generate a random fee rate in the range of 100 - 400
CFeeRate rate(int64_t(rand.randrange(300) + 100) * SATOSHI);
// Generate a random target value between 1000 and wallet balance
Amount target =
int64_t(rand.randrange(balance / SATOSHI - 1000) + 1000) * SATOSHI;
// Perform selection
CoinSelectionParams coin_selection_params_knapsack(
false, 34, 148, CFeeRate(Amount::zero()), 0);
CoinSelectionParams coin_selection_params_bnb(
true, 34, 148, CFeeRate(Amount::zero()), 0);
CoinSet out_set;
Amount out_value = Amount::zero();
bool bnb_used = false;
BOOST_CHECK(testWallet.SelectCoinsMinConf(
target, filter_standard, GroupCoins(vCoins), out_set,
out_value, coin_selection_params_bnb, bnb_used) ||
testWallet.SelectCoinsMinConf(
target, filter_standard, GroupCoins(vCoins), out_set,
out_value, coin_selection_params_knapsack, bnb_used));
BOOST_CHECK_GE(out_value, target);
}
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index aa7e099da..2406faac5 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,4545 +1,4525 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include