LCOV - code coverage report
Current view: top level - src/wallet/test - wallet_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 345 345 100.0 %
Date: 2025-02-23 09:33:43 Functions: 21 21 100.0 %

          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()

Generated by: LCOV version 1.14