Changeset View
Standalone View
src/wallet/test/wallet_tests.cpp
// Copyright (c) 2012-2016 The Bitcoin Core developers | // Copyright (c) 2012-2016 The Bitcoin Core developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include "wallet/wallet.h" | #include "wallet/wallet.h" | ||||
#include "chainparams.h" | |||||
#include "config.h" | #include "config.h" | ||||
#include "rpc/server.h" | #include "rpc/server.h" | ||||
#include "test/test_bitcoin.h" | #include "test/test_bitcoin.h" | ||||
#include "validation.h" | #include "validation.h" | ||||
#include "wallet/rpcdump.h" | #include "wallet/rpcdump.h" | ||||
#include "wallet/test/wallet_test_fixture.h" | #include "wallet/test/wallet_test_fixture.h" | ||||
#include <boost/test/unit_test.hpp> | #include <boost/test/unit_test.hpp> | ||||
Show All 16 Lines | |||||
#define RANDOM_REPEATS 5 | #define RANDOM_REPEATS 5 | ||||
std::vector<std::unique_ptr<CWalletTx>> wtxn; | std::vector<std::unique_ptr<CWalletTx>> wtxn; | ||||
typedef std::set<std::pair<const CWalletTx *, unsigned int>> CoinSet; | typedef std::set<std::pair<const CWalletTx *, unsigned int>> CoinSet; | ||||
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) | BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) | ||||
static const CWallet wallet; | // Critical section is used to prevent concurrent execution of | ||||
// tests in this fixture | |||||
static CCriticalSection walletCriticalSection; | |||||
jasonbcox: If you init a new wallet in every test, is this necessary? | |||||
matra774AuthorUnsubmitted Not Done Inline ActionsBoost can execute some test in parallel. Previous version locked on wallet.cs_wallet. Since wallet was a global variable, that resulted in global lock. My commit preserves this behavior (global lock). Is it necessary? I am not sure - the code inside a unit test can access some other global resources that requires the lock. If you take a look at other test cases (for example BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) ), you can see that we have different locking patterns:
There could be some other lurking problems regarding test code and locks. For example :
If those two tests are executed at the same time, one of them should probably fail.. As I said, I wanted to preserve existing (global) locking mechanism, which actually serializes test execution - better safe then sorry :-) What do you propose: matra774: Boost can execute some test in parallel. Previous version locked on wallet.cs_wallet. Since… | |||||
jasonbcoxUnsubmitted Not Done Inline ActionsOk, I wasn't aware this was an issue. Seeing as this preserves behavior, I'm ok with it. However, I've opened up a ticket (T235) to investigate this further, because we (ideally) shouldn't be utilizing different locking styles for different tests. jasonbcox: Ok, I wasn't aware this was an issue. Seeing as this preserves behavior, I'm ok with it. | |||||
static std::vector<COutput> vCoins; | static std::vector<COutput> vCoins; | ||||
static void add_coin(const Amount nValue, int nAge = 6 * 24, | static void add_coin(const CWallet &wallet, const Amount nValue, | ||||
bool fIsFromMe = false, int nInput = 0) { | int nAge = 6 * 24, bool fIsFromMe = false, | ||||
int nInput = 0) { | |||||
static int nextLockTime = 0; | static int nextLockTime = 0; | ||||
CMutableTransaction tx; | CMutableTransaction tx; | ||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes | tx.nLockTime = nextLockTime++; // so all transactions get different hashes | ||||
tx.vout.resize(nInput + 1); | tx.vout.resize(nInput + 1); | ||||
tx.vout[nInput].nValue = nValue; | tx.vout[nInput].nValue = nValue; | ||||
if (fIsFromMe) { | if (fIsFromMe) { | ||||
// IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if | // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if | ||||
// vin.empty(), so stop vin being empty, and cache a non-zero Debit to | // vin.empty(), so stop vin being empty, and cache a non-zero Debit to | ||||
Show All 22 Lines | std::pair<CoinSet::iterator, CoinSet::iterator> ret = | ||||
mismatch(a.begin(), a.end(), b.begin()); | mismatch(a.begin(), a.end(), b.begin()); | ||||
return ret.first == a.end() && ret.second == b.end(); | return ret.first == a.end() && ret.second == b.end(); | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(coin_selection_tests) { | BOOST_AUTO_TEST_CASE(coin_selection_tests) { | ||||
CoinSet setCoinsRet, setCoinsRet2; | CoinSet setCoinsRet, setCoinsRet2; | ||||
Amount nValueRet; | Amount nValueRet; | ||||
LOCK(wallet.cs_wallet); | const CWallet wallet(Params()); | ||||
LOCK(walletCriticalSection); | |||||
// test multiple times to allow for differences in the shuffle order | // test multiple times to allow for differences in the shuffle order | ||||
for (int i = 0; i < RUN_TESTS; i++) { | for (int i = 0; i < RUN_TESTS; i++) { | ||||
empty_wallet(); | empty_wallet(); | ||||
// with an empty wallet we can't even pay one cent | // with an empty wallet we can't even pay one cent | ||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(1 * CENT, 1, 6, 0, vCoins, | BOOST_CHECK(!wallet.SelectCoinsMinConf(1 * CENT, 1, 6, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// add a new 1 cent coin | // add a new 1 cent coin | ||||
add_coin(1 * CENT, 4); | add_coin(wallet, 1 * CENT, 4); | ||||
// with a new 1 cent coin, we still can't find a mature 1 cent | // with a new 1 cent coin, we still can't find a mature 1 cent | ||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(1 * CENT, 1, 6, 0, vCoins, | BOOST_CHECK(!wallet.SelectCoinsMinConf(1 * CENT, 1, 6, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// but we can find a new 1 cent | // but we can find a new 1 cent | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(1 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(1 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); | BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); | ||||
// add a mature 2 cent coin | // add a mature 2 cent coin | ||||
add_coin(2 * CENT); | add_coin(wallet, 2 * CENT); | ||||
// we can't make 3 cents of mature coins | // we can't make 3 cents of mature coins | ||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(3 * CENT, 1, 6, 0, vCoins, | BOOST_CHECK(!wallet.SelectCoinsMinConf(3 * CENT, 1, 6, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we can make 3 cents of new coins | // we can make 3 cents of new coins | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(3 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(3 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); | BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); | ||||
// add a mature 5 cent coin, | // add a mature 5 cent coin, | ||||
add_coin(5 * CENT); | add_coin(wallet, 5 * CENT); | ||||
// a new 10 cent coin sent from one of our own addresses | // a new 10 cent coin sent from one of our own addresses | ||||
add_coin(10 * CENT, 3, true); | add_coin(wallet, 10 * CENT, 3, true); | ||||
// and a mature 20 cent coin | // and a mature 20 cent coin | ||||
add_coin(20 * CENT); | add_coin(wallet, 20 * CENT); | ||||
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: | // now we have new: 1+10=11 (of which 10 was self-sent), and mature: | ||||
// 2+5+20=27. total = 38 | // 2+5+20=27. total = 38 | ||||
// we can't make 38 cents only if we disallow new coins: | // we can't make 38 cents only if we disallow new coins: | ||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 1, 6, 0, vCoins, | BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 1, 6, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we can't even make 37 cents if we don't allow new coins even if | // we can't even make 37 cents if we don't allow new coins even if | ||||
Show All 38 Lines | for (int i = 0; i < RUN_TESTS; i++) { | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); | BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | ||||
// now clear out the wallet and start again to test choosing between | // now clear out the wallet and start again to test choosing between | ||||
// subsets of smaller coins and the next biggest coin | // subsets of smaller coins and the next biggest coin | ||||
empty_wallet(); | empty_wallet(); | ||||
add_coin(6 * CENT); | add_coin(wallet, 6 * CENT); | ||||
add_coin(7 * CENT); | add_coin(wallet, 7 * CENT); | ||||
add_coin(8 * CENT); | add_coin(wallet, 8 * CENT); | ||||
add_coin(20 * CENT); | add_coin(wallet, 20 * CENT); | ||||
// now we have 6+7+8+20+30 = 71 cents total | // now we have 6+7+8+20+30 = 71 cents total | ||||
add_coin(30 * CENT); | add_coin(wallet, 30 * CENT); | ||||
// check that we have 71 and not 72 | // check that we have 71 and not 72 | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(71 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(71 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// now try making 16 cents. the best smaller coins can do is 6+7+8 = | // 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 | // 21; not as good at the next biggest coin, 20 | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we should get 20 in one coin | // we should get 20 in one coin | ||||
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); | BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | ||||
// now we have 5+6+7+8+20+30 = 75 cents total | // now we have 5+6+7+8+20+30 = 75 cents total | ||||
add_coin(5 * CENT); | add_coin(wallet, 5 * CENT); | ||||
// now if we try making 16 cents again, the smaller coins can make 5+6+7 | // 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 | // = 18 cents, better than the next biggest coin, 20 | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we should get 18 in 3 coins | // we should get 18 in 3 coins | ||||
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); | BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); | ||||
// now we have 5+6+7+8+18+20+30 | // now we have 5+6+7+8+18+20+30 | ||||
add_coin(18 * CENT); | add_coin(wallet, 18 * CENT); | ||||
// and now if we try making 16 cents again, the smaller coins can make | // 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 | // 5+6+7 = 18 cents, the same as the next biggest coin, 18 | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we should get 18 in 1 coin | // we should get 18 in 1 coin | ||||
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); | BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); | ||||
// because in the event of a tie, the biggest coin wins | // because in the event of a tie, the biggest coin wins | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | ||||
// now try making 11 cents. we should get 5+6 | // now try making 11 cents. we should get 5+6 | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(11 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(11 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); | BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); | ||||
// check that the smallest bigger coin is used | // check that the smallest bigger coin is used | ||||
add_coin(1 * COIN); | add_coin(wallet, 1 * COIN); | ||||
add_coin(2 * COIN); | add_coin(wallet, 2 * COIN); | ||||
add_coin(3 * COIN); | add_coin(wallet, 3 * COIN); | ||||
// now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents | // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents | ||||
add_coin(4 * COIN); | add_coin(wallet, 4 * COIN); | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(95 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(95 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we should get 1 BCH in 1 coin | // we should get 1 BCH in 1 coin | ||||
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); | BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(195 * CENT, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(195 * CENT, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we should get 2 BCH in 1 coin | // we should get 2 BCH in 1 coin | ||||
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); | BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | ||||
// empty the wallet and start again, now with fractions of a cent, to | // empty the wallet and start again, now with fractions of a cent, to | ||||
// test small change avoidance | // test small change avoidance | ||||
empty_wallet(); | empty_wallet(); | ||||
add_coin(1 * MIN_CHANGE / 10); | add_coin(wallet, 1 * MIN_CHANGE / 10); | ||||
add_coin(2 * MIN_CHANGE / 10); | add_coin(wallet, 2 * MIN_CHANGE / 10); | ||||
add_coin(3 * MIN_CHANGE / 10); | add_coin(wallet, 3 * MIN_CHANGE / 10); | ||||
add_coin(4 * MIN_CHANGE / 10); | add_coin(wallet, 4 * MIN_CHANGE / 10); | ||||
add_coin(5 * MIN_CHANGE / 10); | add_coin(wallet, 5 * MIN_CHANGE / 10); | ||||
// try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE we'll get change | // 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 | // smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE | ||||
// exactly | // exactly | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); | BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); | ||||
// but if we add a bigger coin, small change is avoided | // but if we add a bigger coin, small change is avoided | ||||
add_coin(1111 * MIN_CHANGE); | add_coin(wallet, 1111 * MIN_CHANGE); | ||||
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 | // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we should get the exact amount | // we should get the exact amount | ||||
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); | BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); | ||||
// if we add more small coins: | // if we add more small coins: | ||||
add_coin(6 * MIN_CHANGE / 10); | add_coin(wallet, 6 * MIN_CHANGE / 10); | ||||
add_coin(7 * MIN_CHANGE / 10); | add_coin(wallet, 7 * MIN_CHANGE / 10); | ||||
// and try again to make 1.0 * MIN_CHANGE | // and try again to make 1.0 * MIN_CHANGE | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we should get the exact amount | // we should get the exact amount | ||||
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); | BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); | ||||
// run the 'mtgox' test (see | // run the 'mtgox' test (see | ||||
// http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) | // http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) | ||||
// they tried to consolidate 10 50k coins into one 500k coin, and ended | // they tried to consolidate 10 50k coins into one 500k coin, and ended | ||||
// up with 50k in change | // up with 50k in change | ||||
empty_wallet(); | empty_wallet(); | ||||
for (int j = 0; j < 20; j++) { | for (int j = 0; j < 20; j++) { | ||||
add_coin(50000 * COIN); | add_coin(wallet, 50000 * COIN); | ||||
} | } | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(500000 * COIN, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(500000 * COIN, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we should get the exact amount | // we should get the exact amount | ||||
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); | BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); | ||||
// in ten coins | // in ten coins | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); | ||||
// if there's not enough in the smaller coins to make at least 1 * | // 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 | // MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0), we need to try finding an | ||||
// exact subset anyway | // exact subset anyway | ||||
// sometimes it will fail, and so we use the next biggest coin: | // sometimes it will fail, and so we use the next biggest coin: | ||||
empty_wallet(); | empty_wallet(); | ||||
add_coin(5 * MIN_CHANGE / 10); | add_coin(wallet, 5 * MIN_CHANGE / 10); | ||||
add_coin(6 * MIN_CHANGE / 10); | add_coin(wallet, 6 * MIN_CHANGE / 10); | ||||
add_coin(7 * MIN_CHANGE / 10); | add_coin(wallet, 7 * MIN_CHANGE / 10); | ||||
add_coin(1111 * MIN_CHANGE); | add_coin(wallet, 1111 * MIN_CHANGE); | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we get the bigger coin | // we get the bigger coin | ||||
BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); | BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | ||||
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = | // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = | ||||
// 1.0) | // 1.0) | ||||
empty_wallet(); | empty_wallet(); | ||||
add_coin(4 * MIN_CHANGE / 10); | add_coin(wallet, 4 * MIN_CHANGE / 10); | ||||
add_coin(6 * MIN_CHANGE / 10); | add_coin(wallet, 6 * MIN_CHANGE / 10); | ||||
add_coin(8 * MIN_CHANGE / 10); | add_coin(wallet, 8 * MIN_CHANGE / 10); | ||||
add_coin(1111 * MIN_CHANGE); | add_coin(wallet, 1111 * MIN_CHANGE); | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
// we should get the exact amount | // we should get the exact amount | ||||
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); | BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); | ||||
// in two coins 0.4+0.6 | // in two coins 0.4+0.6 | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); | ||||
// test avoiding small change | // test avoiding small change | ||||
empty_wallet(); | empty_wallet(); | ||||
add_coin(5 * MIN_CHANGE / 100); | add_coin(wallet, 5 * MIN_CHANGE / 100); | ||||
add_coin(1 * MIN_CHANGE); | add_coin(wallet, 1 * MIN_CHANGE); | ||||
add_coin(100 * MIN_CHANGE); | add_coin(wallet, 100 * MIN_CHANGE); | ||||
// trying to make 100.01 from these three coins | // trying to make 100.01 from these three coins | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(10001 * MIN_CHANGE / 100, 1, 1, 0, | BOOST_CHECK(wallet.SelectCoinsMinConf(10001 * MIN_CHANGE / 100, 1, 1, 0, | ||||
vCoins, setCoinsRet, nValueRet)); | vCoins, setCoinsRet, nValueRet)); | ||||
// we should get all coins | // we should get all coins | ||||
BOOST_CHECK_EQUAL(nValueRet, 10105 * MIN_CHANGE / 100); | BOOST_CHECK_EQUAL(nValueRet, 10105 * MIN_CHANGE / 100); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); | ||||
// but if we try to make 99.9, we should take the bigger of the two | // but if we try to make 99.9, we should take the bigger of the two | ||||
// small coins to avoid small change | // small coins to avoid small change | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(9990 * MIN_CHANGE / 100, 1, 1, 0, | BOOST_CHECK(wallet.SelectCoinsMinConf(9990 * MIN_CHANGE / 100, 1, 1, 0, | ||||
vCoins, setCoinsRet, nValueRet)); | vCoins, setCoinsRet, nValueRet)); | ||||
BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE); | BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); | ||||
// test with many inputs | // test with many inputs | ||||
for (Amount amt = Amount(1500); amt < COIN; amt = 10 * amt) { | for (Amount amt = Amount(1500); amt < COIN; amt = 10 * amt) { | ||||
empty_wallet(); | empty_wallet(); | ||||
// Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 | // Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 | ||||
// bytes per input) | // bytes per input) | ||||
for (uint16_t j = 0; j < 676; j++) { | for (uint16_t j = 0; j < 676; j++) { | ||||
add_coin(amt); | add_coin(wallet, amt); | ||||
} | } | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(Amount(2000), 1, 1, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(Amount(2000), 1, 1, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
if (amt - Amount(2000) < MIN_CHANGE) { | if (amt - Amount(2000) < MIN_CHANGE) { | ||||
// needs more than one input: | // needs more than one input: | ||||
uint16_t returnSize = std::ceil( | uint16_t returnSize = std::ceil( | ||||
(2000.0 + MIN_CHANGE.GetSatoshis()) / amt.GetSatoshis()); | (2000.0 + MIN_CHANGE.GetSatoshis()) / amt.GetSatoshis()); | ||||
Amount returnValue = returnSize * amt; | Amount returnValue = returnSize * amt; | ||||
BOOST_CHECK_EQUAL(nValueRet, returnValue); | BOOST_CHECK_EQUAL(nValueRet, returnValue); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize); | BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize); | ||||
} else { | } else { | ||||
// one input is sufficient: | // one input is sufficient: | ||||
BOOST_CHECK_EQUAL(nValueRet, amt); | BOOST_CHECK_EQUAL(nValueRet, amt); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); | ||||
} | } | ||||
} | } | ||||
// test randomness | // test randomness | ||||
{ | { | ||||
empty_wallet(); | empty_wallet(); | ||||
for (int i2 = 0; i2 < 100; i2++) { | for (int i2 = 0; i2 < 100; i2++) { | ||||
add_coin(COIN); | add_coin(wallet, COIN); | ||||
} | } | ||||
// picking 50 from 100 coins doesn't depend on the shuffle, but does | // picking 50 from 100 coins doesn't depend on the shuffle, but does | ||||
// depend on randomness in the stochastic approximation code | // depend on randomness in the stochastic approximation code | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, | ||||
setCoinsRet2, nValueRet)); | setCoinsRet2, nValueRet)); | ||||
Show All 10 Lines | for (int i = 0; i < RUN_TESTS; i++) { | ||||
setCoinsRet2, nValueRet)); | setCoinsRet2, nValueRet)); | ||||
if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; | if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; | ||||
} | } | ||||
BOOST_CHECK_NE(fails, RANDOM_REPEATS); | BOOST_CHECK_NE(fails, RANDOM_REPEATS); | ||||
// add 75 cents in small change. not enough to make 90 cents, then | // add 75 cents in small change. not enough to make 90 cents, then | ||||
// try making 90 cents. there are multiple competing "smallest | // try making 90 cents. there are multiple competing "smallest | ||||
// bigger" coins, one of which should be picked at random | // bigger" coins, one of which should be picked at random | ||||
add_coin(5 * CENT); | add_coin(wallet, 5 * CENT); | ||||
add_coin(10 * CENT); | add_coin(wallet, 10 * CENT); | ||||
add_coin(15 * CENT); | add_coin(wallet, 15 * CENT); | ||||
add_coin(20 * CENT); | add_coin(wallet, 20 * CENT); | ||||
add_coin(25 * CENT); | add_coin(wallet, 25 * CENT); | ||||
fails = 0; | fails = 0; | ||||
for (int j = 0; j < RANDOM_REPEATS; j++) { | for (int j = 0; j < RANDOM_REPEATS; j++) { | ||||
// selecting 1 from 100 identical coins depends on the shuffle; | // selecting 1 from 100 identical coins depends on the shuffle; | ||||
// this test will fail 1% of the time run the test | // this test will fail 1% of the time run the test | ||||
// RANDOM_REPEATS times and only complain if all of them fail | // RANDOM_REPEATS times and only complain if all of them fail | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf( | BOOST_CHECK(wallet.SelectCoinsMinConf( | ||||
90 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); | 90 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf( | BOOST_CHECK(wallet.SelectCoinsMinConf( | ||||
90 * CENT, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); | 90 * CENT, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); | ||||
if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; | if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; | ||||
} | } | ||||
BOOST_CHECK_NE(fails, RANDOM_REPEATS); | BOOST_CHECK_NE(fails, RANDOM_REPEATS); | ||||
} | } | ||||
} | } | ||||
empty_wallet(); | empty_wallet(); | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(ApproximateBestSubset) { | BOOST_AUTO_TEST_CASE(ApproximateBestSubset) { | ||||
CoinSet setCoinsRet; | CoinSet setCoinsRet; | ||||
Amount nValueRet; | Amount nValueRet; | ||||
LOCK(wallet.cs_wallet); | const CWallet wallet(Params()); | ||||
LOCK(walletCriticalSection); | |||||
empty_wallet(); | empty_wallet(); | ||||
// Test vValue sort order | // Test vValue sort order | ||||
for (int i = 0; i < 1000; i++) { | for (int i = 0; i < 1000; i++) { | ||||
add_coin(1000 * COIN); | add_coin(wallet, 1000 * COIN); | ||||
} | } | ||||
add_coin(3 * COIN); | add_coin(wallet, 3 * COIN); | ||||
BOOST_CHECK(wallet.SelectCoinsMinConf(1003 * COIN, 1, 6, 0, vCoins, | BOOST_CHECK(wallet.SelectCoinsMinConf(1003 * COIN, 1, 6, 0, vCoins, | ||||
setCoinsRet, nValueRet)); | setCoinsRet, nValueRet)); | ||||
BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN); | BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN); | ||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); | BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); | ||||
empty_wallet(); | empty_wallet(); | ||||
} | } | ||||
BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { | BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
// Cap last block file size, and mine new block in a new block file. | // Cap last block file size, and mine new block in a new block file. | ||||
CBlockIndex *oldTip = chainActive.Tip(); | CBlockIndex *oldTip = chainActive.Tip(); | ||||
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; | GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; | ||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); | CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); | ||||
CBlockIndex *newTip = chainActive.Tip(); | CBlockIndex *newTip = chainActive.Tip(); | ||||
// Verify ScanForWalletTransactions picks up transactions in both the old | // Verify ScanForWalletTransactions picks up transactions in both the old | ||||
// and new block files. | // and new block files. | ||||
{ | { | ||||
CWallet wallet; | CWallet wallet(Params()); | ||||
LOCK(wallet.cs_wallet); | LOCK(wallet.cs_wallet); | ||||
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); | wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); | ||||
BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip)); | BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip)); | ||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); | BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); | ||||
} | } | ||||
// Prune the older block file. | // Prune the older block file. | ||||
PruneOneBlockFile(oldTip->GetBlockPos().nFile); | PruneOneBlockFile(oldTip->GetBlockPos().nFile); | ||||
UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); | UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); | ||||
// Verify ScanForWalletTransactions only picks transactions in the new block | // Verify ScanForWalletTransactions only picks transactions in the new block | ||||
// file. | // file. | ||||
{ | { | ||||
CWallet wallet; | CWallet wallet(Params()); | ||||
LOCK(wallet.cs_wallet); | LOCK(wallet.cs_wallet); | ||||
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); | wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); | ||||
BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(oldTip)); | BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(oldTip)); | ||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); | BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); | ||||
} | } | ||||
// Verify importmulti RPC returns failure for a key whose creation time is | // Verify importmulti RPC returns failure for a key whose creation time is | ||||
// before the missing block, and success for a key whose creation time is | // before the missing block, and success for a key whose creation time is | ||||
// after. | // after. | ||||
{ | { | ||||
CWallet wallet; | CWallet wallet(Params()); | ||||
vpwallets.insert(vpwallets.begin(), &wallet); | vpwallets.insert(vpwallets.begin(), &wallet); | ||||
UniValue keys; | UniValue keys; | ||||
keys.setArray(); | keys.setArray(); | ||||
UniValue key; | UniValue key; | ||||
key.setObject(); | key.setObject(); | ||||
key.pushKV("scriptPubKey", | key.pushKV("scriptPubKey", | ||||
HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); | HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); | ||||
key.pushKV("timestamp", 0); | key.pushKV("timestamp", 0); | ||||
Show All 20 Lines | // after. | ||||
"missing.\"}},{\"success\":true}]", | "missing.\"}},{\"success\":true}]", | ||||
newTip->GetBlockTimeMax())); | newTip->GetBlockTimeMax())); | ||||
vpwallets.erase(vpwallets.begin()); | vpwallets.erase(vpwallets.begin()); | ||||
} | } | ||||
// Verify ScanForWalletTransactions does not return null when the scan is | // Verify ScanForWalletTransactions does not return null when the scan is | ||||
// elided due to the nTimeFirstKey optimization. | // elided due to the nTimeFirstKey optimization. | ||||
{ | { | ||||
CWallet wallet; | CWallet wallet(Params()); | ||||
{ | { | ||||
LOCK(wallet.cs_wallet); | LOCK(wallet.cs_wallet); | ||||
wallet.UpdateTimeFirstKey(newTip->GetBlockTime() + 7200 + 1); | wallet.UpdateTimeFirstKey(newTip->GetBlockTime() + 7200 + 1); | ||||
} | } | ||||
BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(newTip)); | BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(newTip)); | ||||
} | } | ||||
} | } | ||||
Show All 23 Lines | BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { | ||||
SetMockTime(KEY_TIME); | SetMockTime(KEY_TIME); | ||||
coinbaseTxns.emplace_back( | coinbaseTxns.emplace_back( | ||||
*CreateAndProcessBlock({}, | *CreateAndProcessBlock({}, | ||||
GetScriptForRawPubKey(coinbaseKey.GetPubKey())) | GetScriptForRawPubKey(coinbaseKey.GetPubKey())) | ||||
.vtx[0]); | .vtx[0]); | ||||
// Import key into wallet and call dumpwallet to create backup file. | // Import key into wallet and call dumpwallet to create backup file. | ||||
{ | { | ||||
CWallet wallet; | CWallet wallet(Params()); | ||||
LOCK(wallet.cs_wallet); | LOCK(wallet.cs_wallet); | ||||
wallet.mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = | wallet.mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = | ||||
KEY_TIME; | KEY_TIME; | ||||
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); | wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); | ||||
JSONRPCRequest request; | JSONRPCRequest request; | ||||
request.params.setArray(); | request.params.setArray(); | ||||
request.params.push_back("wallet.backup"); | request.params.push_back("wallet.backup"); | ||||
vpwallets.insert(vpwallets.begin(), &wallet); | vpwallets.insert(vpwallets.begin(), &wallet); | ||||
::dumpwallet(GetConfig(), request); | ::dumpwallet(GetConfig(), request); | ||||
} | } | ||||
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME | // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME | ||||
// were scanned, and no prior blocks were scanned. | // were scanned, and no prior blocks were scanned. | ||||
{ | { | ||||
CWallet wallet; | CWallet wallet(Params()); | ||||
JSONRPCRequest request; | JSONRPCRequest request; | ||||
request.params.setArray(); | request.params.setArray(); | ||||
request.params.push_back("wallet.backup"); | request.params.push_back("wallet.backup"); | ||||
vpwallets[0] = &wallet; | vpwallets[0] = &wallet; | ||||
::importwallet(GetConfig(), request); | ::importwallet(GetConfig(), request); | ||||
BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 3); | BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 3); | ||||
Show All 11 Lines | |||||
// Check that GetImmatureCredit() returns a newly calculated value instead of | // Check that GetImmatureCredit() returns a newly calculated value instead of | ||||
// the cached value after a MarkDirty() call. | // the cached value after a MarkDirty() call. | ||||
// | // | ||||
// This is a regression test written to verify a bugfix for the immature credit | // This is a regression test written to verify a bugfix for the immature credit | ||||
// function. Similar tests probably should be written for the other credit and | // function. Similar tests probably should be written for the other credit and | ||||
// debit functions. | // debit functions. | ||||
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { | BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { | ||||
CWallet wallet; | CWallet wallet(Params()); | ||||
CWalletTx wtx(&wallet, MakeTransactionRef(coinbaseTxns.back())); | CWalletTx wtx(&wallet, MakeTransactionRef(coinbaseTxns.back())); | ||||
LOCK2(cs_main, wallet.cs_wallet); | LOCK2(cs_main, wallet.cs_wallet); | ||||
wtx.hashBlock = chainActive.Tip()->GetBlockHash(); | wtx.hashBlock = chainActive.Tip()->GetBlockHash(); | ||||
wtx.nIndex = 0; | wtx.nIndex = 0; | ||||
// Call GetImmatureCredit() once before adding the key to the wallet to | // Call GetImmatureCredit() once before adding the key to the wallet to | ||||
// cache the current immature credit amount, which is 0. | // cache the current immature credit amount, which is 0. | ||||
BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), Amount(0)); | BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), Amount(0)); | ||||
Show All 26 Lines | static int64_t AddTx(CWallet &wallet, uint32_t lockTime, int64_t mockTime, | ||||
} | } | ||||
wallet.AddToWallet(wtx); | wallet.AddToWallet(wtx); | ||||
return wallet.mapWallet.at(wtx.GetId()).nTimeSmart; | return wallet.mapWallet.at(wtx.GetId()).nTimeSmart; | ||||
} | } | ||||
// Simple test to verify assignment of CWalletTx::nSmartTime value. Could be | // Simple test to verify assignment of CWalletTx::nSmartTime value. Could be | ||||
// expanded to cover more corner cases of smart time logic. | // expanded to cover more corner cases of smart time logic. | ||||
BOOST_AUTO_TEST_CASE(ComputeTimeSmart) { | BOOST_AUTO_TEST_CASE(ComputeTimeSmart) { | ||||
CWallet wallet; | CWallet wallet(Params()); | ||||
// New transaction should use clock time if lower than block time. | // New transaction should use clock time if lower than block time. | ||||
BOOST_CHECK_EQUAL(AddTx(wallet, 1, 100, 120), 100); | BOOST_CHECK_EQUAL(AddTx(wallet, 1, 100, 120), 100); | ||||
// Test that updating existing transaction does not change smart time. | // Test that updating existing transaction does not change smart time. | ||||
BOOST_CHECK_EQUAL(AddTx(wallet, 1, 200, 220), 100); | BOOST_CHECK_EQUAL(AddTx(wallet, 1, 200, 220), 100); | ||||
// New transaction should use clock time if there's no block time. | // New transaction should use clock time if there's no block time. | ||||
Show All 18 Lines |
If you init a new wallet in every test, is this necessary?