1515
1616BOOST_FIXTURE_TEST_SUITE (coin_selection_tests, WalletTestingSetup)
1717
18+ // how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
19+ #define RUN_TESTS 100
20+
21+ // some tests fail 1% of the time due to bad luck.
22+ // we repeat those tests this many times and only complain if all iterations of the test fail
23+ #define RANDOM_REPEATS 5
24+
25+ std::vector<std::unique_ptr<CWalletTx>> wtxn;
26+
1827typedef std::set<CInputCoin> CoinSet;
1928
2029static std::vector<COutput> vCoins;
@@ -36,6 +45,35 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set)
3645 set.emplace (MakeTransactionRef (tx), nInput);
3746}
3847
48+ static void add_coin (const CAmount& nValue, int nAge = 6 *24 , bool fIsFromMe = false , int nInput=0 )
49+ {
50+ static int nextLockTime = 0 ;
51+ CMutableTransaction tx;
52+ tx.nLockTime = nextLockTime++; // so all transactions get different hashes
53+ tx.vout .resize (nInput + 1 );
54+ tx.vout [nInput].nValue = nValue;
55+ if (fIsFromMe ) {
56+ // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
57+ // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
58+ tx.vin .resize (1 );
59+ }
60+ std::unique_ptr<CWalletTx> wtx (new CWalletTx (&testWallet, MakeTransactionRef (std::move (tx))));
61+ if (fIsFromMe )
62+ {
63+ wtx->fDebitCached = true ;
64+ wtx->nDebitCached = 1 ;
65+ }
66+ COutput output (wtx.get (), nInput, nAge, true /* spendable */ , true /* solvable */ , true /* safe */ );
67+ vCoins.push_back (output);
68+ wtxn.emplace_back (std::move (wtx));
69+ }
70+
71+ static void empty_wallet (void )
72+ {
73+ vCoins.clear ();
74+ wtxn.clear ();
75+ }
76+
3977static bool equal_sets (CoinSet a, CoinSet b)
4078{
4179 std::pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch (a.begin (), a.end (), b.begin ());
@@ -168,4 +206,296 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
168206 }
169207}
170208
209+ CoinEligibilityFilter filter_standard (1 , 6 , 0 );
210+ CoinEligibilityFilter filter_confirmed (1 , 1 , 0 );
211+ CoinEligibilityFilter filter_standard_extra (6 , 6 , 0 );
212+
213+ BOOST_AUTO_TEST_CASE (knapsack_solver_test)
214+ {
215+ CoinSet setCoinsRet, setCoinsRet2;
216+ CAmount nValueRet;
217+
218+ LOCK (testWallet.cs_wallet );
219+
220+ // test multiple times to allow for differences in the shuffle order
221+ for (int i = 0 ; i < RUN_TESTS; i++)
222+ {
223+ empty_wallet ();
224+
225+ // with an empty wallet we can't even pay one cent
226+ BOOST_CHECK (!testWallet.SelectCoinsMinConf ( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet));
227+
228+ add_coin (1 *CENT, 4 ); // add a new 1 cent coin
229+
230+ // with a new 1 cent coin, we still can't find a mature 1 cent
231+ BOOST_CHECK (!testWallet.SelectCoinsMinConf ( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet));
232+
233+ // but we can find a new 1 cent
234+ BOOST_CHECK ( testWallet.SelectCoinsMinConf ( 1 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
235+ BOOST_CHECK_EQUAL (nValueRet, 1 * CENT);
236+
237+ add_coin (2 *CENT); // add a mature 2 cent coin
238+
239+ // we can't make 3 cents of mature coins
240+ BOOST_CHECK (!testWallet.SelectCoinsMinConf ( 3 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet));
241+
242+ // we can make 3 cents of new coins
243+ BOOST_CHECK ( testWallet.SelectCoinsMinConf ( 3 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
244+ BOOST_CHECK_EQUAL (nValueRet, 3 * CENT);
245+
246+ add_coin (5 *CENT); // add a mature 5 cent coin,
247+ add_coin (10 *CENT, 3 , true ); // a new 10 cent coin sent from one of our own addresses
248+ add_coin (20 *CENT); // and a mature 20 cent coin
249+
250+ // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
251+
252+ // we can't make 38 cents only if we disallow new coins:
253+ BOOST_CHECK (!testWallet.SelectCoinsMinConf (38 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet));
254+ // we can't even make 37 cents if we don't allow new coins even if they're from us
255+ BOOST_CHECK (!testWallet.SelectCoinsMinConf (38 * CENT, filter_standard_extra, vCoins, setCoinsRet, nValueRet));
256+ // but we can make 37 cents if we accept new coins from ourself
257+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (37 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet));
258+ BOOST_CHECK_EQUAL (nValueRet, 37 * CENT);
259+ // and we can make 38 cents if we accept all new coins
260+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (38 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
261+ BOOST_CHECK_EQUAL (nValueRet, 38 * CENT);
262+
263+ // try making 34 cents from 1,2,5,10,20 - we can't do it exactly
264+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (34 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
265+ BOOST_CHECK_EQUAL (nValueRet, 35 * CENT); // but 35 cents is closest
266+ 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)
267+
268+ // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
269+ BOOST_CHECK ( testWallet.SelectCoinsMinConf ( 7 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
270+ BOOST_CHECK_EQUAL (nValueRet, 7 * CENT);
271+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 2U );
272+
273+ // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
274+ BOOST_CHECK ( testWallet.SelectCoinsMinConf ( 8 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
275+ BOOST_CHECK (nValueRet == 8 * CENT);
276+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 3U );
277+
278+ // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
279+ BOOST_CHECK ( testWallet.SelectCoinsMinConf ( 9 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
280+ BOOST_CHECK_EQUAL (nValueRet, 10 * CENT);
281+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 1U );
282+
283+ // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
284+ empty_wallet ();
285+
286+ add_coin ( 6 *CENT);
287+ add_coin ( 7 *CENT);
288+ add_coin ( 8 *CENT);
289+ add_coin (20 *CENT);
290+ add_coin (30 *CENT); // now we have 6+7+8+20+30 = 71 cents total
291+
292+ // check that we have 71 and not 72
293+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (71 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
294+ BOOST_CHECK (!testWallet.SelectCoinsMinConf (72 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
295+
296+ // 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
297+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
298+ BOOST_CHECK_EQUAL (nValueRet, 20 * CENT); // we should get 20 in one coin
299+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 1U );
300+
301+ add_coin ( 5 *CENT); // now we have 5+6+7+8+20+30 = 75 cents total
302+
303+ // 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
304+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
305+ BOOST_CHECK_EQUAL (nValueRet, 18 * CENT); // we should get 18 in 3 coins
306+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 3U );
307+
308+ add_coin ( 18 *CENT); // now we have 5+6+7+8+18+20+30
309+
310+ // 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
311+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
312+ BOOST_CHECK_EQUAL (nValueRet, 18 * CENT); // we should get 18 in 1 coin
313+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 1U ); // because in the event of a tie, the biggest coin wins
314+
315+ // now try making 11 cents. we should get 5+6
316+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (11 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
317+ BOOST_CHECK_EQUAL (nValueRet, 11 * CENT);
318+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 2U );
319+
320+ // check that the smallest bigger coin is used
321+ add_coin ( 1 *COIN);
322+ add_coin ( 2 *COIN);
323+ add_coin ( 3 *COIN);
324+ add_coin ( 4 *COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
325+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (95 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
326+ BOOST_CHECK_EQUAL (nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
327+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 1U );
328+
329+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (195 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet));
330+ BOOST_CHECK_EQUAL (nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
331+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 1U );
332+
333+ // empty the wallet and start again, now with fractions of a cent, to test small change avoidance
334+
335+ empty_wallet ();
336+ add_coin (MIN_CHANGE * 1 / 10 );
337+ add_coin (MIN_CHANGE * 2 / 10 );
338+ add_coin (MIN_CHANGE * 3 / 10 );
339+ add_coin (MIN_CHANGE * 4 / 10 );
340+ add_coin (MIN_CHANGE * 5 / 10 );
341+
342+ // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
343+ // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
344+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet));
345+ BOOST_CHECK_EQUAL (nValueRet, MIN_CHANGE);
346+
347+ // but if we add a bigger coin, small change is avoided
348+ add_coin (1111 *MIN_CHANGE);
349+
350+ // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
351+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet));
352+ BOOST_CHECK_EQUAL (nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
353+
354+ // if we add more small coins:
355+ add_coin (MIN_CHANGE * 6 / 10 );
356+ add_coin (MIN_CHANGE * 7 / 10 );
357+
358+ // and try again to make 1.0 * MIN_CHANGE
359+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet));
360+ BOOST_CHECK_EQUAL (nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
361+
362+ // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
363+ // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
364+ empty_wallet ();
365+ for (int j = 0 ; j < 20 ; j++)
366+ add_coin (50000 * COIN);
367+
368+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (500000 * COIN, filter_confirmed, vCoins, setCoinsRet, nValueRet));
369+ BOOST_CHECK_EQUAL (nValueRet, 500000 * COIN); // we should get the exact amount
370+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 10U ); // in ten coins
371+
372+ // 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),
373+ // we need to try finding an exact subset anyway
374+
375+ // sometimes it will fail, and so we use the next biggest coin:
376+ empty_wallet ();
377+ add_coin (MIN_CHANGE * 5 / 10 );
378+ add_coin (MIN_CHANGE * 6 / 10 );
379+ add_coin (MIN_CHANGE * 7 / 10 );
380+ add_coin (1111 * MIN_CHANGE);
381+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet));
382+ BOOST_CHECK_EQUAL (nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
383+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 1U );
384+
385+ // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
386+ empty_wallet ();
387+ add_coin (MIN_CHANGE * 4 / 10 );
388+ add_coin (MIN_CHANGE * 6 / 10 );
389+ add_coin (MIN_CHANGE * 8 / 10 );
390+ add_coin (1111 * MIN_CHANGE);
391+ BOOST_CHECK ( testWallet.SelectCoinsMinConf (MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet));
392+ BOOST_CHECK_EQUAL (nValueRet, MIN_CHANGE); // we should get the exact amount
393+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 2U ); // in two coins 0.4+0.6
394+
395+ // test avoiding small change
396+ empty_wallet ();
397+ add_coin (MIN_CHANGE * 5 / 100 );
398+ add_coin (MIN_CHANGE * 1 );
399+ add_coin (MIN_CHANGE * 100 );
400+
401+ // trying to make 100.01 from these three coins
402+ BOOST_CHECK (testWallet.SelectCoinsMinConf (MIN_CHANGE * 10001 / 100 , filter_confirmed, vCoins, setCoinsRet, nValueRet));
403+ BOOST_CHECK_EQUAL (nValueRet, MIN_CHANGE * 10105 / 100 ); // we should get all coins
404+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 3U );
405+
406+ // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
407+ BOOST_CHECK (testWallet.SelectCoinsMinConf (MIN_CHANGE * 9990 / 100 , filter_confirmed, vCoins, setCoinsRet, nValueRet));
408+ BOOST_CHECK_EQUAL (nValueRet, 101 * MIN_CHANGE);
409+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 2U );
410+
411+ // test with many inputs
412+ for (CAmount amt=1500 ; amt < COIN; amt*=10 ) {
413+ empty_wallet ();
414+ // Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 bytes per input)
415+ for (uint16_t j = 0 ; j < 676 ; j++)
416+ add_coin (amt);
417+ BOOST_CHECK (testWallet.SelectCoinsMinConf (2000 , filter_confirmed, vCoins, setCoinsRet, nValueRet));
418+ if (amt - 2000 < MIN_CHANGE) {
419+ // needs more than one input:
420+ uint16_t returnSize = std::ceil ((2000.0 + MIN_CHANGE)/amt);
421+ CAmount returnValue = amt * returnSize;
422+ BOOST_CHECK_EQUAL (nValueRet, returnValue);
423+ BOOST_CHECK_EQUAL (setCoinsRet.size (), returnSize);
424+ } else {
425+ // one input is sufficient:
426+ BOOST_CHECK_EQUAL (nValueRet, amt);
427+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 1U );
428+ }
429+ }
430+
431+ // test randomness
432+ {
433+ empty_wallet ();
434+ for (int i2 = 0 ; i2 < 100 ; i2++)
435+ add_coin (COIN);
436+
437+ // picking 50 from 100 coins doesn't depend on the shuffle,
438+ // but does depend on randomness in the stochastic approximation code
439+ BOOST_CHECK (testWallet.SelectCoinsMinConf (50 * COIN, filter_standard, vCoins, setCoinsRet , nValueRet));
440+ BOOST_CHECK (testWallet.SelectCoinsMinConf (50 * COIN, filter_standard, vCoins, setCoinsRet2, nValueRet));
441+ BOOST_CHECK (!equal_sets (setCoinsRet, setCoinsRet2));
442+
443+ int fails = 0 ;
444+ for (int j = 0 ; j < RANDOM_REPEATS; j++)
445+ {
446+ // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
447+ // run the test RANDOM_REPEATS times and only complain if all of them fail
448+ BOOST_CHECK (testWallet.SelectCoinsMinConf (COIN, filter_standard, vCoins, setCoinsRet , nValueRet));
449+ BOOST_CHECK (testWallet.SelectCoinsMinConf (COIN, filter_standard, vCoins, setCoinsRet2, nValueRet));
450+ if (equal_sets (setCoinsRet, setCoinsRet2))
451+ fails++;
452+ }
453+ BOOST_CHECK_NE (fails, RANDOM_REPEATS);
454+
455+ // add 75 cents in small change. not enough to make 90 cents,
456+ // then try making 90 cents. there are multiple competing "smallest bigger" coins,
457+ // one of which should be picked at random
458+ add_coin (5 * CENT);
459+ add_coin (10 * CENT);
460+ add_coin (15 * CENT);
461+ add_coin (20 * CENT);
462+ add_coin (25 * CENT);
463+
464+ fails = 0 ;
465+ for (int j = 0 ; j < RANDOM_REPEATS; j++)
466+ {
467+ // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
468+ // run the test RANDOM_REPEATS times and only complain if all of them fail
469+ BOOST_CHECK (testWallet.SelectCoinsMinConf (90 *CENT, filter_standard, vCoins, setCoinsRet , nValueRet));
470+ BOOST_CHECK (testWallet.SelectCoinsMinConf (90 *CENT, filter_standard, vCoins, setCoinsRet2, nValueRet));
471+ if (equal_sets (setCoinsRet, setCoinsRet2))
472+ fails++;
473+ }
474+ BOOST_CHECK_NE (fails, RANDOM_REPEATS);
475+ }
476+ }
477+ empty_wallet ();
478+ }
479+
480+ BOOST_AUTO_TEST_CASE (ApproximateBestSubset)
481+ {
482+ CoinSet setCoinsRet;
483+ CAmount nValueRet;
484+
485+ LOCK (testWallet.cs_wallet );
486+
487+ empty_wallet ();
488+
489+ // Test vValue sort order
490+ for (int i = 0 ; i < 1000 ; i++)
491+ add_coin (1000 * COIN);
492+ add_coin (3 * COIN);
493+
494+ BOOST_CHECK (testWallet.SelectCoinsMinConf (1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet));
495+ BOOST_CHECK_EQUAL (nValueRet, 1003 * COIN);
496+ BOOST_CHECK_EQUAL (setCoinsRet.size (), 2U );
497+
498+ empty_wallet ();
499+ }
500+
171501BOOST_AUTO_TEST_SUITE_END ()
0 commit comments