aboutsummaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorChris Moore <dooglus@gmail.com>2012-02-27 04:19:32 -0800
committerLuke Dashjr <luke-jr+git@utopios.org>2012-06-04 16:36:34 +0000
commit9b0369c773c1d350a05435d8d3ec4e954828fb82 (patch)
tree43b695e7dd82200c0bcd407b40613ee4501ac2eb /src/test
parent882ba0e7524b54be861b379366b5845a3978b256 (diff)
downloadbitcoin-9b0369c773c1d350a05435d8d3ec4e954828fb82.tar.xz
Refactor SelectCoinsMinConf() and add unit tests.
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.
Diffstat (limited to 'src/test')
-rw-r--r--src/test/wallet_tests.cpp256
1 files changed, 256 insertions, 0 deletions
diff --git a/src/test/wallet_tests.cpp b/src/test/wallet_tests.cpp
new file mode 100644
index 0000000000..705609464a
--- /dev/null
+++ b/src/test/wallet_tests.cpp
@@ -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()