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

          Line data    Source code
       1             : // Copyright (c) 2020-2022 The PIVX Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or https://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include "wallet/test/pos_test_fixture.h"
       6             : 
       7             : #include "blockassembler.h"
       8             : #include "coincontrol.h"
       9             : #include "util/blockstatecatcher.h"
      10             : #include "blocksignature.h"
      11             : #include "consensus/merkle.h"
      12             : #include "primitives/block.h"
      13             : #include "script/sign.h"
      14             : #include "test/util/blocksutil.h"
      15             : #include "wallet/wallet.h"
      16             : 
      17             : #include <boost/test/unit_test.hpp>
      18             : 
      19             : BOOST_AUTO_TEST_SUITE(pos_validations_tests)
      20             : 
      21           2 : void reSignTx(CMutableTransaction& mtx,
      22             :               const std::vector<CTxOut>& txPrevOutputs,
      23             :               CWallet* wallet)
      24             : {
      25           4 :     CTransaction txNewConst(mtx);
      26           5 :     for (int index=0; index < (int) txPrevOutputs.size(); index++) {
      27           3 :         const CTxOut& prevOut = txPrevOutputs.at(index);
      28           6 :         SignatureData sigdata;
      29           6 :         BOOST_ASSERT(ProduceSignature(
      30             :                 TransactionSignatureCreator(wallet, &txNewConst, index, prevOut.nValue, SIGHASH_ALL),
      31             :                 prevOut.scriptPubKey,
      32             :                 sigdata,
      33             :                 txNewConst.GetRequiredSigVersion(),
      34             :                 true));
      35           3 :         UpdateTransaction(mtx, index, sigdata);
      36             :     }
      37           2 : }
      38             : 
      39           2 : BOOST_FIXTURE_TEST_CASE(coinstake_tests, TestPoSChainSetup)
      40             : {
      41             :     // Verify that we are at block 251
      42           3 :     BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Tip()->nHeight), 250);
      43           1 :     SyncWithValidationInterfaceQueue();
      44             : 
      45             :     // Let's create the block
      46           1 :     std::vector<CStakeableOutput> availableCoins;
      47           2 :     BOOST_CHECK(pwalletMain->StakeableCoins(&availableCoins));
      48           1 :     std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(
      49           2 :             Params(), false).CreateNewBlock(CScript(),
      50             :                                             pwalletMain.get(),
      51             :                                             true,
      52             :                                             &availableCoins,
      53           2 :                                             true);
      54           2 :     std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(pblocktemplate->block);
      55           2 :     BOOST_CHECK(pblock->IsProofOfStake());
      56             : 
      57             :     // Add a second input to a coinstake
      58           2 :     CMutableTransaction mtx(*pblock->vtx[1]);
      59           1 :     const CStakeableOutput& in2 = availableCoins.back();
      60           1 :     availableCoins.pop_back();
      61           2 :     CTxIn vin2(in2.tx->GetHash(), in2.i);
      62           1 :     mtx.vin.emplace_back(vin2);
      63             : 
      64           2 :     CTxOut prevOutput1 = pwalletMain->GetWalletTx(mtx.vin[0].prevout.hash)->tx->vout[mtx.vin[0].prevout.n];
      65           4 :     std::vector<CTxOut> txPrevOutputs{prevOutput1, in2.tx->tx->vout[in2.i]};
      66             : 
      67           1 :     reSignTx(mtx, txPrevOutputs, pwalletMain.get());
      68           2 :     pblock->vtx[1] = MakeTransactionRef(mtx);
      69           1 :     pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
      70           2 :     BOOST_CHECK(SignBlock(*pblock, *pwalletMain));
      71           1 :     ProcessBlockAndCheckRejectionReason(pblock, "bad-cs-multi-inputs", 250);
      72             : 
      73             :     // Check multi-empty-outputs now
      74           2 :     pblock = std::make_shared<CBlock>(pblocktemplate->block);
      75           1 :     mtx = CMutableTransaction(*pblock->vtx[1]);
      76        1000 :     for (int i = 0; i < 999; ++i) {
      77         999 :         mtx.vout.emplace_back();
      78         999 :         mtx.vout.back().SetEmpty();
      79             :     }
      80           2 :     reSignTx(mtx, {prevOutput1}, pwalletMain.get());
      81           2 :     pblock->vtx[1] = MakeTransactionRef(mtx);
      82           1 :     pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
      83           2 :     BOOST_CHECK(SignBlock(*pblock, *pwalletMain));
      84           1 :     ProcessBlockAndCheckRejectionReason(pblock, "bad-txns-vout-empty", 250);
      85             : 
      86             :     // Now connect the proper block
      87           2 :     pblock = std::make_shared<CBlock>(pblocktemplate->block);
      88           1 :     ProcessNewBlock(pblock, nullptr);
      89           3 :     BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Tip()->GetBlockHash()), pblock->GetHash());
      90           1 : }
      91             : 
      92          13 : CTransaction CreateAndCommitTx(CWallet* pwalletMain, const CTxDestination& dest, CAmount destValue, CCoinControl* coinControl = nullptr)
      93             : {
      94          26 :     CTransactionRef txNew;
      95          26 :     CReserveKey reservekey(pwalletMain);
      96          13 :     CAmount nFeeRet = 0;
      97          26 :     std::string strFailReason;
      98             :     // The minimum depth (100) required to spend coinbase outputs is calculated from the current chain tip.
      99             :     // Since this transaction could be included in a fork block, at a lower height, this may result in
     100             :     // selecting non-yet-available inputs, and thus creating non-connectable blocks due to premature-cb-spend.
     101             :     // So, to be sure, only select inputs which are more than 120-blocks deep in the chain.
     102          13 :     BOOST_ASSERT(pwalletMain->CreateTransaction(GetScriptForDestination(dest),
     103             :                                    destValue,
     104             :                                    txNew,
     105             :                                    reservekey,
     106             :                                    nFeeRet,
     107             :                                    strFailReason,
     108             :                                    coinControl,
     109             :                                    true, /* sign*/
     110             :                                    false, /*fIncludeDelegated*/
     111             :                                    nullptr, /*fStakeDelegationVoided*/
     112             :                                    0, /*nExtraSize*/
     113             :                                    120 /*nMinDepth*/));
     114          26 :     pwalletMain->CommitTransaction(txNew, reservekey, nullptr);
     115          26 :     return *txNew;
     116             : }
     117             : 
     118           5 : COutPoint GetOutpointWithAmount(const CTransaction& tx, CAmount outpointValue)
     119             : {
     120           7 :     for (size_t i = 0; i < tx.vout.size(); i++) {
     121           7 :         if (tx.vout[i].nValue == outpointValue) {
     122           5 :             return COutPoint(tx.GetHash(), i);
     123             :         }
     124             :     }
     125           0 :     BOOST_ASSERT_MSG(false, "error in test, no output in tx for value");
     126             :     return {};
     127             : }
     128             : 
     129        6294 : static bool IsSpentOnFork(const COutput& coin, std::initializer_list<std::shared_ptr<CBlock>> forkchain = {})
     130             : {
     131        9010 :     for (const auto& block : forkchain) {
     132        2738 :         const auto& usedOutput = block->vtx[1]->vin.at(0).prevout;
     133        2738 :         if (coin.tx->GetHash() == usedOutput.hash && coin.i == (int)usedOutput.n) {
     134             :             // spent on fork
     135             :             return true;
     136             :         }
     137             :     }
     138             :     return false;
     139             : }
     140             : 
     141          49 : std::shared_ptr<CBlock> CreateBlockInternal(CWallet* pwalletMain, const std::vector<CMutableTransaction>& txns = {},
     142             :                                             CBlockIndex* customPrevBlock = nullptr,
     143             :                                             std::initializer_list<std::shared_ptr<CBlock>> forkchain = {})
     144             : {
     145          49 :     std::vector<CStakeableOutput> availableCoins;
     146          98 :     BOOST_CHECK(pwalletMain->StakeableCoins(&availableCoins));
     147             : 
     148             :     // Remove any utxo which is not deeper than 120 blocks (for the same reasoning
     149             :     // used when selecting tx inputs in CreateAndCommitTx)
     150             :     // Also, as the wallet is not prepared to follow several chains at the same time,
     151             :     // need to manually remove from the stakeable utxo set every already used
     152             :     // coinstake inputs on the previous blocks of the parallel chain so they
     153             :     // are not used again.
     154          49 :     for (auto it = availableCoins.begin(); it != availableCoins.end() ;) {
     155        7273 :         if (it->nDepth <= 120 || IsSpentOnFork(*it, forkchain)) {
     156        1002 :             it = availableCoins.erase(it);
     157             :         } else {
     158       13593 :             it++;
     159             :         }
     160             :     }
     161             : 
     162          49 :     std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(
     163          49 :             Params(), false).CreateNewBlock(CScript(),
     164             :                                             pwalletMain,
     165             :                                             true,
     166             :                                             &availableCoins,
     167             :                                             true,
     168             :                                             false,
     169             :                                             customPrevBlock,
     170          98 :                                             false);
     171          49 :     BOOST_ASSERT(pblocktemplate);
     172          49 :     auto pblock = std::make_shared<CBlock>(pblocktemplate->block);
     173          49 :     if (!txns.empty()) {
     174          31 :         for (const auto& tx : txns) {
     175          32 :             pblock->vtx.emplace_back(MakeTransactionRef(tx));
     176             :         }
     177          15 :         pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
     178          15 :         assert(SignBlock(*pblock, *pwalletMain));
     179             :     }
     180          98 :     return pblock;
     181             : }
     182             : 
     183           1 : static COutput GetUnspentCoin(CWallet* pwallet, std::initializer_list<std::shared_ptr<CBlock>> forkchain = {})
     184             : {
     185           1 :     std::vector<COutput> availableCoins;
     186           1 :     CWallet::AvailableCoinsFilter coinsFilter;
     187           1 :     coinsFilter.minDepth = 120;
     188           2 :     BOOST_CHECK(pwallet->AvailableCoins(&availableCoins, nullptr, coinsFilter));
     189           1 :     for (const auto& coin : availableCoins) {
     190           1 :         if (!IsSpentOnFork(coin, forkchain)) {
     191           2 :             return coin;
     192             :         }
     193             :     }
     194           0 :     throw std::runtime_error("Unspent coin not found");
     195             : }
     196             : 
     197           2 : BOOST_FIXTURE_TEST_CASE(created_on_fork_tests, TestPoSChainSetup)
     198             : {
     199             :     // Let's create few more PoS blocks
     200          31 :     for (int i=0; i<30; i++) {
     201          60 :         std::shared_ptr<CBlock> pblock = CreateBlockInternal(pwalletMain.get());
     202          90 :         BOOST_CHECK(ProcessNewBlock(pblock, nullptr));
     203             :     }
     204             : 
     205             :     /*
     206             :     Chains diagram:
     207             :     A -- B -- C -- D -- E -- F -- G -- H -- I
     208             :               \
     209             :                 -- D1 -- E1 -- F1
     210             :                      \
     211             :                       -- E2 -- F2
     212             :               \
     213             :                 -- D3 -- E3 -- F3
     214             :                            \
     215             :                             -- F3_1 -- G3 -- H3 -- I3
     216             :     */
     217             : 
     218             :     // Tests:
     219             :     // 1) coins created in D1 and spent in E1. --> should pass
     220             :     // 2) coins created in E, being spent in D4 --> should fail.
     221             :     // 3) coins created and spent in E2, being double spent in F2. --> should fail
     222             :     // 4) coins created in D and spent in E3. --> should fail
     223             :     // 5) coins create in D, spent in F and then double spent in F3. --> should fail
     224             :     // 6) coins created in G and G3, being spent in H and H3 --> should pass.
     225             :     // 7) use coinstake on different chains --> should pass.
     226             : 
     227             :     // Let's create block C with a valid cTx
     228           2 :     auto cTx = CreateAndCommitTx(pwalletMain.get(), *pwalletMain->getNewAddress("").getObjResult(), 249 * COIN);
     229           1 :     const auto& cTx_out = GetOutpointWithAmount(cTx, 249 * COIN);
     230           3 :     WITH_LOCK(pwalletMain->cs_wallet, pwalletMain->LockCoin(cTx_out));
     231           4 :     std::shared_ptr<CBlock> pblockC = CreateBlockInternal(pwalletMain.get(), {cTx});
     232           3 :     BOOST_CHECK(ProcessNewBlock(pblockC, nullptr));
     233             : 
     234             :     // Create block D with a valid dTx
     235           3 :     auto dTx = CreateAndCommitTx(pwalletMain.get(), *pwalletMain->getNewAddress("").getObjResult(), 249 * COIN);
     236           1 :     auto dTxOutPoint = GetOutpointWithAmount(dTx, 249 * COIN);
     237           3 :     WITH_LOCK(pwalletMain->cs_wallet, pwalletMain->LockCoin(dTxOutPoint));
     238           4 :     std::shared_ptr<CBlock> pblockD = CreateBlockInternal(pwalletMain.get(), {dTx});
     239             : 
     240             :     // Create D1 forked block that connects a new tx
     241           3 :     auto dest = pwalletMain->getNewAddress("").getObjResult();
     242           2 :     auto d1Tx = CreateAndCommitTx(pwalletMain.get(), *dest, 200 * COIN);
     243           4 :     std::shared_ptr<CBlock> pblockD1 = CreateBlockInternal(pwalletMain.get(), {d1Tx});
     244             : 
     245             :     // Process blocks
     246           1 :     ProcessNewBlock(pblockD, nullptr);
     247           1 :     ProcessNewBlock(pblockD1, nullptr);
     248           5 :     BOOST_CHECK(WITH_LOCK(cs_main, return chainActive.Tip()->GetBlockHash() ==  pblockD->GetHash()));
     249             : 
     250             :     // Ensure that the coin does not exist in the main chain
     251           1 :     const Coin& utxo = pcoinsTip->AccessCoin(COutPoint(d1Tx.GetHash(), 0));
     252           2 :     BOOST_CHECK(utxo.out.IsNull());
     253             : 
     254             :     // Create valid block E
     255           2 :     auto eTx = CreateAndCommitTx(pwalletMain.get(), *dest, 200 * COIN);
     256           4 :     std::shared_ptr<CBlock> pblockE = CreateBlockInternal(pwalletMain.get(), {eTx});
     257           3 :     BOOST_CHECK(ProcessNewBlock(pblockE, nullptr));
     258             : 
     259             :     // #################################################
     260             :     // ### 1) -> coins created in D' and spent in E' ###
     261             :     // #################################################
     262             : 
     263             :     // Create tx spending the previously created tx on the forked chain
     264           2 :     CCoinControl coinControl;
     265           1 :     coinControl.fAllowOtherInputs = false;
     266           1 :     coinControl.Select(COutPoint(d1Tx.GetHash(), 0), d1Tx.vout[0].nValue);
     267           2 :     auto e1Tx = CreateAndCommitTx(pwalletMain.get(), *dest, d1Tx.vout[0].nValue - 0.1 * COIN, &coinControl);
     268             : 
     269           1 :     CBlockIndex* pindexPrev = mapBlockIndex.at(pblockD1->GetHash());
     270           6 :     std::shared_ptr<CBlock> pblockE1 = CreateBlockInternal(pwalletMain.get(), {e1Tx}, pindexPrev, {pblockD1});
     271           3 :     BOOST_CHECK(ProcessNewBlock(pblockE1, nullptr));
     272             : 
     273             :     // #################################################################
     274             :     // ### 2) coins created in E, being spent in D4 --> should fail. ###
     275             :     // #################################################################
     276             : 
     277           1 :     coinControl.UnSelectAll();
     278           1 :     coinControl.Select(GetOutpointWithAmount(eTx, 200 * COIN), 200 * COIN);
     279           1 :     coinControl.fAllowOtherInputs = false;
     280           2 :     auto D4_tx1 = CreateAndCommitTx(pwalletMain.get(), *dest, 199 * COIN, &coinControl);
     281           5 :     std::shared_ptr<CBlock> pblockD4 = CreateBlockInternal(pwalletMain.get(), {D4_tx1}, mapBlockIndex.at(pblockC->GetHash()));
     282           3 :     BOOST_CHECK(!ProcessNewBlock(pblockD4, nullptr));
     283             : 
     284             :     // #####################################################################
     285             :     // ### 3) -> coins created and spent in E2, being double spent in F2 ###
     286             :     // #####################################################################
     287             : 
     288             :     // Create block E2 with E2_tx1 and E2_tx2. Where E2_tx2 is spending the outputs of E2_tx1
     289           2 :     CCoinControl coinControlE2;
     290           1 :     coinControlE2.Select(cTx_out, 249 * COIN);
     291           2 :     auto E2_tx1 = CreateAndCommitTx(pwalletMain.get(), *dest, 200 * COIN, &coinControlE2);
     292             : 
     293           1 :     coinControl.UnSelectAll();
     294           1 :     coinControl.Select(GetOutpointWithAmount(E2_tx1, 200 * COIN), 200 * COIN);
     295           1 :     coinControl.fAllowOtherInputs = false;
     296           1 :     auto E2_tx2 = CreateAndCommitTx(pwalletMain.get(), *dest, 199 * COIN, &coinControl);
     297             : 
     298           1 :     std::shared_ptr<CBlock> pblockE2 = CreateBlockInternal(pwalletMain.get(), {E2_tx1, E2_tx2},
     299           7 :                                                            pindexPrev, {pblockD1});
     300           3 :     BOOST_CHECK(ProcessNewBlock(pblockE2, nullptr));
     301             : 
     302             :     // Create block with F2_tx1 spending E2_tx1 again.
     303           2 :     auto F2_tx1 = CreateAndCommitTx(pwalletMain.get(), *dest, 199 * COIN, &coinControl);
     304             : 
     305           1 :     pindexPrev = mapBlockIndex.at(pblockE2->GetHash());
     306           1 :     std::shared_ptr<CBlock> pblock5Forked = CreateBlockInternal(pwalletMain.get(), {F2_tx1},
     307           7 :                                                                 pindexPrev, {pblockD1, pblockE2});
     308           2 :     BlockStateCatcherWrapper stateCatcher(pblock5Forked->GetHash());
     309           1 :     stateCatcher.registerEvent();
     310           3 :     BOOST_CHECK(!ProcessNewBlock(pblock5Forked, nullptr));
     311           2 :     BOOST_CHECK(stateCatcher.get().found);
     312           2 :     BOOST_CHECK(!stateCatcher.get().state.IsValid());
     313           3 :     BOOST_CHECK_EQUAL(stateCatcher.get().state.GetRejectReason(), "bad-txns-inputs-spent-fork-post-split");
     314             : 
     315             :     // #############################################
     316             :     // ### 4) coins created in D and spent in E3 ###
     317             :     // #############################################
     318             : 
     319             :     // First create D3
     320           1 :     pindexPrev = mapBlockIndex.at(pblockC->GetHash());
     321           2 :     std::shared_ptr<CBlock> pblockD3 = CreateBlockInternal(pwalletMain.get(), {}, pindexPrev);
     322           3 :     BOOST_CHECK(ProcessNewBlock(pblockD3, nullptr));
     323             : 
     324             :     // Now let's try to spend the coins created in D in E3
     325           1 :     coinControl.UnSelectAll();
     326           1 :     coinControl.Select(dTxOutPoint, 249 * COIN);
     327           1 :     coinControl.fAllowOtherInputs = false;
     328           2 :     auto E3_tx1 = CreateAndCommitTx(pwalletMain.get(), *dest, 200 * COIN, &coinControl);
     329             : 
     330           1 :     pindexPrev = mapBlockIndex.at(pblockD3->GetHash());
     331           6 :     std::shared_ptr<CBlock> pblockE3 = CreateBlockInternal(pwalletMain.get(), {E3_tx1}, pindexPrev, {pblockD3});
     332           1 :     stateCatcher.get().clear();
     333           1 :     stateCatcher.get().setBlockHash(pblockE3->GetHash());
     334           3 :     BOOST_CHECK(!ProcessNewBlock(pblockE3, nullptr));
     335           2 :     BOOST_CHECK(stateCatcher.get().found);
     336           2 :     BOOST_CHECK(!stateCatcher.get().state.IsValid());
     337           3 :     BOOST_CHECK_EQUAL(stateCatcher.get().state.GetRejectReason(), "bad-txns-inputs-created-post-split");
     338             : 
     339             :     // ####################################################################
     340             :     // ### 5) coins create in D, spent in F and then double spent in F3 ###
     341             :     // ####################################################################
     342             : 
     343             :     // Create valid block F spending the coins created in D
     344           1 :     const auto& F_tx1 = E3_tx1;
     345           4 :     std::shared_ptr<CBlock> pblockF = CreateBlockInternal(pwalletMain.get(), {F_tx1});
     346           3 :     BOOST_CHECK(ProcessNewBlock(pblockF, nullptr));
     347           5 :     BOOST_CHECK(WITH_LOCK(cs_main, return chainActive.Tip()->GetBlockHash() ==  pblockF->GetHash()));
     348             : 
     349             :     // Create valid block E3
     350           1 :     pindexPrev = mapBlockIndex.at(pblockD3->GetHash());
     351           3 :     pblockE3 = CreateBlockInternal(pwalletMain.get(), {}, pindexPrev, {pblockD3});
     352           3 :     BOOST_CHECK(ProcessNewBlock(pblockE3, nullptr));
     353             : 
     354             :     // Now double spend F_tx1 in F3
     355           1 :     pindexPrev = mapBlockIndex.at(pblockE3->GetHash());
     356           7 :     std::shared_ptr<CBlock> pblockF3 = CreateBlockInternal(pwalletMain.get(), {F_tx1}, pindexPrev, {pblockD3, pblockE3});
     357             :     // Accepted on disk but not connected.
     358           3 :     BOOST_CHECK(ProcessNewBlock(pblockF3, nullptr));
     359           4 :     BOOST_CHECK(WITH_LOCK(cs_main, return chainActive.Tip()->GetBlockHash();) != pblockF3->GetHash());
     360             : 
     361           1 :     {
     362             :         // Trigger a rescan so the wallet cleans up its internal state.
     363           2 :         WalletRescanReserver reserver(pwalletMain.get());
     364           2 :         BOOST_CHECK(reserver.reserve());
     365           1 :         pwalletMain->RescanFromTime(0, reserver, true /* update */);
     366             :     }
     367             : 
     368             :     // ##############################################################################
     369             :     // ### 6) coins created in G and G3, being spent in H and H3 --> should pass. ###
     370             :     // ##############################################################################
     371             : 
     372             :     // First create new coins in G
     373             :     // select an input that is not already spent in D3 or E3 (since we want to spend it also in G3)
     374           3 :     const COutput& input = GetUnspentCoin(pwalletMain.get(), {pblockD3, pblockE3});
     375             : 
     376           1 :     coinControl.UnSelectAll();
     377           1 :     coinControl.Select(COutPoint(input.tx->GetHash(), input.i), input.Value());
     378           1 :     coinControl.fAllowOtherInputs = false;
     379             : 
     380           1 :     dest = pwalletMain->getNewAddress("").getObjResult();
     381           2 :     auto gTx = CreateAndCommitTx(pwalletMain.get(), *dest, 200 * COIN, &coinControl);
     382           1 :     auto gOut = GetOutpointWithAmount(gTx, 200 * COIN);
     383           4 :     std::shared_ptr<CBlock> pblockG = CreateBlockInternal(pwalletMain.get(), {gTx});
     384           3 :     BOOST_CHECK(ProcessNewBlock(pblockG, nullptr));
     385             : 
     386             :     // Now create the same coin in G3
     387           5 :     pblockF3 = CreateBlockInternal(pwalletMain.get(), {}, mapBlockIndex.at(pblockE3->GetHash()), {pblockD3, pblockE3});
     388           3 :     BOOST_CHECK(ProcessNewBlock(pblockF3, nullptr));
     389           9 :     auto pblockG3 = CreateBlockInternal(pwalletMain.get(), {gTx}, mapBlockIndex.at(pblockF3->GetHash()), {pblockD3, pblockE3, pblockF3});
     390           3 :     BOOST_CHECK(ProcessNewBlock(pblockG3, nullptr));
     391           1 :     FlushStateToDisk();
     392             : 
     393             :     // Now spend the coin in both, H and H3
     394           1 :     coinControl.UnSelectAll();
     395           1 :     coinControl.Select(gOut, 200 * COIN);
     396           1 :     coinControl.fAllowOtherInputs = false;
     397           2 :     auto hTx = CreateAndCommitTx(pwalletMain.get(), *dest, 199 * COIN, &coinControl);
     398           4 :     std::shared_ptr<CBlock> pblockH = CreateBlockInternal(pwalletMain.get(), {hTx});
     399           3 :     BOOST_CHECK(ProcessNewBlock(pblockH, nullptr));
     400           5 :     BOOST_CHECK(WITH_LOCK(cs_main, return chainActive.Tip()->GetBlockHash() ==  pblockH->GetHash()));
     401           1 :     FlushStateToDisk();
     402             : 
     403             :     // H3 now..
     404           1 :     std::shared_ptr<CBlock> pblockH3 = CreateBlockInternal(pwalletMain.get(),
     405             :                                                            {hTx},
     406           1 :                                                            mapBlockIndex.at(pblockG3->GetHash()),
     407          10 :                                                            {pblockD3, pblockE3, pblockF3, pblockG3});
     408           3 :     BOOST_CHECK(ProcessNewBlock(pblockH3, nullptr));
     409             : 
     410             :     // Try to read the forking point manually
     411           2 :     CBlock bl;
     412           3 :     BOOST_CHECK(ReadBlockFromDisk(bl, mapBlockIndex.at(pblockC->GetHash())));
     413             : 
     414             :     // Make I3 the tip now.
     415           1 :     std::shared_ptr<CBlock> pblockI3 = CreateBlockInternal(pwalletMain.get(),
     416             :                                                            {},
     417           1 :                                                            mapBlockIndex.at(pblockH3->GetHash()),
     418           9 :                                                            {pblockD3, pblockE3, pblockF3, pblockG3, pblockH3});
     419           3 :     BOOST_CHECK(ProcessNewBlock(pblockI3, nullptr));
     420           5 :     BOOST_CHECK(WITH_LOCK(cs_main, return chainActive.Tip()->GetBlockHash() ==  pblockI3->GetHash()));
     421             : 
     422             :     // And rescan the wallet on top of the new chain
     423           2 :     WalletRescanReserver reserver(pwalletMain.get());
     424           2 :     BOOST_CHECK(reserver.reserve());
     425           1 :     pwalletMain->RescanFromTime(0, reserver, true /* update */);
     426             : 
     427             :     // #################################################################################
     428             :     // ### 7) Now try to use the same coinstake on different chains --> should pass. ###
     429             :     // #################################################################################
     430             : 
     431             :     // Take I3 coinstake and use it for block I, changing its hash adding a new tx
     432           2 :     std::shared_ptr<CBlock> pblockI = std::make_shared<CBlock>(*pblockI3);
     433           2 :     auto iTx = CreateAndCommitTx(pwalletMain.get(), *dest, 1 * COIN);
     434           1 :     pblockI->vtx.emplace_back(MakeTransactionRef(iTx));
     435           1 :     pblockI->hashMerkleRoot = BlockMerkleRoot(*pblockI);
     436           1 :     assert(SignBlock(*pblockI, *pwalletMain));
     437           2 :     BOOST_CHECK(pblockI3->GetHash() != pblockI->GetHash());
     438           3 :     BOOST_CHECK(ProcessNewBlock(pblockI, nullptr));
     439           1 : }
     440             : 
     441             : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14