Line data Source code
1 : // Copyright (c) 2020-2021 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/wallet_test_fixture.h" 6 : 7 : #include "primitives/block.h" 8 : #include "sapling/transaction_builder.h" 9 : #include "sapling/sapling_operation.h" 10 : #include "wallet/wallet.h" 11 : 12 : #include <boost/test/unit_test.hpp> 13 : 14 : /* 15 : * A text fixture with a preloaded 100-blocks regtest chain, with sapling activating at block 101, 16 : * and a wallet containing the key used for the coinbase outputs. 17 : */ 18 : struct TestSaplingChainSetup: public TestChain100Setup 19 : { 20 : std::unique_ptr<CWallet> pwalletMain; 21 : 22 1 : TestSaplingChainSetup() : TestChain100Setup() 23 : { 24 1 : initZKSNARKS(); // init zk-snarks lib 25 : 26 1 : bool fFirstRun; 27 1 : pwalletMain = std::make_unique<CWallet>("testWallet", WalletDatabase::CreateMock()); 28 1 : pwalletMain->LoadWallet(fFirstRun); 29 1 : RegisterValidationInterface(pwalletMain.get()); 30 : 31 1 : int SAPLING_ACTIVATION_HEIGHT = 101; 32 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V5_0, SAPLING_ACTIVATION_HEIGHT); 33 : 34 : // setup wallet 35 1 : { 36 1 : LOCK(pwalletMain->cs_wallet); 37 1 : pwalletMain->SetMinVersion(FEATURE_SAPLING); 38 2 : gArgs.ForceSetArg("-keypool", "5"); 39 1 : pwalletMain->SetupSPKM(true); 40 : 41 : // import coinbase key used to generate the 100-blocks chain 42 2 : BOOST_CHECK(pwalletMain->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey())); 43 : } 44 2 : WalletRescanReserver reserver(pwalletMain.get()); 45 2 : BOOST_CHECK(reserver.reserve()); 46 1 : pwalletMain->RescanFromTime(0, reserver, true /* update */); 47 1 : } 48 : 49 1 : ~TestSaplingChainSetup() 50 2 : { 51 1 : UnregisterValidationInterface(pwalletMain.get()); 52 1 : } 53 : }; 54 : 55 : BOOST_FIXTURE_TEST_SUITE(wallet_sapling_transactions_validations_tests, TestSaplingChainSetup) 56 : 57 3 : static SaplingOperation createOperationAndBuildTx(std::unique_ptr<CWallet>& pwallet, 58 : std::vector<SendManyRecipient> recipients, 59 : bool selectTransparentCoins) 60 : { 61 : // Create the operation 62 3 : SaplingOperation operation(Params().GetConsensus(), pwallet.get()); 63 3 : auto operationResult = operation.setRecipients(recipients) 64 : ->setSelectTransparentCoins(selectTransparentCoins) 65 3 : ->setSelectShieldedCoins(!selectTransparentCoins) 66 3 : ->build(); 67 3 : BOOST_ASSERT_MSG(operationResult, operationResult.getError().c_str()); 68 : 69 6 : CValidationState state; 70 3 : BOOST_ASSERT_MSG( 71 : CheckTransaction(operation.getFinalTx(), state, true), 72 : "Invalid Sapling transaction"); 73 6 : return operation; 74 : } 75 : 76 : // Test double spend notes in the mempool and in blocks. 77 2 : BOOST_AUTO_TEST_CASE(test_in_block_and_mempool_notes_double_spend) 78 : { 79 1 : auto ret = pwalletMain->getNewAddress("coinbase"); 80 2 : BOOST_CHECK(ret); 81 2 : CTxDestination coinbaseDest = *ret.getObjResult(); 82 1 : BOOST_ASSERT_MSG(ret, "cannot create address"); 83 1 : BOOST_ASSERT_MSG(IsValidDestination(coinbaseDest), "invalid destination"); 84 1 : BOOST_ASSERT_MSG(IsMine(*pwalletMain, coinbaseDest), "destination not from wallet"); 85 : 86 : // create the chain 87 3 : int tipHeight = WITH_LOCK(cs_main, return chainActive.Tip()->nHeight); 88 1 : BOOST_CHECK_EQUAL(tipHeight, 100); 89 2 : const CScript& scriptPubKey = GetScriptForDestination(coinbaseDest); 90 1 : int nGenerateBlocks = 110; 91 11 : for (int i = tipHeight; i < nGenerateBlocks; ++i) { 92 10 : CreateAndProcessBlock({}, scriptPubKey); 93 10 : SyncWithValidationInterfaceQueue(); 94 : } 95 : 96 : // Verify that we are at block 110 97 3 : tipHeight = WITH_LOCK(cs_main, return chainActive.Tip()->nHeight); 98 1 : BOOST_CHECK_EQUAL(tipHeight, nGenerateBlocks); 99 : // Verify that the wallet has all of the coins 100 1 : BOOST_CHECK_EQUAL(pwalletMain->GetAvailableBalance(), CAmount(250 * COIN * 10)); // 10 blocks available 101 1 : BOOST_CHECK_EQUAL(pwalletMain->GetImmatureBalance(), CAmount(250 * COIN * 100)); // 100 blocks immature 102 : 103 : // Now that we have the chain, let's shield 100 PIVs 104 : // single recipient 105 2 : std::vector<SendManyRecipient> recipients; 106 1 : libzcash::SaplingPaymentAddress pa = pwalletMain->GenerateNewSaplingZKey("sapling1"); 107 1 : recipients.emplace_back(pa, CAmount(100 * COIN), "", false); 108 : 109 : // Create the operation and build the transaction 110 1 : SaplingOperation operation = createOperationAndBuildTx(pwalletMain, recipients, true); 111 : // broadcast the tx to the network 112 2 : std::string retHash; 113 1 : BOOST_ASSERT_MSG(operation.send(retHash), "error committing and broadcasting the transaction"); 114 : 115 : // Generate a five blocks to fully confirm the tx and test balance 116 6 : for (int i = 1; i <= 5; ++i) { 117 5 : CreateAndProcessBlock({}, scriptPubKey, false /*fNoMempoolTx*/); 118 : } 119 1 : SyncWithValidationInterfaceQueue(); 120 1 : BOOST_CHECK_EQUAL(pwalletMain->GetAvailableShieldedBalance(), CAmount(100 * COIN)); // 100 shield PIVs 121 1 : BOOST_CHECK_EQUAL(pwalletMain->GetUnconfirmedShieldedBalance(), CAmount(0)); // 0 shield PIVs 122 : 123 : // ############################################## 124 : // Context set! 125 : // Now let's try to double spend the same note twice in the same block 126 : 127 : // first generate a valid tx spending only one note 128 : // Create the operation and build the transaction 129 2 : auto res = pwalletMain->getNewAddress("receiveValid"); 130 2 : BOOST_CHECK(res); 131 2 : CTxDestination tDest2 = *res.getObjResult(); 132 2 : std::vector<SendManyRecipient> recipients2; 133 1 : recipients2.emplace_back(tDest2, CAmount(90 * COIN), false); 134 1 : SaplingOperation operation2 = createOperationAndBuildTx(pwalletMain, recipients2, false); 135 : 136 : // Create a second transaction that spends the same note with a different output now 137 2 : res = pwalletMain->getNewAddress("receiveInvalid"); 138 2 : BOOST_CHECK(res); 139 2 : CTxDestination tDest3 = *res.getObjResult();; 140 2 : std::vector<SendManyRecipient> recipients3; 141 1 : recipients3.emplace_back(tDest3, CAmount(5 * COIN), false); 142 1 : SaplingOperation operation3 = createOperationAndBuildTx(pwalletMain, recipients3, false); 143 : 144 : // Now that both transactions were created, broadcast the first one 145 2 : std::string retTxHash2; 146 2 : BOOST_CHECK(operation2.send(retTxHash2)); 147 : 148 : // Now broadcast the second one, this one should fail when tried to enter in the mempool as there is already another note spending the same nullifier 149 2 : std::string retTxHash3; 150 2 : auto opResult3 = operation3.send(retTxHash3); 151 2 : BOOST_CHECK(!opResult3); 152 4 : BOOST_CHECK(opResult3.getError().find("bad-txns-nullifier-double-spent")); 153 : 154 : // let's now test it inside a block 155 : // create the block with the two transactions and test validity 156 3 : const CBlock& block = CreateAndProcessBlock({operation3.getFinalTx()}, scriptPubKey, false /*fNoMempoolTx*/); 157 1 : SyncWithValidationInterfaceQueue(); 158 : 159 1 : { 160 2 : LOCK(cs_main); 161 : // Same tip as before, no block connection 162 3 : BOOST_CHECK(chainActive.Tip()->GetBlockHash() != block.GetHash()); 163 2 : BOOST_ASSERT_MSG(chainActive.Tip()->nHeight, 115); 164 : } 165 1 : } 166 : 167 : BOOST_AUTO_TEST_SUITE_END()