Line data Source code
1 : // Copyright (c) 2012-2014 The Bitcoin Core developers
2 : // Copyright (c) 2019-2021 The PIVX Core developers
3 : // Distributed under the MIT/X11 software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "wallet/test/wallet_test_fixture.h"
7 :
8 : #include "consensus/merkle.h"
9 : #include "rpc/server.h"
10 : #include "txmempool.h"
11 : #include "validation.h"
12 : #include "wallet/wallet.h"
13 : #include "wallet/walletutil.h"
14 :
15 : #include <set>
16 : #include <utility>
17 : #include <vector>
18 :
19 : #include <boost/test/unit_test.hpp>
20 : #include <univalue.h>
21 :
22 : extern UniValue importmulti(const JSONRPCRequest& request);
23 : extern UniValue dumpwallet(const JSONRPCRequest& request);
24 : extern UniValue importwallet(const JSONRPCRequest& request);
25 :
26 : // how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
27 : #define RUN_TESTS 100
28 :
29 : // some tests fail 1% of the time due to bad luck.
30 : // we repeat those tests this many times and only complain if all iterations of the test fail
31 : #define RANDOM_REPEATS 5
32 :
33 : std::vector<std::unique_ptr<CWalletTx>> wtxn;
34 :
35 : typedef std::set<std::pair<const CWalletTx*,unsigned int> > CoinSet;
36 :
37 : BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
38 :
39 : static const CWallet testWallet("dummy", WalletDatabase::CreateDummy());
40 : static std::vector<COutput> vCoins;
41 :
42 354000 : static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
43 : {
44 354000 : static int nextLockTime = 0;
45 708000 : CMutableTransaction tx;
46 354000 : tx.nLockTime = nextLockTime++; // so all transactions get different hashes
47 354000 : tx.vout.resize(nInput+1);
48 354000 : tx.vout[nInput].nValue = nValue;
49 354000 : if (fIsFromMe) {
50 : // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
51 : // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
52 100 : tx.vin.resize(1);
53 : }
54 708000 : std::unique_ptr<CWalletTx> wtx(new CWalletTx(&testWallet, MakeTransactionRef(std::move(tx))));
55 354000 : if (fIsFromMe) {
56 100 : wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1);
57 : }
58 354000 : COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
59 354000 : vCoins.push_back(output);
60 354000 : wtxn.emplace_back(std::move(wtx));
61 354000 : }
62 :
63 1301 : static void empty_wallet(void)
64 : {
65 1301 : vCoins.clear();
66 1301 : wtxn.clear();
67 1301 : }
68 :
69 1100 : static bool equal_sets(CoinSet a, CoinSet b)
70 : {
71 1100 : std::pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin());
72 1100 : return ret.first == a.end() && ret.second == b.end();
73 : }
74 :
75 2 : BOOST_AUTO_TEST_CASE(coin_selection_tests)
76 : {
77 2 : CoinSet setCoinsRet, setCoinsRet2;
78 1 : CAmount nValueRet;
79 :
80 2 : LOCK(testWallet.cs_wallet);
81 :
82 : // test multiple times to allow for differences in the shuffle order
83 101 : for (int i = 0; i < RUN_TESTS; i++)
84 : {
85 100 : empty_wallet();
86 :
87 : // with an empty wallet we can't even pay one cent
88 200 : BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
89 :
90 100 : add_coin(1*CENT, 4); // add a new 1 cent coin
91 :
92 : // with a new 1 cent coin, we still can't find a mature 1 cent
93 300 : BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
94 :
95 : // but we can find a new 1 cent
96 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
97 100 : BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
98 :
99 100 : add_coin(2*CENT); // add a mature 2 cent coin
100 :
101 : // we can't make 3 cents of mature coins
102 300 : BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
103 :
104 : // we can make 3 cents of new coins
105 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf( 3 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
106 100 : BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
107 :
108 100 : add_coin(5*CENT); // add a mature 5 cent coin,
109 100 : add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses
110 100 : add_coin(20*CENT); // and a mature 20 cent coin
111 :
112 : // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
113 :
114 : // we can't make 38 cents only if we disallow new coins:
115 300 : BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
116 : // we can't even make 37 cents if we don't allow new coins even if they're from us
117 300 : BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, 6, 6, 0, vCoins, setCoinsRet, nValueRet));
118 : // but we can make 37 cents if we accept new coins from ourself
119 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(37 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
120 100 : BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
121 : // and we can make 38 cents if we accept all new coins
122 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(38 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
123 100 : BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
124 :
125 : // try making 34 cents from 1,2,5,10,20 - we can't do it exactly
126 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(34 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
127 100 : BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
128 100 : 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)
129 :
130 : // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
131 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf( 7 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
132 100 : BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
133 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
134 :
135 : // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
136 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf( 8 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
137 200 : BOOST_CHECK(nValueRet == 8 * CENT);
138 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
139 :
140 : // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
141 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf( 9 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
142 100 : BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
143 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
144 :
145 : // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
146 100 : empty_wallet();
147 :
148 100 : add_coin(6*CENT);
149 100 : add_coin(7*CENT);
150 100 : add_coin(8*CENT);
151 100 : add_coin(20*CENT);
152 100 : add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
153 :
154 : // check that we have 71 and not 72
155 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(71 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
156 300 : BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
157 :
158 : // 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
159 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
160 100 : BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
161 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
162 :
163 100 : add_coin(5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
164 :
165 : // 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
166 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
167 100 : BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
168 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
169 :
170 100 : add_coin(18*CENT); // now we have 5+6+7+8+18+20+30
171 :
172 : // 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
173 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
174 100 : BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
175 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
176 :
177 : // now try making 11 cents. we should get 5+6
178 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(11 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
179 100 : BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
180 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
181 :
182 : // check that the smallest bigger coin is used
183 100 : add_coin(1*COIN);
184 100 : add_coin(2*COIN);
185 100 : add_coin(3*COIN);
186 100 : add_coin(4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
187 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(95 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
188 100 : BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
189 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
190 :
191 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(195 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
192 100 : BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
193 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
194 :
195 : // empty the wallet and start again, now with fractions of a cent, to test small change avoidance
196 :
197 100 : empty_wallet();
198 100 : add_coin(0.1*MIN_CHANGE);
199 100 : add_coin(0.2*MIN_CHANGE);
200 100 : add_coin(0.3*MIN_CHANGE);
201 100 : add_coin(0.4*MIN_CHANGE);
202 100 : add_coin(0.5*MIN_CHANGE);
203 :
204 : // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
205 : // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
206 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
207 100 : BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
208 :
209 : // but if we add a bigger coin, small change is avoided
210 100 : add_coin(1111*MIN_CHANGE);
211 :
212 : // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
213 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
214 100 : BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
215 :
216 : // if we add more small coins:
217 100 : add_coin(0.6*MIN_CHANGE);
218 100 : add_coin(0.7*MIN_CHANGE);
219 :
220 : // and try again to make 1.0 * MIN_CHANGE
221 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
222 100 : BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
223 :
224 : // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
225 : // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
226 100 : empty_wallet();
227 2100 : for (int j = 0; j < 20; j++)
228 2000 : add_coin(50000 * COIN);
229 :
230 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(500000 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
231 100 : BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
232 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
233 :
234 : // 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),
235 : // we need to try finding an exact subset anyway
236 :
237 : // sometimes it will fail, and so we use the next biggest coin:
238 100 : empty_wallet();
239 100 : add_coin(0.5 * CENT);
240 100 : add_coin(0.6 * CENT);
241 100 : add_coin(0.7 * CENT);
242 100 : add_coin(1111 * CENT);
243 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(1 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
244 100 : BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin
245 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
246 :
247 : // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
248 100 : empty_wallet();
249 100 : add_coin(0.4 * MIN_CHANGE);
250 100 : add_coin(0.6 * MIN_CHANGE);
251 100 : add_coin(0.8 * MIN_CHANGE);
252 100 : add_coin(1111 * MIN_CHANGE);
253 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
254 100 : BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
255 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
256 :
257 : // test avoiding small change
258 100 : empty_wallet();
259 100 : add_coin(0.05 * MIN_CHANGE);
260 100 : add_coin(1 * MIN_CHANGE);
261 100 : add_coin(100 * MIN_CHANGE);
262 :
263 : // trying to make 100.01 from these three coins
264 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(100.01 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
265 100 : BOOST_CHECK_EQUAL(nValueRet, 101.05 * MIN_CHANGE); // we should get all coins
266 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
267 :
268 : // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
269 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(99.9 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
270 100 : BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
271 100 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
272 :
273 : // test with many inputs
274 600 : for (CAmount amt=1500; amt < COIN; amt*=10) {
275 500 : empty_wallet();
276 : // Create 676 inputs (= MAX_STANDARD_TX_SIZE / 148 bytes per input)
277 338500 : for (uint16_t j = 0; j < 676; j++)
278 338000 : add_coin(amt);
279 1500 : BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
280 500 : if (amt - 2000 < MIN_CHANGE) {
281 : // needs more than one input:
282 300 : uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt);
283 300 : CAmount returnValue = amt * returnSize;
284 300 : BOOST_CHECK_EQUAL(nValueRet, returnValue);
285 300 : BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize);
286 : } else {
287 : // one input is sufficient:
288 200 : BOOST_CHECK_EQUAL(nValueRet, amt);
289 200 : BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
290 : }
291 : }
292 :
293 : // test randomness
294 100 : {
295 100 : empty_wallet();
296 10100 : for (int i2 = 0; i2 < 100; i2++)
297 10000 : add_coin(COIN);
298 :
299 : // picking 50 from 100 coins doesn't depend on the shuffle,
300 : // but does depend on randomness in the stochastic approximation code
301 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, setCoinsRet , nValueRet));
302 300 : BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, setCoinsRet2, nValueRet));
303 200 : BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
304 :
305 100 : int fails = 0;
306 600 : for (int j = 0; j < RANDOM_REPEATS; j++)
307 : {
308 : // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
309 : // run the test RANDOM_REPEATS times and only complain if all of them fail
310 1500 : BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, 1, 6, 0, vCoins, setCoinsRet , nValueRet));
311 1500 : BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, 1, 6, 0, vCoins, setCoinsRet2, nValueRet));
312 500 : if (equal_sets(setCoinsRet, setCoinsRet2))
313 3 : fails++;
314 : }
315 100 : BOOST_CHECK_NE(fails, RANDOM_REPEATS);
316 :
317 : // add 75 cents in small change. not enough to make 90 cents,
318 : // then try making 90 cents. there are multiple competing "smallest bigger" coins,
319 : // one of which should be picked at random
320 100 : add_coin(5 * CENT);
321 100 : add_coin(10 * CENT);
322 100 : add_coin(15 * CENT);
323 100 : add_coin(20 * CENT);
324 100 : add_coin(25 * CENT);
325 :
326 100 : fails = 0;
327 600 : for (int j = 0; j < RANDOM_REPEATS; j++)
328 : {
329 : // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
330 : // run the test RANDOM_REPEATS times and only complain if all of them fail
331 1500 : BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, 1, 6, 0, vCoins, setCoinsRet , nValueRet));
332 1500 : BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, 1, 6, 0, vCoins, setCoinsRet2, nValueRet));
333 500 : if (equal_sets(setCoinsRet, setCoinsRet2))
334 5 : fails++;
335 : }
336 100 : BOOST_CHECK_NE(fails, RANDOM_REPEATS);
337 : }
338 : }
339 1 : empty_wallet();
340 1 : }
341 :
342 1 : static void AddKey(CWallet& wallet, const CKey& key)
343 : {
344 1 : LOCK(wallet.cs_wallet);
345 1 : wallet.AddKeyPubKey(key, key.GetPubKey());
346 1 : }
347 :
348 2 : BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
349 : {
350 : // Cap last block file size, and mine new block in a new block file.
351 1 : CBlockIndex* const nullBlock = nullptr;
352 1 : CBlockIndex* oldTip = chainActive.Tip();
353 1 : GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
354 1 : CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
355 1 : CBlockIndex* newTip = chainActive.Tip();
356 :
357 1 : LOCK(cs_main);
358 :
359 : // Verify ScanForWalletTransactions picks up transactions in both the old
360 : // and new block files.
361 1 : {
362 3 : CWallet wallet("dummy", WalletDatabase::CreateDummy());
363 2 : WITH_LOCK(wallet.cs_wallet, wallet.SetLastBlockProcessed(newTip); );
364 1 : AddKey(wallet, coinbaseKey);
365 2 : WalletRescanReserver reserver(&wallet);
366 1 : reserver.reserve();
367 1 : BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
368 1 : BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 500 * COIN);
369 : }
370 :
371 : // !TODO: Prune the older block file.
372 : /*
373 : PruneOneBlockFile(oldTip->GetBlockPos().nFile);
374 : UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
375 :
376 : // Verify ScanForWalletTransactions only picks transactions in the new block
377 : // file.
378 : {
379 : CWallet wallet("dummy", WalletDatabase::CreateDummy());
380 : AddKey(wallet, coinbaseKey);
381 : WalletRescanReserver reserver(&wallet);
382 : reserver.reserve();
383 : BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));;
384 : BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 250 * COIN);
385 : }
386 : */
387 :
388 : // Verify importmulti RPC returns failure for a key whose creation time is
389 : // before the missing block, and success for a key whose creation time is
390 : // after.
391 1 : {
392 3 : CWallet wallet("dummy", WalletDatabase::CreateDummy());
393 2 : WITH_LOCK(wallet.cs_wallet, wallet.SetLastBlockProcessed(newTip); );
394 1 : vpwallets.insert(vpwallets.begin(), &wallet);
395 2 : UniValue keys;
396 1 : keys.setArray();
397 1 : UniValue key;
398 1 : key.setObject();
399 4 : key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey())));
400 1 : key.pushKV("timestamp", 0);
401 2 : key.pushKV("internal", UniValue(true));
402 1 : keys.push_back(key);
403 1 : key.clear();
404 1 : key.setObject();
405 2 : CKey futureKey;
406 1 : futureKey.MakeNewKey(true);
407 4 : key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey())));
408 1 : key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1);
409 2 : key.pushKV("internal", UniValue(true));
410 1 : keys.push_back(key);
411 2 : JSONRPCRequest request;
412 1 : request.params.setArray();
413 1 : request.params.push_back(keys);
414 :
415 1 : UniValue response = importmulti(request);
416 : // !TODO: after pruning, check that the rescan for the first key fails.
417 2 : BOOST_CHECK_EQUAL(response.write(), "[{\"success\":true},{\"success\":true}]");
418 1 : vpwallets.erase(vpwallets.begin());
419 : }
420 1 : }
421 :
422 : // Verify importwallet RPC starts rescan at earliest block with timestamp
423 : // greater or equal than key birthday. Previously there was a bug where
424 : // importwallet RPC would start the scan at the latest block with timestamp less
425 : // than or equal to key birthday.
426 2 : BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
427 : {
428 : // Create one block
429 2 : const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 15;
430 1 : SetMockTime(BLOCK_TIME);
431 1 : coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
432 :
433 : // Set key birthday to block time increased by the timestamp window, so
434 : // rescan will start at the block time.
435 1 : const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW;
436 1 : SetMockTime(KEY_TIME);
437 1 : coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
438 :
439 5 : std::string backup_file = (SetDataDir("importwallet_rescan") / "wallet.backup").string();
440 :
441 : // Import key into wallet and call dumpwallet to create backup file.
442 1 : {
443 3 : CWallet wallet("dummy", WalletDatabase::CreateDummy());
444 1 : {
445 1 : LOCK(wallet.cs_wallet);
446 1 : wallet.mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
447 1 : wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
448 : }
449 :
450 1 : JSONRPCRequest request;
451 1 : request.params.setArray();
452 1 : request.params.push_back(backup_file);
453 1 : vpwallets.insert(vpwallets.begin(), &wallet);
454 1 : ::dumpwallet(request);
455 : }
456 :
457 : // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
458 : // were scanned, and no prior blocks were scanned.
459 1 : {
460 3 : CWallet wallet("dummy", WalletDatabase::CreateDummy());
461 :
462 1 : JSONRPCRequest request;
463 1 : request.params.setArray();
464 1 : request.params.push_back(backup_file);
465 1 : vpwallets[0] = &wallet;
466 1 : ::importwallet(request);
467 :
468 2 : LOCK(wallet.cs_wallet);
469 1 : BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 2);
470 1 : BOOST_CHECK_EQUAL(coinbaseTxns.size(), 102);
471 103 : for (size_t i = 0; i < coinbaseTxns.size(); ++i) {
472 102 : bool found = wallet.GetWalletTx(coinbaseTxns[i].GetHash());
473 102 : bool expected = i >= 100;
474 102 : BOOST_CHECK_EQUAL(found, expected);
475 : }
476 : }
477 :
478 1 : SetMockTime(0);
479 1 : vpwallets.erase(vpwallets.begin());
480 1 : }
481 :
482 1 : void removeTxFromMempool(CWalletTx& wtx)
483 : {
484 1 : LOCK(mempool.cs);
485 1 : if (mempool.exists(wtx.GetHash())) {
486 1 : auto it = mempool.mapTx.find(wtx.GetHash());
487 1 : if (it != mempool.mapTx.end()) {
488 1 : mempool.mapTx.erase(it);
489 : }
490 : }
491 1 : }
492 :
493 : /**
494 : * Mimic block creation.
495 : */
496 1 : CBlockIndex* SimpleFakeMine(CWalletTx& wtx, CWallet &wallet, CBlockIndex* pprev = nullptr)
497 : {
498 1 : CBlock block;
499 1 : block.vtx.emplace_back(wtx.tx);
500 1 : block.hashMerkleRoot = BlockMerkleRoot(block);
501 1 : if (pprev) block.hashPrevBlock = pprev->GetBlockHash();
502 1 : CBlockIndex* fakeIndex = new CBlockIndex(block);
503 1 : fakeIndex->pprev = pprev;
504 1 : mapBlockIndex.emplace(block.GetHash(), fakeIndex);
505 2 : fakeIndex->phashBlock = &mapBlockIndex.find(block.GetHash())->first;
506 1 : chainActive.SetTip(fakeIndex);
507 3 : BOOST_CHECK(chainActive.Contains(fakeIndex));
508 2 : WITH_LOCK(wallet.cs_wallet, wallet.SetLastBlockProcessed(fakeIndex));
509 1 : wtx.m_confirm = CWalletTx::Confirmation(CWalletTx::Status::CONFIRMED, fakeIndex->nHeight, fakeIndex->GetBlockHash(), 0);
510 1 : removeTxFromMempool(wtx);
511 1 : wtx.fInMempool = false;
512 1 : return fakeIndex;
513 : }
514 :
515 1 : void fakeMempoolInsertion(const CTransactionRef& wtxCredit)
516 : {
517 1 : CTxMemPoolEntry entry(wtxCredit, 0, 0, 0, false, 0);
518 2 : LOCK(mempool.cs);
519 1 : mempool.mapTx.insert(entry);
520 1 : }
521 :
522 2 : CWalletTx& BuildAndLoadTxToWallet(const std::vector<CTxIn>& vin,
523 : const std::vector<CTxOut>& vout,
524 : CWallet& wallet)
525 : {
526 4 : CMutableTransaction mTx;
527 2 : mTx.vin = vin;
528 2 : mTx.vout = vout;
529 4 : CTransaction tx(mTx);
530 4 : CWalletTx wtx(&wallet, MakeTransactionRef(tx));
531 2 : wallet.LoadToWallet(wtx);
532 4 : return wallet.mapWallet.at(tx.GetHash());
533 : }
534 :
535 1 : CWalletTx& ReceiveBalanceWith(const std::vector<CTxOut>& vout,
536 : CWallet& wallet)
537 : {
538 2 : std::vector<CTxIn> vin;
539 3 : vin.emplace_back(CTxIn(COutPoint(uint256(), 999)));
540 2 : return BuildAndLoadTxToWallet(vin, vout, wallet);
541 : }
542 :
543 2 : void CheckBalances(const CWalletTx& tx,
544 : const CAmount& nCreditAll,
545 : const CAmount& nCreditSpendable,
546 : const CAmount& nAvailableCredit,
547 : const CAmount& nDebitAll,
548 : const CAmount& nDebitSpendable)
549 : {
550 2 : BOOST_CHECK_EQUAL(tx.GetCredit(ISMINE_ALL), nCreditAll);
551 2 : BOOST_CHECK_EQUAL(tx.GetCredit(ISMINE_SPENDABLE), nCreditSpendable);
552 4 : BOOST_CHECK(tx.IsAmountCached(CWalletTx::CREDIT, ISMINE_SPENDABLE));
553 2 : BOOST_CHECK_EQUAL(tx.GetAvailableCredit(), nAvailableCredit);
554 4 : BOOST_CHECK(tx.IsAmountCached(CWalletTx::AVAILABLE_CREDIT, ISMINE_SPENDABLE));
555 2 : BOOST_CHECK_EQUAL(tx.GetDebit(ISMINE_ALL), nDebitAll);
556 2 : BOOST_CHECK_EQUAL(tx.GetDebit(ISMINE_SPENDABLE), nDebitSpendable);
557 4 : BOOST_CHECK(tx.IsAmountCached(CWalletTx::DEBIT, ISMINE_SPENDABLE));
558 2 : }
559 :
560 : /**
561 : * Validates the correct behaviour of the CWalletTx "standard" balance methods.
562 : * (where "standard" is defined by direct P2PKH scripts, no P2CS contracts nor other types)
563 : *
564 : * 1) CWalletTx::GetCredit.
565 : * 2) CWalletTx::GetDebit.
566 : * 4) CWalletTx::GetAvailableCredit
567 : * 3) CWallet::GetUnconfirmedBalance.
568 : */
569 2 : BOOST_AUTO_TEST_CASE(cached_balances_tests)
570 : {
571 : // 1) Receive balance from an external source and verify:
572 : // * GetCredit(ISMINE_ALL) correctness (must be equal to 'nCredit' amount)
573 : // * GetCredit(ISMINE_SPENDABLE) correctness (must be equal to ISMINE_ALL) + must be cached.
574 : // * GetAvailableCredit() correctness (must be equal to ISMINE_ALL)
575 : // * GetDebit(ISMINE_ALL) correctness (must be 0)
576 : // * wallet.GetUnconfirmedBalance() correctness (must be equal 'nCredit')
577 :
578 : // 2) Confirm the tx and verify:
579 : // * wallet.GetUnconfirmedBalance() correctness (must be 0)
580 : // * GetAvailableCredit() correctness (must be equal to (1) ISMINE_ALL)
581 :
582 : // 3) Spend one of the two outputs of the receiving tx to an external source
583 : // and verify:
584 : // * creditTx.GetAvailableCredit() correctness (must be equal to 'nCredit' / 2) + must be cached.
585 : // * debitTx.GetDebit(ISMINE_ALL) correctness (must be equal to 'nCredit' / 2)
586 : // * debitTx.GetDebit(ISMINE_SPENDABLE) correctness (must be equal to 'nCredit' / 2) + must be cached.
587 : // * debitTx.GetAvailableCredit() correctness (must be 0).
588 :
589 1 : CAmount nCredit = 20 * COIN;
590 :
591 : // Setup wallet
592 2 : CWallet wallet("testWallet1", WalletDatabase::CreateMock());
593 1 : bool fFirstRun;
594 1 : BOOST_CHECK_EQUAL(wallet.LoadWallet(fFirstRun), DB_LOAD_OK);
595 3 : LOCK2(cs_main, wallet.cs_wallet);
596 1 : wallet.SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL);
597 1 : wallet.SetupSPKM(false);
598 2 : wallet.SetLastBlockProcessed(chainActive.Tip());
599 :
600 : // Receive balance from an external source
601 2 : auto res = wallet.getNewAddress("receiving_address");
602 1 : BOOST_ASSERT(res);
603 2 : CTxDestination receivingAddr = *res.getObjResult();
604 2 : CTxOut creditOut(nCredit/2, GetScriptForDestination(receivingAddr));
605 3 : CWalletTx& wtxCredit = ReceiveBalanceWith({creditOut, creditOut},wallet);
606 :
607 : // Validates (1)
608 1 : CheckBalances(
609 : wtxCredit,
610 : nCredit, // CREDIT-ISMINE_ALL
611 : nCredit, // CREDIT-ISMINE_SPENDABLE
612 : nCredit, // AVAILABLE_CREDIT
613 2 : 0, // DEBIT-ISMINE_ALL
614 1 : 0 // DEBIT-ISMINE_SPENDABLE
615 : );
616 :
617 : // GetUnconfirmedBalance requires tx in mempool.
618 1 : fakeMempoolInsertion(wtxCredit.tx);
619 1 : wtxCredit.fInMempool = true;
620 1 : BOOST_CHECK_EQUAL(wallet.GetUnconfirmedBalance(), nCredit);
621 :
622 : // 2) Confirm tx and verify
623 1 : SimpleFakeMine(wtxCredit, wallet);
624 1 : BOOST_CHECK_EQUAL(wallet.GetUnconfirmedBalance(), 0);
625 1 : BOOST_CHECK_EQUAL(wtxCredit.GetAvailableCredit(), nCredit);
626 :
627 : // 3) Spend one of the two outputs of the receiving tx to an external source and verify.
628 : // Create debit transaction.
629 1 : CAmount nDebit = nCredit / 2;
630 3 : std::vector<CTxIn> vinDebit = {CTxIn(COutPoint(wtxCredit.GetHash(), 0))};
631 2 : CKey key;
632 1 : key.MakeNewKey(true);
633 3 : std::vector<CTxOut> voutDebit = {CTxOut(nDebit, GetScriptForDestination(key.GetPubKey().GetID()))};
634 1 : CWalletTx& wtxDebit = BuildAndLoadTxToWallet(vinDebit, voutDebit, wallet);
635 :
636 : // Validates (3)
637 :
638 : // First the debit tx
639 1 : CheckBalances(
640 : wtxDebit,
641 2 : 0, // CREDIT-ISMINE_ALL
642 2 : 0, // CREDIT-ISMINE_SPENDABLE
643 1 : 0, // AVAILABLE_CREDIT
644 : nDebit, // DEBIT-ISMINE_ALL
645 : nDebit // DEBIT-ISMINE_SPENDABLE
646 : );
647 :
648 : // Secondly the prev credit tx update
649 :
650 : // One output spent, the other one not. Force available credit recalculation.
651 : // If we don't request it, it will not happen.
652 1 : BOOST_CHECK_EQUAL(wtxCredit.GetAvailableCredit(false), nCredit - nDebit);
653 2 : BOOST_CHECK(wtxCredit.IsAmountCached(CWalletTx::AVAILABLE_CREDIT, ISMINE_SPENDABLE));
654 :
655 1 : }
656 :
657 : BOOST_AUTO_TEST_SUITE_END()
|