Browse Source
AvailableCoins() makes a vector of available outputs which is then passed to SelectCoinsMinConf(). This allows unit tests to test the coin selection algorithm without having the whole blockchain available.miguelfreitas
Chris Moore
13 years ago
committed by
Luke Dashjr
3 changed files with 343 additions and 48 deletions
@ -0,0 +1,256 @@ |
|||||||
|
#include <boost/test/unit_test.hpp> |
||||||
|
|
||||||
|
#include "main.h" |
||||||
|
#include "wallet.h" |
||||||
|
|
||||||
|
// 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 |
||||||
|
|
||||||
|
// enables tests which currently fail, due to code which tries too hard to avoid sub-cent change
|
||||||
|
// #define STRICT
|
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(wallet_tests) |
||||||
|
|
||||||
|
static CWallet wallet; |
||||||
|
static vector<COutput> vCoins; |
||||||
|
|
||||||
|
static void add_coin(int64 nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0) |
||||||
|
{ |
||||||
|
static int i; |
||||||
|
CTransaction* tx = new CTransaction; |
||||||
|
tx->nLockTime = i++; // so all transactions get different hashes
|
||||||
|
tx->vout.resize(nInput+1); |
||||||
|
tx->vout[nInput].nValue = nValue; |
||||||
|
CWalletTx* wtx = new CWalletTx(&wallet, *tx); |
||||||
|
delete tx; |
||||||
|
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()
|
||||||
|
wtx->vin.resize(1); |
||||||
|
wtx->fDebitCached = true; |
||||||
|
wtx->nDebitCached = 1; |
||||||
|
} |
||||||
|
COutput output(wtx, nInput, nAge); |
||||||
|
vCoins.push_back(output); |
||||||
|
} |
||||||
|
|
||||||
|
static void empty_wallet(void) |
||||||
|
{ |
||||||
|
BOOST_FOREACH(COutput output, vCoins) |
||||||
|
delete output.tx; |
||||||
|
vCoins.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(coin_selection_tests) |
||||||
|
{ |
||||||
|
static set<pair<const CWalletTx*,unsigned int> > setCoinsRet; |
||||||
|
static int64 nValueRet; |
||||||
|
|
||||||
|
// 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(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, 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(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); |
||||||
|
|
||||||
|
// but we can find a new 1 cent
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, 1, 1, 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(!wallet.SelectCoinsMinConf( 3 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); |
||||||
|
|
||||||
|
// we can make 3 cents of new coins
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, 1, 1, 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(!wallet.SelectCoinsMinConf(38 * CENT, 1, 6, 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(!wallet.SelectCoinsMinConf(38 * CENT, 6, 6, vCoins, setCoinsRet, nValueRet)); |
||||||
|
// but we can make 37 cents if we accept new coins from ourself
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(37 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT); |
||||||
|
// and we can make 38 cents if we accept all new coins
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(38 * CENT, 1, 1, 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( wallet.SelectCoinsMinConf(34 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3); // 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. I'd hope to see just 2+5, but 1 gets included too
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
#ifdef STRICT |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 7 * CENT); |
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2); |
||||||
|
#else |
||||||
|
BOOST_CHECK(nValueRet == 7 * CENT || nValueRet == 8 * CENT); |
||||||
|
BOOST_CHECK(setCoinsRet.size() == 2 || setCoinsRet.size() == 3); |
||||||
|
#endif |
||||||
|
|
||||||
|
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK(nValueRet == 8 * CENT); |
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3); |
||||||
|
|
||||||
|
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); |
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); |
||||||
|
|
||||||
|
// now clear out the wallet and start again to test chosing 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( wallet.SelectCoinsMinConf(71 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, 1, 1, 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( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); |
||||||
|
|
||||||
|
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( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3); |
||||||
|
|
||||||
|
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( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); // because in the event of a tie, the biggest coin wins
|
||||||
|
|
||||||
|
// now try making 11 cents. we hope for 5+6 but get 5+7 due to fear of sub-cent change
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(11 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
#ifdef STRICT |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); |
||||||
|
#else |
||||||
|
BOOST_CHECK(nValueRet == 11 * CENT || nValueRet == 12 * CENT); |
||||||
|
#endif |
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2); |
||||||
|
|
||||||
|
// 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( wallet.SelectCoinsMinConf(95 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 bitcoin in 1 coin
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); |
||||||
|
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(195 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 bitcoins in 1 coin
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); |
||||||
|
|
||||||
|
// empty the wallet and start again, now with fractions of a cent, to test sub-cent change avoidance
|
||||||
|
empty_wallet(); |
||||||
|
add_coin(0.1*CENT); |
||||||
|
add_coin(0.2*CENT); |
||||||
|
add_coin(0.3*CENT); |
||||||
|
add_coin(0.4*CENT); |
||||||
|
add_coin(0.5*CENT); |
||||||
|
|
||||||
|
// try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents
|
||||||
|
// we'll get sub-cent change whatever happens, so can expect 1.0 exactly
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); |
||||||
|
|
||||||
|
// but if we add a bigger coin, making it possible to avoid sub-cent change, things change:
|
||||||
|
add_coin(1111*CENT); |
||||||
|
|
||||||
|
// try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
#ifdef STRICT |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we hope for the exact amount
|
||||||
|
#else |
||||||
|
BOOST_CHECK(nValueRet == 1 * CENT || nValueRet == 1111 * CENT); // but get a single 1111 cent coin
|
||||||
|
#endif |
||||||
|
|
||||||
|
// if we add more sub-cent coins:
|
||||||
|
add_coin(0.6*CENT); |
||||||
|
add_coin(0.7*CENT); |
||||||
|
|
||||||
|
// and try again to make 1.0 cents, the new coins will be used to make 2.0 cents - leaving 1.0 cent change
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
#ifdef STRICT |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we hope for the exact amount
|
||||||
|
#else |
||||||
|
BOOST_CHECK(nValueRet == 1 * CENT || nValueRet == 2 * CENT); // but get a bunch of sub-cent coins totalling 2 cents
|
||||||
|
BOOST_CHECK_GE(setCoinsRet.size(), 4); // 0.7 + 0.6 + 0.5 + 0.2 = 2.0 for example
|
||||||
|
#endif |
||||||
|
|
||||||
|
// 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 i = 0; i < 20; i++) |
||||||
|
add_coin(50000 * COIN); |
||||||
|
|
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
#ifdef STRICT |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we hope for the exact amount
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10); // in ten coins
|
||||||
|
#else |
||||||
|
BOOST_CHECK(nValueRet == 500000 * COIN || nValueRet == 550000 * COIN); // but get 50k too much
|
||||||
|
BOOST_CHECK(setCoinsRet.size() == 10 || setCoinsRet.size() == 11); // in 11 coins
|
||||||
|
#endif |
||||||
|
|
||||||
|
// if there's not enough in the smaller coins to make at least 1 cent 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(0.5 * CENT); |
||||||
|
add_coin(0.6 * CENT); |
||||||
|
add_coin(0.7 * CENT); |
||||||
|
add_coin(1111 * CENT); |
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); |
||||||
|
|
||||||
|
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
|
||||||
|
empty_wallet(); |
||||||
|
add_coin(0.4 * CENT); |
||||||
|
add_coin(0.6 * CENT); |
||||||
|
add_coin(0.8 * CENT); |
||||||
|
add_coin(1111 * CENT); |
||||||
|
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); |
||||||
|
#ifdef STRICT |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we hope for the exact amount
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2); // in two coins 0.4+0.6
|
||||||
|
#else |
||||||
|
BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // but since value of smaller coins < target+cent, we get the bigger coin again
|
||||||
|
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); |
||||||
|
#endif |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END() |
Loading…
Reference in new issue