15
15
16
16
BOOST_FIXTURE_TEST_SUITE (coin_selection_tests, WalletTestingSetup)
17
17
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
+
18
27
typedef std::set<CInputCoin> CoinSet;
19
28
20
29
static std::vector<COutput> vCoins;
@@ -36,6 +45,35 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set)
36
45
set.emplace (MakeTransactionRef (tx), nInput);
37
46
}
38
47
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
+
39
77
static bool equal_sets (CoinSet a, CoinSet b)
40
78
{
41
79
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)
168
206
}
169
207
}
170
208
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
+
171
501
BOOST_AUTO_TEST_SUITE_END ()
0 commit comments