aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/test')
-rw-r--r--src/wallet/test/accounting_tests.cpp52
-rw-r--r--src/wallet/test/coinselector_tests.cpp575
-rw-r--r--src/wallet/test/crypto_tests.cpp2
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp18
-rw-r--r--src/wallet/test/wallet_test_fixture.h9
-rw-r--r--src/wallet/test/wallet_tests.cpp413
6 files changed, 650 insertions, 419 deletions
diff --git a/src/wallet/test/accounting_tests.cpp b/src/wallet/test/accounting_tests.cpp
index cafd69d075..cc6e491f53 100644
--- a/src/wallet/test/accounting_tests.cpp
+++ b/src/wallet/test/accounting_tests.cpp
@@ -13,13 +13,13 @@
BOOST_FIXTURE_TEST_SUITE(accounting_tests, WalletTestingSetup)
static void
-GetResults(CWallet *wallet, std::map<CAmount, CAccountingEntry>& results)
+GetResults(CWallet& wallet, std::map<CAmount, CAccountingEntry>& results)
{
std::list<CAccountingEntry> aes;
results.clear();
- BOOST_CHECK(wallet->ReorderTransactions() == DB_LOAD_OK);
- wallet->ListAccountCreditDebit("", aes);
+ BOOST_CHECK(wallet.ReorderTransactions() == DBErrors::LOAD_OK);
+ wallet.ListAccountCreditDebit("", aes);
for (CAccountingEntry& ae : aes)
{
results[ae.nOrderPos] = ae;
@@ -29,32 +29,32 @@ GetResults(CWallet *wallet, std::map<CAmount, CAccountingEntry>& results)
BOOST_AUTO_TEST_CASE(acc_orderupgrade)
{
std::vector<CWalletTx*> vpwtx;
- CWalletTx wtx;
+ CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
CAccountingEntry ae;
std::map<CAmount, CAccountingEntry> results;
- LOCK(pwalletMain->cs_wallet);
+ LOCK(m_wallet.cs_wallet);
ae.strAccount = "";
ae.nCreditDebit = 1;
ae.nTime = 1333333333;
ae.strOtherAccount = "b";
ae.strComment = "";
- pwalletMain->AddAccountingEntry(ae);
+ m_wallet.AddAccountingEntry(ae);
wtx.mapValue["comment"] = "z";
- pwalletMain->AddToWallet(wtx);
- vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]);
+ m_wallet.AddToWallet(wtx);
+ vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash()));
vpwtx[0]->nTimeReceived = (unsigned int)1333333335;
vpwtx[0]->nOrderPos = -1;
ae.nTime = 1333333336;
ae.strOtherAccount = "c";
- pwalletMain->AddAccountingEntry(ae);
+ m_wallet.AddAccountingEntry(ae);
- GetResults(pwalletMain.get(), results);
+ GetResults(m_wallet, results);
- BOOST_CHECK(pwalletMain->nOrderPosNext == 3);
+ BOOST_CHECK(m_wallet.nOrderPosNext == 3);
BOOST_CHECK(2 == results.size());
BOOST_CHECK(results[0].nTime == 1333333333);
BOOST_CHECK(results[0].strComment.empty());
@@ -65,13 +65,13 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333330;
ae.strOtherAccount = "d";
- ae.nOrderPos = pwalletMain->IncOrderPosNext();
- pwalletMain->AddAccountingEntry(ae);
+ ae.nOrderPos = m_wallet.IncOrderPosNext();
+ m_wallet.AddAccountingEntry(ae);
- GetResults(pwalletMain.get(), results);
+ GetResults(m_wallet, results);
BOOST_CHECK(results.size() == 3);
- BOOST_CHECK(pwalletMain->nOrderPosNext == 4);
+ BOOST_CHECK(m_wallet.nOrderPosNext == 4);
BOOST_CHECK(results[0].nTime == 1333333333);
BOOST_CHECK(1 == vpwtx[0]->nOrderPos);
BOOST_CHECK(results[2].nTime == 1333333336);
@@ -82,28 +82,28 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
wtx.mapValue["comment"] = "y";
{
CMutableTransaction tx(*wtx.tx);
- --tx.nLockTime; // Just to change the hash :)
+ ++tx.nLockTime; // Just to change the hash :)
wtx.SetTx(MakeTransactionRef(std::move(tx)));
}
- pwalletMain->AddToWallet(wtx);
- vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]);
+ m_wallet.AddToWallet(wtx);
+ vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash()));
vpwtx[1]->nTimeReceived = (unsigned int)1333333336;
wtx.mapValue["comment"] = "x";
{
CMutableTransaction tx(*wtx.tx);
- --tx.nLockTime; // Just to change the hash :)
+ ++tx.nLockTime; // Just to change the hash :)
wtx.SetTx(MakeTransactionRef(std::move(tx)));
}
- pwalletMain->AddToWallet(wtx);
- vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]);
+ m_wallet.AddToWallet(wtx);
+ vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash()));
vpwtx[2]->nTimeReceived = (unsigned int)1333333329;
vpwtx[2]->nOrderPos = -1;
- GetResults(pwalletMain.get(), results);
+ GetResults(m_wallet, results);
BOOST_CHECK(results.size() == 3);
- BOOST_CHECK(pwalletMain->nOrderPosNext == 6);
+ BOOST_CHECK(m_wallet.nOrderPosNext == 6);
BOOST_CHECK(0 == vpwtx[2]->nOrderPos);
BOOST_CHECK(results[1].nTime == 1333333333);
BOOST_CHECK(2 == vpwtx[0]->nOrderPos);
@@ -116,12 +116,12 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333334;
ae.strOtherAccount = "e";
ae.nOrderPos = -1;
- pwalletMain->AddAccountingEntry(ae);
+ m_wallet.AddAccountingEntry(ae);
- GetResults(pwalletMain.get(), results);
+ GetResults(m_wallet, results);
BOOST_CHECK(results.size() == 4);
- BOOST_CHECK(pwalletMain->nOrderPosNext == 7);
+ BOOST_CHECK(m_wallet.nOrderPosNext == 7);
BOOST_CHECK(0 == vpwtx[2]->nOrderPos);
BOOST_CHECK(results[1].nTime == 1333333333);
BOOST_CHECK(2 == vpwtx[0]->nOrderPos);
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
new file mode 100644
index 0000000000..184a8a3f1f
--- /dev/null
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -0,0 +1,575 @@
+// 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 "wallet/wallet.h"
+#include "wallet/coinselection.h"
+#include "wallet/coincontrol.h"
+#include "amount.h"
+#include "primitives/transaction.h"
+#include "random.h"
+#include "test/test_bitcoin.h"
+#include "wallet/test/wallet_test_fixture.h"
+
+#include <boost/test/unit_test.hpp>
+#include <random>
+
+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<std::unique_ptr<CWalletTx>> wtxn;
+
+typedef std::set<CInputCoin> CoinSet;
+
+static std::vector<COutput> vCoins;
+static CWallet testWallet("dummy", CWalletDBWrapper::CreateDummy());
+static CAmount balance = 0;
+
+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(0), 0);
+
+static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
+{
+ CMutableTransaction tx;
+ tx.vout.resize(nInput + 1);
+ tx.vout[nInput].nValue = nValue;
+ set.emplace_back(MakeTransactionRef(tx), nInput);
+}
+
+static void add_coin(const CAmount& 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(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
+{
+ balance += nValue;
+ static int nextLockTime = 0;
+ CMutableTransaction tx;
+ tx.nLockTime = nextLockTime++; // so all transactions get different hashes
+ tx.vout.resize(nInput + 1);
+ tx.vout[nInput].nValue = nValue;
+ 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);
+ }
+ std::unique_ptr<CWalletTx> wtx(new CWalletTx(&testWallet, MakeTransactionRef(std::move(tx))));
+ if (fIsFromMe)
+ {
+ wtx->fDebitCached = true;
+ wtx->nDebitCached = 1;
+ }
+ COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
+ vCoins.push_back(output);
+ testWallet.AddToWallet(*wtx.get());
+ wtxn.emplace_back(std::move(wtx));
+}
+
+static void empty_wallet(void)
+{
+ vCoins.clear();
+ wtxn.clear();
+ balance = 0;
+}
+
+static bool equal_sets(CoinSet a, CoinSet b)
+{
+ std::pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin());
+ return ret.first == a.end() && ret.second == b.end();
+}
+
+static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
+{
+ utxo_pool.clear();
+ CAmount target = 0;
+ for (int i = 0; i < utxos; ++i) {
+ target += (CAmount)1 << (utxos+i);
+ add_coin((CAmount)1 << (utxos+i), 2*i, utxo_pool);
+ add_coin(((CAmount)1 << (utxos+i)) + ((CAmount)1 << (utxos-1-i)), 2*i + 1, utxo_pool);
+ }
+ return target;
+}
+
+// Branch and bound coin selection tests
+BOOST_AUTO_TEST_CASE(bnb_search_test)
+{
+
+ LOCK(testWallet.cs_wallet);
+
+ // Setup
+ std::vector<CInputCoin> utxo_pool;
+ CoinSet selection;
+ CoinSet actual_selection;
+ CAmount value_ret = 0;
+ CAmount not_input_fees = 0;
+
+ /////////////////////////
+ // Known Outcome tests //
+ /////////////////////////
+ BOOST_TEST_MESSAGE("Testing known outcomes");
+
+ // Empty utxo pool
+ BOOST_CHECK(!SelectCoinsBnB(utxo_pool, 1 * CENT, 0.5 * CENT, 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(utxo_pool, 1 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ BOOST_CHECK(equal_sets(selection, actual_selection));
+ actual_selection.clear();
+ selection.clear();
+
+ // Select 2 Cent
+ add_coin(2 * CENT, 2, actual_selection);
+ BOOST_CHECK(SelectCoinsBnB(utxo_pool, 2 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ BOOST_CHECK(equal_sets(selection, actual_selection));
+ 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(utxo_pool, 5 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ BOOST_CHECK(equal_sets(selection, actual_selection));
+ actual_selection.clear();
+ selection.clear();
+
+ // Select 11 Cent, not possible
+ BOOST_CHECK(!SelectCoinsBnB(utxo_pool, 11 * CENT, 0.5 * CENT, 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(utxo_pool, 10 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ BOOST_CHECK(equal_sets(selection, actual_selection));
+ 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(utxo_pool, 10 * CENT, 5000, selection, value_ret, not_input_fees));
+
+ // Select 0.25 Cent, not possible
+ BOOST_CHECK(!SelectCoinsBnB(utxo_pool, 0.25 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ actual_selection.clear();
+ selection.clear();
+
+ // Iteration exhaustion test
+ CAmount target = make_hard_case(17, utxo_pool);
+ BOOST_CHECK(!SelectCoinsBnB(utxo_pool, target, 0, selection, value_ret, not_input_fees)); // Should exhaust
+ target = make_hard_case(14, utxo_pool);
+ BOOST_CHECK(SelectCoinsBnB(utxo_pool, target, 0, selection, value_ret, not_input_fees)); // Should not exhaust
+
+ // Test same value early bailout optimization
+ 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(utxo_pool, 30 * CENT, 5000, selection, value_ret, not_input_fees));
+
+ ////////////////////
+ // 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(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), 0);
+ CoinSet setCoinsRet;
+ CAmount nValueRet;
+ bool bnb_used;
+ empty_wallet();
+ add_coin(1);
+ vCoins.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
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+
+ // Make sure that we aren't using BnB when there are preset inputs
+ empty_wallet();
+ add_coin(5 * CENT);
+ add_coin(3 * CENT);
+ add_coin(2 * CENT);
+ CCoinControl coin_control;
+ coin_control.fAllowOtherInputs = true;
+ coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i));
+ BOOST_CHECK(testWallet.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)
+{
+ CoinSet setCoinsRet, setCoinsRet2;
+ CAmount 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, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+
+ add_coin(1*CENT, 4); // add a new 1 cent coin
+
+ // with a new 1 cent coin, we still can't find a mature 1 cent
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+
+ // but we can find a new 1 cent
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
+
+ add_coin(2*CENT); // add a mature 2 cent coin
+
+ // we can't make 3 cents of mature coins
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+
+ // we can make 3 cents of new coins
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
+
+ add_coin(5*CENT); // add a mature 5 cent coin,
+ add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses
+ add_coin(20*CENT); // and a mature 20 cent coin
+
+ // 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, 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, 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, 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, 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, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
+
+ // 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, 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, 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, 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( 6*CENT);
+ add_coin( 7*CENT);
+ add_coin( 8*CENT);
+ add_coin(20*CENT);
+ add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
+
+ // check that we have 71 and not 72
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, filter_confirmed, 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, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
+
+ add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
+
+ // 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, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
+
+ add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
+
+ // 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, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
+
+ // now try making 11 cents. we should get 5+6
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, filter_confirmed, 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( 1*COIN);
+ add_coin( 2*COIN);
+ add_coin( 3*COIN);
+ add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
+
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 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(MIN_CHANGE * 1 / 10);
+ add_coin(MIN_CHANGE * 2 / 10);
+ add_coin(MIN_CHANGE * 3 / 10);
+ add_coin(MIN_CHANGE * 4 / 10);
+ add_coin(MIN_CHANGE * 5 / 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, 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(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, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
+
+ // if we add more small coins:
+ add_coin(MIN_CHANGE * 6 / 10);
+ add_coin(MIN_CHANGE * 7 / 10);
+
+ // and try again to make 1.0 * MIN_CHANGE
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
+
+ // 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(50000 * COIN);
+
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
+
+ // 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(MIN_CHANGE * 5 / 10);
+ add_coin(MIN_CHANGE * 6 / 10);
+ add_coin(MIN_CHANGE * 7 / 10);
+ add_coin(1111 * MIN_CHANGE);
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
+ 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(MIN_CHANGE * 4 / 10);
+ add_coin(MIN_CHANGE * 6 / 10);
+ add_coin(MIN_CHANGE * 8 / 10);
+ add_coin(1111 * MIN_CHANGE);
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
+
+ // test avoiding small change
+ empty_wallet();
+ add_coin(MIN_CHANGE * 5 / 100);
+ add_coin(MIN_CHANGE * 1);
+ add_coin(MIN_CHANGE * 100);
+
+ // trying to make 100.01 from these three coins
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
+ 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(MIN_CHANGE * 9990 / 100, filter_confirmed, 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 (CAmount amt=1500; amt < COIN; amt*=10) {
+ 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(amt);
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ if (amt - 2000 < MIN_CHANGE) {
+ // needs more than one input:
+ uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt);
+ CAmount returnValue = amt * returnSize;
+ 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(COIN);
+
+ // 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, vCoins, setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, 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, vCoins, setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, 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(5 * CENT);
+ add_coin(10 * CENT);
+ add_coin(15 * CENT);
+ add_coin(20 * CENT);
+ add_coin(25 * CENT);
+
+ 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, vCoins, setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, 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;
+ CAmount nValueRet;
+ bool bnb_used;
+
+ LOCK(testWallet.cs_wallet);
+
+ empty_wallet();
+
+ // Test vValue sort order
+ for (int i = 0; i < 1000; i++)
+ add_coin(1000 * COIN);
+ add_coin(3 * COIN);
+
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, filter_standard, 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)
+{
+ // Random generator stuff
+ std::default_random_engine generator;
+ std::exponential_distribution<double> distribution (100);
+ FastRandomContext rand;
+
+ // Output stuff
+ CAmount out_value = 0;
+ CoinSet out_set;
+ CAmount target = 0;
+ bool bnb_used;
+
+ // Run this test 100 times
+ for (int i = 0; i < 100; ++i)
+ {
+ // Reset
+ out_value = 0;
+ target = 0;
+ out_set.clear();
+ empty_wallet();
+
+ // Make a wallet with 1000 exponentially distributed random inputs
+ for (int j = 0; j < 1000; ++j)
+ {
+ add_coin((CAmount)(distribution(generator)*10000000));
+ }
+
+ // Generate a random fee rate in the range of 100 - 400
+ CFeeRate rate(rand.randrange(300) + 100);
+
+ // Generate a random target value between 1000 and wallet balance
+ target = rand.randrange(balance - 1000) + 1000;
+
+ // Perform selection
+ CoinSelectionParams coin_selection_params_knapsack(false, 34, 148, CFeeRate(0), 0);
+ CoinSelectionParams coin_selection_params_bnb(true, 34, 148, CFeeRate(0), 0);
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(target, filter_standard, vCoins, out_set, out_value, coin_selection_params_bnb, bnb_used) ||
+ testWallet.SelectCoinsMinConf(target, filter_standard, 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/test/crypto_tests.cpp b/src/wallet/test/crypto_tests.cpp
index 89b2c4e796..d8c0cdf0f9 100644
--- a/src/wallet/test/crypto_tests.cpp
+++ b/src/wallet/test/crypto_tests.cpp
@@ -10,7 +10,7 @@
#include <boost/test/unit_test.hpp>
-BOOST_FIXTURE_TEST_SUITE(wallet_crypto, BasicTestingSetup)
+BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup)
class TestCrypter
{
diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp
index 7797f85f07..5c550742c8 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -6,27 +6,19 @@
#include <rpc/server.h>
#include <wallet/db.h>
+#include <wallet/wallet.h>
WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
- TestingSetup(chainName)
+ TestingSetup(chainName), m_wallet("mock", CWalletDBWrapper::CreateMock())
{
- bitdb.MakeMock();
-
bool fFirstRun;
- g_address_type = OUTPUT_TYPE_DEFAULT;
- g_change_type = OUTPUT_TYPE_DEFAULT;
- std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
- pwalletMain = MakeUnique<CWallet>(std::move(dbw));
- pwalletMain->LoadWallet(fFirstRun);
- RegisterValidationInterface(pwalletMain.get());
+ m_wallet.LoadWallet(fFirstRun);
+ RegisterValidationInterface(&m_wallet);
RegisterWalletRPCCommands(tableRPC);
}
WalletTestingSetup::~WalletTestingSetup()
{
- UnregisterValidationInterface(pwalletMain.get());
-
- bitdb.Flush(true);
- bitdb.Reset();
+ UnregisterValidationInterface(&m_wallet);
}
diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h
index e694f2a56a..b328b3543b 100644
--- a/src/wallet/test/wallet_test_fixture.h
+++ b/src/wallet/test/wallet_test_fixture.h
@@ -2,8 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#ifndef BITCOIN_WALLET_TEST_FIXTURE_H
-#define BITCOIN_WALLET_TEST_FIXTURE_H
+#ifndef BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H
+#define BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H
#include <test/test_bitcoin.h>
@@ -17,8 +17,7 @@ struct WalletTestingSetup: public TestingSetup {
explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
~WalletTestingSetup();
- std::unique_ptr<CWallet> pwalletMain;
+ CWallet m_wallet;
};
-#endif
-
+#endif // BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 12b84ac744..e93e0f1966 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -24,345 +24,8 @@ extern UniValue importmulti(const JSONRPCRequest& request);
extern UniValue dumpwallet(const JSONRPCRequest& request);
extern UniValue importwallet(const JSONRPCRequest& request);
-// 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<std::unique_ptr<CWalletTx>> wtxn;
-
-typedef std::set<CInputCoin> CoinSet;
-
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
-static const CWallet testWallet;
-static std::vector<COutput> vCoins;
-
-static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
-{
- static int nextLockTime = 0;
- CMutableTransaction tx;
- tx.nLockTime = nextLockTime++; // so all transactions get different hashes
- tx.vout.resize(nInput+1);
- tx.vout[nInput].nValue = nValue;
- 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);
- }
- std::unique_ptr<CWalletTx> wtx(new CWalletTx(&testWallet, MakeTransactionRef(std::move(tx))));
- if (fIsFromMe)
- {
- wtx->fDebitCached = true;
- wtx->nDebitCached = 1;
- }
- COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
- vCoins.push_back(output);
- wtxn.emplace_back(std::move(wtx));
-}
-
-static void empty_wallet(void)
-{
- vCoins.clear();
- wtxn.clear();
-}
-
-static bool equal_sets(CoinSet a, CoinSet b)
-{
- std::pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin());
- return ret.first == a.end() && ret.second == b.end();
-}
-
-BOOST_AUTO_TEST_CASE(coin_selection_tests)
-{
- CoinSet setCoinsRet, setCoinsRet2;
- CAmount nValueRet;
-
- 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, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
-
- add_coin(1*CENT, 4); // add a new 1 cent coin
-
- // with a new 1 cent coin, we still can't find a mature 1 cent
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
-
- // but we can find a new 1 cent
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
-
- add_coin(2*CENT); // add a mature 2 cent coin
-
- // we can't make 3 cents of mature coins
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
-
- // we can make 3 cents of new coins
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
-
- add_coin(5*CENT); // add a mature 5 cent coin,
- add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses
- add_coin(20*CENT); // and a mature 20 cent coin
-
- // 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, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
- // 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, 6, 6, 0, vCoins, setCoinsRet, nValueRet));
- // but we can make 37 cents if we accept new coins from ourself
- BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
- // and we can make 38 cents if we accept all new coins
- BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- 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, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
-
- // 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, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- 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, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- 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, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- 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( 6*CENT);
- add_coin( 7*CENT);
- add_coin( 8*CENT);
- add_coin(20*CENT);
- add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
-
- // check that we have 71 and not 72
- BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
-
- // 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, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
-
- add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
-
- // 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, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
-
- add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
-
- // 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, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
-
- // now try making 11 cents. we should get 5+6
- BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
-
- // check that the smallest bigger coin is used
- add_coin( 1*COIN);
- add_coin( 2*COIN);
- add_coin( 3*COIN);
- add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
- BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
-
- BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 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(MIN_CHANGE * 1 / 10);
- add_coin(MIN_CHANGE * 2 / 10);
- add_coin(MIN_CHANGE * 3 / 10);
- add_coin(MIN_CHANGE * 4 / 10);
- add_coin(MIN_CHANGE * 5 / 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, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
-
- // but if we add a bigger coin, small change is avoided
- add_coin(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, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
-
- // if we add more small coins:
- add_coin(MIN_CHANGE * 6 / 10);
- add_coin(MIN_CHANGE * 7 / 10);
-
- // and try again to make 1.0 * MIN_CHANGE
- BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
-
- // 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(50000 * COIN);
-
- BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
-
- // 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(MIN_CHANGE * 5 / 10);
- add_coin(MIN_CHANGE * 6 / 10);
- add_coin(MIN_CHANGE * 7 / 10);
- add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
- 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(MIN_CHANGE * 4 / 10);
- add_coin(MIN_CHANGE * 6 / 10);
- add_coin(MIN_CHANGE * 8 / 10);
- add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
-
- // test avoiding small change
- empty_wallet();
- add_coin(MIN_CHANGE * 5 / 100);
- add_coin(MIN_CHANGE * 1);
- add_coin(MIN_CHANGE * 100);
-
- // trying to make 100.01 from these three coins
- BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
- 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(MIN_CHANGE * 9990 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
-
- // test with many inputs
- for (CAmount amt=1500; amt < COIN; amt*=10) {
- 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(amt);
- BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
- if (amt - 2000 < MIN_CHANGE) {
- // needs more than one input:
- uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt);
- CAmount returnValue = amt * returnSize;
- 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(COIN);
-
- // 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, 1, 6, 0, vCoins, setCoinsRet , nValueRet));
- BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, setCoinsRet2, nValueRet));
- 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, 1, 6, 0, vCoins, setCoinsRet , nValueRet));
- BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, 1, 6, 0, vCoins, setCoinsRet2, nValueRet));
- 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(5 * CENT);
- add_coin(10 * CENT);
- add_coin(15 * CENT);
- add_coin(20 * CENT);
- add_coin(25 * CENT);
-
- 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, 1, 6, 0, vCoins, setCoinsRet , nValueRet));
- BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, 1, 6, 0, vCoins, setCoinsRet2, nValueRet));
- if (equal_sets(setCoinsRet, setCoinsRet2))
- fails++;
- }
- BOOST_CHECK_NE(fails, RANDOM_REPEATS);
- }
- }
- empty_wallet();
-}
-
-BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
-{
- CoinSet setCoinsRet;
- CAmount nValueRet;
-
- LOCK(testWallet.cs_wallet);
-
- empty_wallet();
-
- // Test vValue sort order
- for (int i = 0; i < 1000; i++)
- add_coin(1000 * COIN);
- add_coin(3 * COIN);
-
- BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
-
- empty_wallet();
-}
-
static void AddKey(CWallet& wallet, const CKey& key)
{
LOCK(wallet.cs_wallet);
@@ -383,7 +46,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
- CWallet wallet;
+ CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@@ -398,7 +61,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// Verify ScanForWalletTransactions only picks transactions in the new block
// file.
{
- CWallet wallet;
+ CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@@ -410,7 +73,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// before the missing block, and success for a key whose creation time is
// after.
{
- CWallet wallet;
+ CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
vpwallets.insert(vpwallets.begin(), &wallet);
UniValue keys;
keys.setArray();
@@ -469,7 +132,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Import key into wallet and call dumpwallet to create backup file.
{
- CWallet wallet;
+ CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
LOCK(wallet.cs_wallet);
wallet.mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
@@ -484,7 +147,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
// were scanned, and no prior blocks were scanned.
{
- CWallet wallet;
+ CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
JSONRPCRequest request;
request.params.setArray();
@@ -514,7 +177,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// debit functions.
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
{
- CWallet wallet;
+ CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
CWalletTx wtx(&wallet, MakeTransactionRef(coinbaseTxns.back()));
LOCK2(cs_main, wallet.cs_wallet);
wtx.hashBlock = chainActive.Tip()->GetBlockHash();
@@ -551,7 +214,10 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
if (block) {
wtx.SetMerkleBranch(block, 0);
}
- wallet.AddToWallet(wtx);
+ {
+ LOCK(cs_main);
+ wallet.AddToWallet(wtx);
+ }
LOCK(wallet.cs_wallet);
return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
}
@@ -560,27 +226,25 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
// expanded to cover more corner cases of smart time logic.
BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
{
- CWallet wallet;
-
// New transaction should use clock time if lower than block time.
- BOOST_CHECK_EQUAL(AddTx(wallet, 1, 100, 120), 100);
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 100, 120), 100);
// Test that updating existing transaction does not change smart time.
- BOOST_CHECK_EQUAL(AddTx(wallet, 1, 200, 220), 100);
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 200, 220), 100);
// New transaction should use clock time if there's no block time.
- BOOST_CHECK_EQUAL(AddTx(wallet, 2, 300, 0), 300);
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 2, 300, 0), 300);
// New transaction should use block time if lower than clock time.
- BOOST_CHECK_EQUAL(AddTx(wallet, 3, 420, 400), 400);
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 3, 420, 400), 400);
// New transaction should use latest entry time if higher than
// min(block time, clock time).
- BOOST_CHECK_EQUAL(AddTx(wallet, 4, 500, 390), 400);
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 4, 500, 390), 400);
// If there are future entries, new transaction should use time of the
// newest entry that is no more than 300 seconds ahead of the clock time.
- BOOST_CHECK_EQUAL(AddTx(wallet, 5, 50, 600), 300);
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 5, 50, 600), 300);
// Reset mock time for other tests.
SetMockTime(0);
@@ -589,12 +253,12 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
{
CTxDestination dest = CKeyID();
- LOCK(pwalletMain->cs_wallet);
- pwalletMain->AddDestData(dest, "misc", "val_misc");
- pwalletMain->AddDestData(dest, "rr0", "val_rr0");
- pwalletMain->AddDestData(dest, "rr1", "val_rr1");
+ LOCK(m_wallet.cs_wallet);
+ m_wallet.AddDestData(dest, "misc", "val_misc");
+ m_wallet.AddDestData(dest, "rr0", "val_rr0");
+ m_wallet.AddDestData(dest, "rr1", "val_rr1");
- auto values = pwalletMain->GetDestValues("rr");
+ auto values = m_wallet.GetDestValues("rr");
BOOST_CHECK_EQUAL(values.size(), 2);
BOOST_CHECK_EQUAL(values[0], "val_rr0");
BOOST_CHECK_EQUAL(values[1], "val_rr1");
@@ -606,10 +270,7 @@ public:
ListCoinsTestingSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
- ::bitdb.MakeMock();
- g_address_type = OUTPUT_TYPE_DEFAULT;
- g_change_type = OUTPUT_TYPE_DEFAULT;
- wallet.reset(new CWallet(std::unique_ptr<CWalletDBWrapper>(new CWalletDBWrapper(&bitdb, "wallet_test.dat"))));
+ wallet = MakeUnique<CWallet>("mock", CWalletDBWrapper::CreateMock());
bool firstRun;
wallet->LoadWallet(firstRun);
AddKey(*wallet, coinbaseKey);
@@ -621,29 +282,27 @@ public:
~ListCoinsTestingSetup()
{
wallet.reset();
- ::bitdb.Flush(true);
- ::bitdb.Reset();
}
CWalletTx& AddTx(CRecipient recipient)
{
- CWalletTx wtx;
+ CTransactionRef tx;
CReserveKey reservekey(wallet.get());
CAmount fee;
int changePos = -1;
std::string error;
CCoinControl dummy;
- BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error, dummy));
+ BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, reservekey, fee, changePos, error, dummy));
CValidationState state;
- BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state));
+ BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, {}, reservekey, nullptr, state));
CMutableTransaction blocktx;
{
LOCK(wallet->cs_wallet);
- blocktx = CMutableTransaction(*wallet->mapWallet.at(wtx.GetHash()).tx);
+ blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetHash()).tx);
}
CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
LOCK(wallet->cs_wallet);
- auto it = wallet->mapWallet.find(wtx.GetHash());
+ auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
it->second.SetMerkleBranch(chainActive.Tip(), 1);
return it->second;
@@ -677,18 +336,24 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2);
// Lock both coins. Confirm number of available coins drops to 0.
- std::vector<COutput> available;
- wallet->AvailableCoins(available);
- BOOST_CHECK_EQUAL(available.size(), 2);
+ {
+ LOCK2(cs_main, wallet->cs_wallet);
+ std::vector<COutput> available;
+ wallet->AvailableCoins(available);
+ BOOST_CHECK_EQUAL(available.size(), 2);
+ }
for (const auto& group : list) {
for (const auto& coin : group.second) {
LOCK(wallet->cs_wallet);
wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
}
}
- wallet->AvailableCoins(available);
- BOOST_CHECK_EQUAL(available.size(), 0);
-
+ {
+ LOCK2(cs_main, wallet->cs_wallet);
+ std::vector<COutput> available;
+ wallet->AvailableCoins(available);
+ BOOST_CHECK_EQUAL(available.size(), 0);
+ }
// Confirm ListCoins still returns same result as before, despite coins
// being locked.
list = wallet->ListCoins();