// Copyright (c) 2011-2016 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "gtest/gtest.h" #include "IWalletLegacy.h" #include "crypto/crypto.h" #include "CryptoNoteCore/Account.h" #include "CryptoNoteCore/Currency.h" #include "CryptoNoteCore/TransactionApi.h" #include "Logging/ConsoleLogger.h" #include "Transfers/TransfersContainer.h" #include "TransactionApiHelpers.h" using namespace CryptoNote; namespace { const size_t TEST_TRANSACTION_SPENDABLE_AGE = 1; const uint64_t TEST_OUTPUT_AMOUNT = 100; const uint64_t TEST_BLOCK_HEIGHT = 99; const uint32_t TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX = 113; const uint32_t UNCONFIRMED = std::numeric_limits::max(); const uint64_t TEST_TIMESTAMP = 1000000; class TransfersContainerKeyImage : public ::testing::Test { public: TransfersContainerKeyImage() : currency(CurrencyBuilder(logger).currency()), container(currency, TEST_TRANSACTION_SPENDABLE_AGE), account(generateAccountKeys()), txTemplate(createTransaction()) { txTemplate->getTransactionSecretKey(txSecretKey); } protected: std::vector getOutputs(uint32_t flags) { std::vector outs; container.getOutputs(outs, flags); return outs; } size_t outputsCount(uint32_t flags) { return getOutputs(flags).size(); } TransactionBlockInfo blockInfo(uint32_t height) const { return TransactionBlockInfo{ height, 1000000 }; } TestTransactionBuilder createTransactionWithFixedKey() { return TestTransactionBuilder(txTemplate->getTransactionData(), txSecretKey); } std::unique_ptr addTransactionWithFixedKey(uint32_t height, size_t inputs = 1, uint64_t amount = TEST_OUTPUT_AMOUNT, uint32_t txIndex = 0) { auto tx = createTransactionWithFixedKey(); while (inputs--) { tx.addTestInput(amount + 1); } auto outputIndex = (height == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) ? UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX : TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX; auto outInfo = tx.addTestKeyOutput(amount, outputIndex, account); auto finalTx = tx.build(); EXPECT_TRUE(container.addTransaction(TransactionBlockInfo{ height, 1000000, txIndex }, *finalTx, { outInfo })); return finalTx; } std::unique_ptr addTransaction(uint32_t height = WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) { TestTransactionBuilder tx; tx.addTestInput(TEST_OUTPUT_AMOUNT + 1); auto outputIndex = (height == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) ? UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX : TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX; auto outInfo = tx.addTestKeyOutput(TEST_OUTPUT_AMOUNT, outputIndex, account); auto finalTx = tx.build(); EXPECT_TRUE(container.addTransaction(blockInfo(height), *finalTx, { outInfo })); return finalTx; } std::unique_ptr addSpendingTransaction(const Hash& sourceTx, uint64_t height, uint32_t outputIndex, uint64_t amount = TEST_OUTPUT_AMOUNT, bool fixedKey = false) { auto outputs = container.getTransactionOutputs(sourceTx, ITransfersContainer::IncludeTypeAll | ITransfersContainer::IncludeStateUnlocked | ITransfersContainer::IncludeStateSoftLocked); EXPECT_FALSE(outputs.empty()); if (outputs.empty()) return std::unique_ptr(); TestTransactionBuilder normalTx; TestTransactionBuilder fixedTx(createTransactionWithFixedKey()); auto& tx = fixedKey ? fixedTx : normalTx; size_t inputAmount = 0; for (const auto& t : outputs) { inputAmount += t.amount; tx.addInput(account, t); } EXPECT_GE(inputAmount, amount); std::vector transfers; tx.addTestKeyOutput(amount, outputIndex); // output to some random address if (inputAmount > amount) { transfers.emplace_back(tx.addTestKeyOutput(inputAmount - amount, outputIndex + 1, account)); // change } auto finalTx = tx.build(); EXPECT_TRUE(container.addTransaction(blockInfo(height), *finalTx, transfers)); return finalTx; } Logging::ConsoleLogger logger; Currency currency; TransfersContainer container; AccountKeys account; std::unique_ptr txTemplate; SecretKey txSecretKey; }; } ///////////////////////////////////////////////////////////////////////////// // addTransaction ///////////////////////////////////////////////////////////////////////////// TEST_F(TransfersContainerKeyImage, addTransaction_addingSecondUnconfirmedTransferHidesBothUnconfirmedTransfers) { // add first transaction auto tx1b = createTransactionWithFixedKey(); auto tx2b = createTransactionWithFixedKey(); ASSERT_EQ(tx1b.getTransactionPublicKey(), tx2b.getTransactionPublicKey()); tx1b.addTestInput(TEST_OUTPUT_AMOUNT); auto tx1out = tx1b.addTestKeyOutput(TEST_OUTPUT_AMOUNT, UNCONFIRMED, account); auto tx1 = tx1b.build(); ASSERT_TRUE(container.addTransaction({ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 100000 }, *tx1, { tx1out })); ASSERT_EQ(1, container.transactionsCount()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllLocked)); ASSERT_EQ(1, outputsCount(ITransfersContainer::IncludeAllLocked)); // fill tx2 tx2b.addTestInput(TEST_OUTPUT_AMOUNT); tx2b.addTestInput(TEST_OUTPUT_AMOUNT); auto tx2out = tx2b.addTestKeyOutput(TEST_OUTPUT_AMOUNT, UNCONFIRMED, account); auto tx2 = tx2b.build(); ASSERT_EQ(tx1out.keyImage, tx2out.keyImage); ASSERT_NE(tx1->getTransactionPrefixHash(), tx2->getTransactionPrefixHash()); ASSERT_TRUE(container.addTransaction({ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 100000 }, *tx2, { tx2out })); ASSERT_EQ(2, container.transactionsCount()); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAllLocked)); // transactions' outputs should shadow one another ASSERT_EQ(0, outputsCount(ITransfersContainer::IncludeAllLocked)); } TEST_F(TransfersContainerKeyImage, addTransaction_unconfirmedTransferAddedAfterConfirmedBecomeHidden) { // fill tx1 auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); container.advanceHeight(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE); ASSERT_EQ(1, container.transactionsCount()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); ASSERT_EQ(1, outputsCount(ITransfersContainer::IncludeAllUnlocked)); auto tx2 = createTransactionWithFixedKey(); ASSERT_EQ(tx1->getTransactionPublicKey(), tx2.getTransactionPublicKey()); // fill tx2 tx2.addTestInput(TEST_OUTPUT_AMOUNT); tx2.addTestInput(TEST_OUTPUT_AMOUNT); auto tx2out = tx2.addTestKeyOutput(TEST_OUTPUT_AMOUNT, UNCONFIRMED, account); ASSERT_TRUE(container.addTransaction({ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 100000 }, *tx2.build(), { tx2out })); ASSERT_EQ(2, container.transactionsCount()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAllLocked)); ASSERT_EQ(1, outputsCount(ITransfersContainer::IncludeAllUnlocked)); ASSERT_EQ(0, outputsCount(ITransfersContainer::IncludeAllLocked)); } TEST_F(TransfersContainerKeyImage, addTransaction_unconfirmedTransferAddedAfterSpentBecomeHidden) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); container.advanceHeight(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); addSpendingTransaction(tx1->getTransactionHash(), TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAllUnlocked)); ASSERT_EQ(0, outputsCount(ITransfersContainer::IncludeAll)); addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2); ASSERT_EQ(3, container.transactionsCount()); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_EQ(0, outputsCount(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage, addTransaction_confirmedTransferAddedAfterUnconfirmedHidesUnconfirmed) { auto tx1 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllLocked)); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); container.advanceHeight(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE); ASSERT_EQ(TEST_OUTPUT_AMOUNT * 2, container.balance(ITransfersContainer::IncludeAllUnlocked)); // confirmed added ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAllLocked)); // unconfirmed shadowed } TEST_F(TransfersContainerKeyImage, addTransaction_secondConfirmedTransferAddedAsHidden_BothTransfersInTheSameBlock) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 1, TEST_OUTPUT_AMOUNT, 1); ASSERT_EQ(1, container.transactionsCount()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllLocked)); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2, 2); container.advanceHeight(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE); ASSERT_EQ(2, container.transactionsCount()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); } TEST_F(TransfersContainerKeyImage, addTransaction_secondConfirmedTransferAddedAsHidden_TransfersInDifferentBlocks) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); ASSERT_EQ(1, container.transactionsCount()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeStateSoftLocked | ITransfersContainer::IncludeTypeAll)); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 2, TEST_OUTPUT_AMOUNT * 2); container.advanceHeight(TEST_BLOCK_HEIGHT + 1 + TEST_TRANSACTION_SPENDABLE_AGE); ASSERT_EQ(2, container.transactionsCount()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); } TEST_F(TransfersContainerKeyImage, addTransaction_confirmedTransferAddedAfterSpentBecomeHidden) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); container.advanceHeight(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE); addSpendingTransaction(tx1->getTransactionHash(), TEST_BLOCK_HEIGHT + 1, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); // all spent addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE + 1, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(3, container.transactionsCount()); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); // all spent } ///////////////////////////////////////////////////////////////////////////// // markTransactionConfirmed ///////////////////////////////////////////////////////////////////////////// TEST_F(TransfersContainerKeyImage, markTransactionConfirmed_confirmingOneOfAFewUnconfirmedTransfersMakesThisTransferVisible) { addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1, TEST_OUTPUT_AMOUNT); auto tx2 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(3, container.transactionsCount()); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_TRUE(container.markTransactionConfirmed( { TEST_BLOCK_HEIGHT, 100000 }, tx2->getTransactionHash(), { TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX })); ASSERT_EQ(TEST_OUTPUT_AMOUNT * 2, container.balance(ITransfersContainer::IncludeStateSoftLocked | ITransfersContainer::IncludeTypeAll)); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeStateLocked | ITransfersContainer::IncludeTypeAll)); } TEST_F(TransfersContainerKeyImage, markTransactionConfirmed_oneConfirmedOtherUnconfirmed_confirmingOneUnconfirmed) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 1, TEST_OUTPUT_AMOUNT, 1); auto tx2 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(3, container.transactionsCount()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeStateSoftLocked | ITransfersContainer::IncludeTypeAll)); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeStateLocked | ITransfersContainer::IncludeTypeAll)); // same block, but smaller transactionIndex ASSERT_TRUE(container.markTransactionConfirmed( { TEST_BLOCK_HEIGHT, TEST_TIMESTAMP, 2 }, tx2->getTransactionHash(), { TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX })); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage, markTransactionConfirmed_oneSpentOtherUnconfirmed_confirmingOneUnconfirmed) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 1, TEST_OUTPUT_AMOUNT); container.advanceHeight(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); addSpendingTransaction(tx1->getTransactionHash(), TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); auto tx2 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); auto tx3 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_TRUE(container.markTransactionConfirmed( { TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE + 1, TEST_TIMESTAMP, 0 }, tx2->getTransactionHash(), {TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX})); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); } ///////////////////////////////////////////////////////////////////////////// // addTransaction - spending fails ///////////////////////////////////////////////////////////////////////////// class TransfersContainerKeyImage_Spend : public TransfersContainerKeyImage { public: bool spendOutput(const TransactionOutputInformation& outInfo) { auto amount = outInfo.amount; TestTransactionBuilder spendTx; spendTx.addInput(account, outInfo); spendTx.addTestKeyOutput(amount, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX); return container.addTransaction({ TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE, TEST_TIMESTAMP, 0 }, *spendTx.build(), {}); } }; TEST_F(TransfersContainerKeyImage_Spend, spendingKeyImageWithWrongAmountCausesException) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto outputs = getOutputs(ITransfersContainer::IncludeTypeAll | ITransfersContainer::IncludeStateSoftLocked); ASSERT_EQ(1, outputs.size()); // mess with amount outputs[0].amount = TEST_OUTPUT_AMOUNT * 2; ASSERT_ANY_THROW(spendOutput(outputs[0])); ASSERT_EQ(1, container.transactionsCount()); } TEST_F(TransfersContainerKeyImage_Spend, spendUnconfirmedeKeyImageCausesException) { auto tx1 = addTransactionWithFixedKey(UNCONFIRMED); auto outputs = getOutputs(ITransfersContainer::IncludeAllLocked); ASSERT_FALSE(outputs.empty()); auto tx2 = addTransactionWithFixedKey(UNCONFIRMED, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(2, container.transactionsCount()); ASSERT_ANY_THROW(spendOutput(outputs[0])); } TEST_F(TransfersContainerKeyImage_Spend, spendingUnconfirmedTransferIfThereIsConfirmedWithAnotherAmountCausesException) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto tx2 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(2, container.transactionsCount()); auto outputs = getOutputs(ITransfersContainer::IncludeTypeAll | ITransfersContainer::IncludeStateSoftLocked); ASSERT_FALSE(outputs.empty()); outputs[0].amount = TEST_OUTPUT_AMOUNT * 2; ASSERT_ANY_THROW(spendOutput(outputs[0])); } TEST_F(TransfersContainerKeyImage_Spend, spendingTransferIfThereIsSpentTransferWithAnotherAmountCausesException) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto outputs = container.getTransactionOutputs(tx1->getTransactionHash(), ITransfersContainer::IncludeAll); ASSERT_EQ(1, outputs.size()); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); addSpendingTransaction(tx1->getTransactionHash(), TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_ANY_THROW(spendOutput(outputs[0])); } ///////////////////////////////////////////////////////////////////////////// // addTransaction - spending succeeds ///////////////////////////////////////////////////////////////////////////// TEST_F(TransfersContainerKeyImage_Spend, spendingVisibleConfirmedTransfer) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_NE(tx1->getTransactionHash(), tx2->getTransactionHash()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); // spend first confirmed transaction addSpendingTransaction(tx1->getTransactionHash(), TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); auto spentOutputs = container.getSpentOutputs(); ASSERT_EQ(1, spentOutputs.size()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, spentOutputs[0].amount); ASSERT_EQ(tx1->getTransactionHash(), spentOutputs[0].transactionHash); } TEST_F(TransfersContainerKeyImage_Spend, spendHiddenConfirmedTransfer) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_NE(tx1->getTransactionHash(), tx2->getTransactionHash()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); // spend second confirmed transaction (hidden) addSpendingTransaction(tx2->getTransactionHash(), TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); auto spentOutputs = container.getSpentOutputs(); ASSERT_EQ(1, spentOutputs.size()); ASSERT_EQ(TEST_OUTPUT_AMOUNT * 2, spentOutputs[0].amount); ASSERT_EQ(tx2->getTransactionHash(), spentOutputs[0].transactionHash); } TEST_F(TransfersContainerKeyImage_Spend, spendSecondHiddenConfirmedOutput) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 2, TEST_OUTPUT_AMOUNT * 2); auto tx3 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 2, 3, TEST_OUTPUT_AMOUNT * 3); // spend third confirmed transaction (hidden) addSpendingTransaction(tx3->getTransactionHash(), TEST_BLOCK_HEIGHT + 2 + TEST_TRANSACTION_SPENDABLE_AGE, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); auto spentOutputs = container.getSpentOutputs(); ASSERT_EQ(1, spentOutputs.size()); ASSERT_EQ(TEST_OUTPUT_AMOUNT * 3, spentOutputs[0].amount); ASSERT_EQ(tx3->getTransactionHash(), spentOutputs[0].transactionHash); } TEST_F(TransfersContainerKeyImage_Spend, spendHiddenWithSameAmount_oneBlock) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT - 1); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2, 1); auto tx3 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 3, TEST_OUTPUT_AMOUNT * 2, 2); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); addSpendingTransaction(tx3->getTransactionHash(), TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); auto spentOutputs = container.getSpentOutputs(); ASSERT_EQ(1, spentOutputs.size()); ASSERT_EQ(TEST_OUTPUT_AMOUNT * 2, spentOutputs[0].amount); ASSERT_EQ(tx2->getTransactionHash(), spentOutputs[0].transactionHash); } TEST_F(TransfersContainerKeyImage_Spend, spendHiddenWithSameAmount_differentBlocks) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 3, TEST_OUTPUT_AMOUNT * 2); auto tx3 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 2, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); addSpendingTransaction(tx3->getTransactionHash(), TEST_BLOCK_HEIGHT + 2 + TEST_TRANSACTION_SPENDABLE_AGE, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); auto spentOutputs = container.getSpentOutputs(); ASSERT_EQ(1, spentOutputs.size()); ASSERT_EQ(TEST_OUTPUT_AMOUNT * 2, spentOutputs[0].amount); ASSERT_EQ(tx2->getTransactionHash(), spentOutputs[0].transactionHash); } ///////////////////////////////////////////////////////////////////////////// // remove spending transaction ///////////////////////////////////////////////////////////////////////////// class TransfersContainerKeyImage_Remove : public TransfersContainerKeyImage { public: void checkSpentOutputs(const Hash& expectedTxHash) { auto spentOutputs = container.getSpentOutputs(); ASSERT_EQ(1, spentOutputs.size()); ASSERT_EQ(expectedTxHash, spentOutputs[0].transactionHash); } }; TEST_F(TransfersContainerKeyImage_Remove, removeUnconfirmedTxSpendingVisibleOutput) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 1, TEST_OUTPUT_AMOUNT, 1); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2, 2); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); auto spendingTx = addSpendingTransaction(tx1->getTransactionHash(), WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, UNCONFIRMED, TEST_OUTPUT_AMOUNT); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_NO_FATAL_FAILURE(checkSpentOutputs(tx1->getTransactionHash())); ASSERT_TRUE(container.deleteUnconfirmedTransaction(spendingTx->getTransactionHash())); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage_Remove, removeUnconfirmedTxSpendingHiddenOut) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 1, TEST_OUTPUT_AMOUNT, 1); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2, 2); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); auto spendingTx = addSpendingTransaction(tx2->getTransactionHash(), WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, UNCONFIRMED, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_NO_FATAL_FAILURE(checkSpentOutputs(tx2->getTransactionHash())); ASSERT_TRUE(container.deleteUnconfirmedTransaction(spendingTx->getTransactionHash())); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage_Remove, removeUnconfirmedTxAfterAddingMoreTx) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT, 1, TEST_OUTPUT_AMOUNT, 1); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); auto spendingTx = addSpendingTransaction(tx1->getTransactionHash(), WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, UNCONFIRMED, TEST_OUTPUT_AMOUNT); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_TRUE(container.deleteUnconfirmedTransaction(spendingTx->getTransactionHash())); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); } ///////////////////////////////////////////////////////////////////////////// // remove unconfirmed output ///////////////////////////////////////////////////////////////////////////// TEST_F(TransfersContainerKeyImage, removingOneOfTwoUnconfirmedTransfersMakesAnotherVisible) { auto tx1 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT); auto tx2 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2, 2); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_TRUE(container.deleteUnconfirmedTransaction(tx2->getTransactionHash())); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage, removingOneOfThreeUnconfirmedTransfersDoesNotMakeVisibleAnyOfRemaining) { auto tx1 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT); auto tx2 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); auto tx3 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_TRUE(container.deleteUnconfirmedTransaction(tx2->getTransactionHash())); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage, removingOneOfTwoUnconfirmedTransfersIfThereIsConfirmedTransferDoesNotAffectBalance) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto tx2 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); auto tx3 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); ASSERT_TRUE(container.deleteUnconfirmedTransaction(tx2->getTransactionHash())); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage, removingOnlyUnconfirmedTransfersIfThereIsConfirmedTransferDoesNotAffectBalance) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto tx2 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeStateSoftLocked | ITransfersContainer::IncludeTypeAll)); ASSERT_TRUE(container.deleteUnconfirmedTransaction(tx2->getTransactionHash())); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage, removingOneOfTwoUnconfirmedTransfersIfThereIsSpentTransferDoesNotAffectBalance) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); auto tx2 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); auto tx3 = addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeStateSoftLocked | ITransfersContainer::IncludeTypeAll)); container.advanceHeight(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE); auto spendingTx = addSpendingTransaction(tx1->getTransactionHash(), TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE + 1, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_TRUE(container.deleteUnconfirmedTransaction(tx2->getTransactionHash())); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); } ///////////////////////////////////////////////////////////////////////////// // remove confirmed output ///////////////////////////////////////////////////////////////////////////// TEST_F(TransfersContainerKeyImage, removeConfirmed_oneOfThree) { addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 2, TEST_OUTPUT_AMOUNT * 2); addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 2, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); container.detach(TEST_BLOCK_HEIGHT + 2); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); } TEST_F(TransfersContainerKeyImage, removeConfirmed_oneOfTwo) { addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAllUnlocked)); container.detach(TEST_BLOCK_HEIGHT + 1); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeStateSoftLocked | ITransfersContainer::IncludeTypeAll)); } TEST_F(TransfersContainerKeyImage, removeConfirmed_revealsUnconfirmed) { addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); ASSERT_EQ(1, container.detach(TEST_BLOCK_HEIGHT).size()); ASSERT_EQ(TEST_OUTPUT_AMOUNT * 2, container.balance(ITransfersContainer::IncludeAll)); ASSERT_EQ(TEST_OUTPUT_AMOUNT * 2, container.balance(ITransfersContainer::IncludeAllLocked)); } TEST_F(TransfersContainerKeyImage, removeConfirmed_twoUnconfirmedHidden) { addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 2, TEST_OUTPUT_AMOUNT * 2); addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); ASSERT_EQ(1, container.detach(TEST_BLOCK_HEIGHT).size()); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage, removeConfirmed_twoConfirmedOneUnconfirmedHidden) { addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + 1, 2, TEST_OUTPUT_AMOUNT * 2); addTransactionWithFixedKey(WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); ASSERT_EQ(1, container.detach(TEST_BLOCK_HEIGHT + 1).size()); ASSERT_EQ(TEST_OUTPUT_AMOUNT, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage, removeConfirmed_oneSpentOneConfirmed) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); addSpendingTransaction(tx1->getTransactionHash(), TEST_BLOCK_HEIGHT + 1, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE + 1, 2, TEST_OUTPUT_AMOUNT * 2); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_EQ(1, container.detach(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE + 1).size()); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); } TEST_F(TransfersContainerKeyImage, removeConfirmed_oneSpentTwoConfirmed) { auto tx1 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT); addSpendingTransaction(tx1->getTransactionHash(), TEST_BLOCK_HEIGHT + 1, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX, TEST_OUTPUT_AMOUNT); auto tx2 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE + 1, 2, TEST_OUTPUT_AMOUNT * 2); auto tx3 = addTransactionWithFixedKey(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE + 2, 3, TEST_OUTPUT_AMOUNT * 3); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); ASSERT_EQ(4, container.transactionsCount()); ASSERT_EQ(1, container.detach(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE + 2).size()); ASSERT_EQ(3, container.transactionsCount()); ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAll)); }