Feathercoin  0.5.0
P2P Digital Currency
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
wallet_tests.cpp
Go to the documentation of this file.
1 #include <boost/test/unit_test.hpp>
2 
3 #include "main.h"
4 #include "wallet.h"
5 
6 // how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
7 #define RUN_TESTS 100
8 
9 // some tests fail 1% of the time due to bad luck.
10 // we repeat those tests this many times and only complain if all iterations of the test fail
11 #define RANDOM_REPEATS 5
12 
13 using namespace std;
14 
15 typedef set<pair<const CWalletTx*,unsigned int> > CoinSet;
16 
17 BOOST_AUTO_TEST_SUITE(wallet_tests)
18 
19 static CWallet wallet;
20 static vector<COutput> vCoins;
21 
22 static void add_coin(int64 nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
23 {
24  static int nextLockTime = 0;
25  CTransaction tx;
26  tx.nLockTime = nextLockTime++; // so all transactions get different hashes
27  tx.vout.resize(nInput+1);
28  tx.vout[nInput].nValue = nValue;
29  CWalletTx* wtx = new CWalletTx(&wallet, tx);
30  if (fIsFromMe)
31  {
32  // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
33  // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
34  wtx->vin.resize(1);
35  wtx->fDebitCached = true;
36  wtx->nDebitCached = 1;
37  }
38  COutput output(wtx, nInput, nAge);
39  vCoins.push_back(output);
40 }
41 
42 static void empty_wallet(void)
43 {
44  BOOST_FOREACH(COutput output, vCoins)
45  delete output.tx;
46  vCoins.clear();
47 }
48 
49 static bool equal_sets(CoinSet a, CoinSet b)
50 {
51  pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin());
52  return ret.first == a.end() && ret.second == b.end();
53 }
54 
55 BOOST_AUTO_TEST_CASE(coin_selection_tests)
56 {
57  CoinSet setCoinsRet, setCoinsRet2;
58  int64 nValueRet;
59 
60  // test multiple times to allow for differences in the shuffle order
61  for (int i = 0; i < RUN_TESTS; i++)
62  {
63  empty_wallet();
64 
65  // with an empty wallet we can't even pay one cent
66  BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
67 
68  add_coin(1*CENT, 4); // add a new 1 cent coin
69 
70  // with a new 1 cent coin, we still can't find a mature 1 cent
71  BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
72 
73  // but we can find a new 1 cent
74  BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
75  BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
76 
77  add_coin(2*CENT); // add a mature 2 cent coin
78 
79  // we can't make 3 cents of mature coins
80  BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
81 
82  // we can make 3 cents of new coins
83  BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
84  BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
85 
86  add_coin(5*CENT); // add a mature 5 cent coin,
87  add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses
88  add_coin(20*CENT); // and a mature 20 cent coin
89 
90  // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
91 
92  // we can't make 38 cents only if we disallow new coins:
93  BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
94  // we can't even make 37 cents if we don't allow new coins even if they're from us
95  BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 6, 6, vCoins, setCoinsRet, nValueRet));
96  // but we can make 37 cents if we accept new coins from ourself
97  BOOST_CHECK( wallet.SelectCoinsMinConf(37 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
98  BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
99  // and we can make 38 cents if we accept all new coins
100  BOOST_CHECK( wallet.SelectCoinsMinConf(38 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
101  BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
102 
103  // try making 34 cents from 1,2,5,10,20 - we can't do it exactly
104  BOOST_CHECK( wallet.SelectCoinsMinConf(34 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
105  BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents
106  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)
107 
108  // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
109  BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
110  BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
111  BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
112 
113  // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
114  BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
115  BOOST_CHECK(nValueRet == 8 * CENT);
116  BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
117 
118  // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
119  BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
120  BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
121  BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
122 
123  // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
124  empty_wallet();
125 
126  add_coin( 6*CENT);
127  add_coin( 7*CENT);
128  add_coin( 8*CENT);
129  add_coin(20*CENT);
130  add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
131 
132  // check that we have 71 and not 72
133  BOOST_CHECK( wallet.SelectCoinsMinConf(71 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
134  BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
135 
136  // 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
137  BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
138  BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
139  BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
140 
141  add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
142 
143  // 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
144  BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
145  BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
146  BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
147 
148  add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
149 
150  // 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
151  BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
152  BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
153  BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
154 
155  // now try making 11 cents. we should get 5+6
156  BOOST_CHECK( wallet.SelectCoinsMinConf(11 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
157  BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
158  BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
159 
160  // check that the smallest bigger coin is used
161  add_coin( 1*COIN);
162  add_coin( 2*COIN);
163  add_coin( 3*COIN);
164  add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
165  BOOST_CHECK( wallet.SelectCoinsMinConf(95 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
166  BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
167  BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
168 
169  BOOST_CHECK( wallet.SelectCoinsMinConf(195 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
170  BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
171  BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
172 
173  // empty the wallet and start again, now with fractions of a cent, to test sub-cent change avoidance
174  empty_wallet();
175  add_coin(0.1*CENT);
176  add_coin(0.2*CENT);
177  add_coin(0.3*CENT);
178  add_coin(0.4*CENT);
179  add_coin(0.5*CENT);
180 
181  // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents
182  // we'll get sub-cent change whatever happens, so can expect 1.0 exactly
183  BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
184  BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
185 
186  // but if we add a bigger coin, making it possible to avoid sub-cent change, things change:
187  add_coin(1111*CENT);
188 
189  // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents
190  BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
191  BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
192 
193  // if we add more sub-cent coins:
194  add_coin(0.6*CENT);
195  add_coin(0.7*CENT);
196 
197  // and try again to make 1.0 cents, we can still make 1.0 cents
198  BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
199  BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
200 
201  // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
202  // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
203  empty_wallet();
204  for (int i = 0; i < 20; i++)
205  add_coin(50000 * COIN);
206 
207  BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
208  BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
209  BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
210 
211  // 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),
212  // we need to try finding an exact subset anyway
213 
214  // sometimes it will fail, and so we use the next biggest coin:
215  empty_wallet();
216  add_coin(0.5 * CENT);
217  add_coin(0.6 * CENT);
218  add_coin(0.7 * CENT);
219  add_coin(1111 * CENT);
220  BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
221  BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin
222  BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
223 
224  // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
225  empty_wallet();
226  add_coin(0.4 * CENT);
227  add_coin(0.6 * CENT);
228  add_coin(0.8 * CENT);
229  add_coin(1111 * CENT);
230  BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
231  BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
232  BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
233 
234  // test avoiding sub-cent change
235  empty_wallet();
236  add_coin(0.0005 * COIN);
237  add_coin(0.01 * COIN);
238  add_coin(1 * COIN);
239 
240  // trying to make 1.0001 from these three coins
241  BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
242  BOOST_CHECK_EQUAL(nValueRet, 1.0105 * COIN); // we should get all coins
243  BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
244 
245  // but if we try to make 0.999, we should take the bigger of the two small coins to avoid sub-cent change
246  BOOST_CHECK( wallet.SelectCoinsMinConf(0.999 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
247  BOOST_CHECK_EQUAL(nValueRet, 1.01 * COIN); // we should get 1 + 0.01
248  BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
249 
250  // test randomness
251  {
252  empty_wallet();
253  for (int i2 = 0; i2 < 100; i2++)
254  add_coin(COIN);
255 
256  // picking 50 from 100 coins doesn't depend on the shuffle,
257  // but does depend on randomness in the stochastic approximation code
258  BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet , nValueRet));
259  BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet2, nValueRet));
260  BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
261 
262  int fails = 0;
263  for (int i = 0; i < RANDOM_REPEATS; i++)
264  {
265  // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
266  // run the test RANDOM_REPEATS times and only complain if all of them fail
267  BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet , nValueRet));
268  BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet2, nValueRet));
269  if (equal_sets(setCoinsRet, setCoinsRet2))
270  fails++;
271  }
272  BOOST_CHECK_NE(fails, RANDOM_REPEATS);
273 
274  // add 75 cents in small change. not enough to make 90 cents,
275  // then try making 90 cents. there are multiple competing "smallest bigger" coins,
276  // one of which should be picked at random
277  add_coin( 5*CENT); add_coin(10*CENT); add_coin(15*CENT); add_coin(20*CENT); add_coin(25*CENT);
278 
279  fails = 0;
280  for (int i = 0; i < RANDOM_REPEATS; i++)
281  {
282  // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
283  // run the test RANDOM_REPEATS times and only complain if all of them fail
284  BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet , nValueRet));
285  BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet2, nValueRet));
286  if (equal_sets(setCoinsRet, setCoinsRet2))
287  fails++;
288  }
289  BOOST_CHECK_NE(fails, RANDOM_REPEATS);
290  }
291  }
292 }
293 
294 BOOST_AUTO_TEST_SUITE_END()
#define RANDOM_REPEATS
set< pair< const CWalletTx *, unsigned int > > CoinSet
bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, std::vector< COutput > vCoins, std::set< std::pair< const CWalletTx *, unsigned int > > &setCoinsRet, int64 &nValueRet) const
Definition: wallet.cpp:1043
bool fDebitCached
Definition: wallet.h:385
#define RUN_TESTS
Definition: wallet_tests.cpp:7
unsigned int nLockTime
Definition: main.h:486
int64 nDebitCached
Definition: wallet.h:390
BOOST_AUTO_TEST_CASE(coin_selection_tests)
std::vector< CTxOut > vout
Definition: main.h:485
std::vector< CTxIn > vin
Definition: main.h:484
A transaction with a bunch of additional info that only the owner cares about.
Definition: wallet.h:367
A CWallet is an extension of a keystore, which also maintains a set of transactions and balances...
Definition: wallet.h:69
The basic transaction that is broadcasted on the network and contained in blocks. ...
Definition: main.h:477
long long int64
Definition: serialize.h:25