// 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 #include #include #include #include #include #include #include #include "CryptoNoteCore/CryptoNoteFormatUtils.h" #include "../IntegrationTestLib/BaseFunctionalTests.h" #include "../IntegrationTestLib/Logger.h" #include "Logging/ConsoleLogger.h" #ifndef CHECK_AND_ASSERT_MES #define CHECK_AND_ASSERT_MES(expr, fail_ret_val, message) do{if(!(expr)) {LOG_ERROR(message); return fail_ret_val;};}while(0) #endif #ifndef CHECK_AND_ASSERT_MES_NON_FATAL #define CHECK_AND_ASSERT_MES_NON_FATAL(expr, fail_ret_val, message) do{if(!(expr)) {LOG_WARNING(message); };}while(0) #endif Tests::Common::BaseFunctionalTestsConfig baseCfg; // System::Dispatcher globalDispatcher; namespace po = boost::program_options; namespace { class ConfigurationError : public std::runtime_error { public: ConfigurationError(const char* desc) : std::runtime_error(desc) {} }; struct Configuration : public Tests::Common::BaseFunctionalTestsConfig { Configuration() : desc("Allowed options") { init(); } bool handleCommandLine(int argc, char **argv) { po::variables_map vm; po::store(po::command_line_parser(argc, argv).options(desc).allow_unregistered().run(), vm); po::notify(vm); BaseFunctionalTestsConfig::handleCommandLine(vm); if (vm.count("help")) { std::cout << desc << std::endl; return false; } if (vm.count("test-type")) { auto testType = vm["test-type"].as(); if (testType < 1 || testType >= TESTLAST) throw ConfigurationError("Incorrect test type."); _testType = (TestType)testType; } else throw ConfigurationError("Missing test type."); return true; } enum TestType { WALLET2WALLET = 1, BLOCKTHRUDAEMONS = 3, RELAYBLOCKTHRUDAEMONS = 4, TESTPOOLANDINPROCNODE = 5, TESTPOOLDELETION = 6, TESTMULTIVERSION = 7, TESTLAST } _testType; po::options_description desc; protected: void init() { desc.add_options() ("help,h", "produce this help message and exit") ("test-type,t", po::value()->default_value(1), "test type:\r\n" "1 - wallet to wallet test,\r\n" "3 - block thru daemons test\r\n" "4 - relay block thru daemons\r\n" "5 - test tx pool and inproc node\r\n" "6 - deleting tx from pool due to timeout\r\n" "7 - multiple daemons interoperability test (use -a option to specify daemons)\r\n"); BaseFunctionalTestsConfig::init(desc); } }; } class SimpleTest : public Tests::Common::BaseFunctionalTests { public: SimpleTest(const CryptoNote::Currency& currency, System::Dispatcher& system, const Tests::Common::BaseFunctionalTestsConfig& config) : BaseFunctionalTests(currency, system, config) {} class WaitForActualGrowObserver : public CryptoNote::IWalletLegacyObserver { Tests::Common::Semaphore& m_GotActual; uint64_t m_lastFunds; public: WaitForActualGrowObserver(Tests::Common::Semaphore& GotActual, uint64_t lastFunds) : m_GotActual(GotActual), m_lastFunds(lastFunds) { } virtual void actualBalanceUpdated(uint64_t actualBalance) override { if (m_lastFunds < actualBalance) { m_GotActual.notify(); } m_lastFunds = actualBalance; } }; class WaitForActualDwindleObserver : public CryptoNote::IWalletLegacyObserver { Tests::Common::Semaphore& m_GotActual; uint64_t m_lastFunds; public: WaitForActualDwindleObserver(Tests::Common::Semaphore& GotActual, uint64_t lastFunds) : m_GotActual(GotActual), m_lastFunds(lastFunds) { } virtual void actualBalanceUpdated(uint64_t actualBalance) override { if (m_lastFunds > actualBalance) { m_GotActual.notify(); } m_lastFunds = actualBalance; } }; class WaitForPendingGrowObserver : public CryptoNote::IWalletLegacyObserver { Tests::Common::Semaphore& m_GotActual; uint64_t m_lastFunds; public: WaitForPendingGrowObserver(Tests::Common::Semaphore& GotActual, uint64_t lastFunds) : m_GotActual(GotActual), m_lastFunds(lastFunds) { } virtual void pendingBalanceUpdated(uint64_t pendingBalance) override { if (m_lastFunds < pendingBalance) { m_GotActual.notify(); } m_lastFunds = pendingBalance; } }; class WaitForConfirmationObserver : public CryptoNote::IWalletLegacyObserver { Tests::Common::Semaphore& m_confirmed; std::function m_pred; public: WaitForConfirmationObserver(Tests::Common::Semaphore& confirmed, std::function pred) : m_confirmed(confirmed), m_pred(pred) { } virtual void pendingBalanceUpdated(uint64_t pendingBalance) override { if (m_pred(pendingBalance)) m_confirmed.notify(); } }; class WaitForSendCompletedObserver : public CryptoNote::IWalletLegacyObserver { Tests::Common::Semaphore& m_Sent; std::error_code& m_error; CryptoNote::TransactionId& m_transactionId; public: WaitForSendCompletedObserver(Tests::Common::Semaphore& Sent, CryptoNote::TransactionId& transactionId, std::error_code& error) : m_Sent(Sent), m_transactionId(transactionId), m_error(error) { } virtual void sendTransactionCompleted(CryptoNote::TransactionId transactionId, std::error_code result) override { m_error = result; m_transactionId = transactionId; m_Sent.notify(); } }; class WaitForExternalTransactionObserver : public CryptoNote::IWalletLegacyObserver { public: WaitForExternalTransactionObserver() { } std::promise promise; virtual void externalTransactionCreated(CryptoNote::TransactionId transactionId) override { promise.set_value(transactionId); } }; class WaitForTransactionUpdated : public CryptoNote::IWalletLegacyObserver { public: WaitForTransactionUpdated() {} std::promise promise; virtual void transactionUpdated(CryptoNote::TransactionId transactionId) override { if (expectindTxId == transactionId) { promise.set_value(); } } CryptoNote::TransactionId expectindTxId; }; bool perform1() { using namespace Tests::Common; using namespace CryptoNote; const uint64_t FEE = 1000000; launchTestnet(2); LOG_TRACE("STEP 1 PASSED"); std::unique_ptr node1; std::unique_ptr node2; nodeDaemons.front()->makeINode(node1); nodeDaemons.front()->makeINode(node2); std::unique_ptr wallet1; std::unique_ptr wallet2; makeWallet(wallet1, node1); makeWallet(wallet2, node2); CHECK_AND_ASSERT_MES(mineBlock(), false, "can't mine block"); CHECK_AND_ASSERT_MES(mineBlock(), false, "can't mine block"); LOG_TRACE("STEP 2 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); CHECK_AND_ASSERT_MES(mineBlock(wallet1), false, "can't mine block on wallet 1"); CHECK_AND_ASSERT_MES(mineBlock(), false, "can't mine block"); LOG_TRACE("STEP 3 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); Semaphore wallet1GotActual; WaitForConfirmationObserver wallet1ActualGrown(wallet1GotActual, [](uint64_t pending)->bool {return pending == 0; }); wallet1->addObserver(&wallet1ActualGrown); CHECK_AND_ASSERT_MES(startMining(1) , false, "startMining(1) failed"); wallet1GotActual.wait(); LOG_TRACE("STEP 4 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); CHECK_AND_ASSERT_MES(stopMining() , false, "stopMining() failed"); auto wallet1ActualBeforeTransaction = wallet1->actualBalance(); auto wallet2ActualBeforeTransaction = wallet2->actualBalance(); auto wallet2PendingBeforeTransaction = wallet2->pendingBalance(); CryptoNote::WalletLegacyTransfer tr; tr.address = wallet2->getAddress(); tr.amount = wallet1ActualBeforeTransaction / 2; TransactionId sendTransaction; std::error_code result; Semaphore moneySent; WaitForSendCompletedObserver sco1(moneySent, sendTransaction, result); Semaphore w2GotPending; WaitForPendingGrowObserver pgo1(w2GotPending, wallet2PendingBeforeTransaction); wallet2->addObserver(&pgo1); wallet1->addObserver(&sco1); wallet1->sendTransaction(tr, FEE); CHECK_AND_ASSERT_MES(startMining(1), false, "startMining(1) failed"); moneySent.wait(); w2GotPending.wait(); CHECK_AND_ASSERT_MES(stopMining(), false, "stopMining() failed"); auto wallet2PendingAfterTransaction = wallet2->pendingBalance(); auto wallet1PendingAfterTransaction = wallet1->pendingBalance(); auto w2PendingDiff = wallet2PendingAfterTransaction - wallet2PendingBeforeTransaction; auto wallet1ActualAfterTransaction = wallet1->actualBalance(); LOG_TRACE("STEP 5 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); CHECK_AND_ASSERT_MES((tr.amount == w2PendingDiff), false, "STEP 6 ASSERTION 1 FAILED\r\n Transfered amount " + m_currency.formatAmount(tr.amount) + " doesn't match recieved amount " + m_currency.formatAmount(w2PendingDiff)); CHECK_AND_ASSERT_MES((wallet1ActualBeforeTransaction - wallet1PendingAfterTransaction - wallet1ActualAfterTransaction - tr.amount - FEE == 0), false, "STEP 6 ASSERTION 2 FAILED\r\n wallet1 Actual Before Transaction doesn't match wallet1 total After Transaction + Transfered amount + Fee " + m_currency.formatAmount(wallet1ActualBeforeTransaction) + " <> " + m_currency.formatAmount(wallet1PendingAfterTransaction) + " + " + m_currency.formatAmount(wallet1ActualAfterTransaction) + " + " + m_currency.formatAmount(tr.amount) + " + " + m_currency.formatAmount(FEE)); LOG_TRACE("STEP 6 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); CHECK_AND_ASSERT_MES(startMining(1), false, "startMining(1) failed"); Semaphore confirmed2; Semaphore confirmed1; WaitForConfirmationObserver confirmationObserver2(confirmed2, [](uint64_t pending)->bool {return pending == 0; }); WaitForConfirmationObserver confirmationObserver1(confirmed1, [](uint64_t pending)->bool {return pending == 0; }); wallet2->addObserver(&confirmationObserver2); wallet1->addObserver(&confirmationObserver1); if (wallet2->pendingBalance() != 0) confirmed2.wait(); if (wallet1->pendingBalance() != 0) confirmed1.wait(); CHECK_AND_ASSERT_MES(stopMining(), false, "stopMining() failed"); auto wallet1ActualAfterTransactionAndConfirmation = wallet1->actualBalance(); auto wallet2ActualAfterTransactionAndConfirmation = wallet2->actualBalance(); auto w2ActualDiff = wallet2ActualAfterTransactionAndConfirmation - wallet2ActualBeforeTransaction; auto w1ActualDiff = wallet1ActualBeforeTransaction - wallet1ActualAfterTransactionAndConfirmation; CHECK_AND_ASSERT_MES((tr.amount == w2ActualDiff), false, "STEP 7 FAILED\r\n Transfered amount " + m_currency.formatAmount(tr.amount) + " doesn't match confirmed recieved amount " + m_currency.formatAmount(w2ActualDiff)); CHECK_AND_ASSERT_MES((w1ActualDiff - tr.amount - FEE == 0), false, "STEP 7 FAILED\r\n wallet1 Actual Before Transaction doesn't match wallet1 Actual After Transaction + Transfered amount + Fee " + m_currency.formatAmount(wallet1ActualBeforeTransaction) + " <> " + m_currency.formatAmount(wallet1ActualAfterTransactionAndConfirmation) + "+" + m_currency.formatAmount(tr.amount) + "+" + m_currency.formatAmount(FEE)); LOG_TRACE("STEP 7 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); wallet1->removeObserver(&wallet1ActualGrown); wallet2->removeObserver(&pgo1); wallet1->removeObserver(&sco1); wallet2->removeObserver(&confirmationObserver2); wallet1->removeObserver(&confirmationObserver1); std::this_thread::sleep_for(std::chrono::milliseconds(2000)); return true; } class WaitForBlockchainHeightChangeObserver : public CryptoNote::INodeObserver { Tests::Common::Semaphore& m_changed; public: WaitForBlockchainHeightChangeObserver(Tests::Common::Semaphore& changed) : m_changed(changed) { } virtual void lastKnownBlockHeightUpdated(uint32_t height) override { m_changed.notify(); } }; class CallbackHeightChangeObserver : public CryptoNote::INodeObserver { std::function m_callback; public: CallbackHeightChangeObserver(std::function callback) : m_callback(callback) {} virtual void lastKnownBlockHeightUpdated(uint32_t height) override { m_callback(height); } }; bool perform2(size_t blocksCount = 10) { using namespace Tests::Common; launchTestnet(3, Line); std::this_thread::sleep_for(std::chrono::milliseconds(10000)); LOG_TRACE("STEP 1 PASSED"); mineBlock(); mineBlock(); LOG_TRACE("STEP 2 PASSED"); std::unique_ptr localNode; std::unique_ptr remoteNode; nodeDaemons.front()->makeINode(localNode); nodeDaemons.back()->makeINode(remoteNode); std::unique_ptr wallet; makeWallet(wallet, localNode); LOG_TRACE("STEP 3 PASSED"); Semaphore blockMined; Semaphore blockArrivedToRemote; WaitForBlockchainHeightChangeObserver localHCO(blockMined); WaitForBlockchainHeightChangeObserver remoteHCO(blockArrivedToRemote); localNode->addObserver(&localHCO); remoteNode->addObserver(&remoteHCO); for (size_t blockNumber = 0; blockNumber < blocksCount; ++blockNumber) { nodeDaemons.front()->startMining(1, wallet->getAddress()); blockMined.wait(); CHECK_AND_ASSERT_MES(blockArrivedToRemote.wait_for(std::chrono::milliseconds(5000)), false, "block propagation too slow >5000ms."); nodeDaemons.front()->stopMining(); LOG_TRACE("STEP 4 STAGE " + TO_STRING(blockNumber+1) + " of " + TO_STRING(blocksCount)+" PASSED"); } return true; } bool perform4() { using namespace CryptoNote; using namespace Tests::Common; launchTestnet(3, Star); LOG_TRACE("STEP 1 PASSED"); std::unique_ptr hopNode; std::unique_ptr localNode; std::unique_ptr remoteNode; nodeDaemons[0]->makeINode(hopNode); nodeDaemons[1]->makeINode(localNode); nodeDaemons[2]->makeINode(remoteNode); LOG_TRACE("STEP 2 PASSED"); std::string test_block1_hex = "0101b392d79f05a742885cb01d11b7b36fb8bf14616d42cd3d8c1429a224df41afa81b86b8a3a84e" "d8c33f010b01ff0108c0a62d02cc353782cbe4c6067bd30510f11d1f2993f2c7fed37239f299ffe3" "f96f135675c096b102023e8d4b2c22d73f91d0d9f8e0e12c8df24e5917f00d0b2dd99786c5bb0e5b" "300580bbb021022764ae61c084db07e7cd83c55e9c833f42b1d422e1008220fdb4acc726b94ea980" "88debe01023330c2b7dc4840f478066370ae48b148ce8dd010c59f6ecc08598682d32f07d080a0d9" "e61d02bcf35dc40ead54a614174774e60d8f5d0e46272c70bc7e70f205f7ccef25c34980b09dc2df" "01026ddcf1aed901f018453fd9352a01d5a44067d271ca403b4cd799d9832076daa280f092cbdd08" "021f613eab32b76ed03f6a796de7a5c92009ea9f9b9e3299ec91df7657cd694e5580c089a9a2f50f" "02f545046885a297ba63a2c7b305a74fdb741129cc367330661c1363e0bb0f0d0b2101acf052dcbe" "407bc34df1b7fffc17f0bfb0ffc23002e2b6de48a210df6f78bf1400"; std::string test_block2_hex = "0101b492d79f05456231a956ed3a8c1ac0bfe8efc1bb5d522d8474e566b051919ddea0ceab478a74" "e35210010c01ff0207c0e41202b2d7e697c6e2e894f9e98262c278235720b39f3a149774cb58cb52" "e5dde21601c09fab0302abffefad3afab42ca1ce2f7dccfa6942256f31387b307becd43571cfe22a" "10688084af5f02e2ab32d9b8fb8ced4bf4a81de0f48c23dc575076e8d233a3532d28f36e79035380" "a0d9e61d020c10664fe1ca35418733fa32ae2deadd4bf7ed982bb5d11ba98a7940a73e161580b09d" "c2df010266c3bfa27436b480a217a2fe06df714f4d2094ec1a0ced3bac2d96881972e28a80f092cb" "dd0802f31e9ac25fb8afd1d9d964331242a94f023c3188db5e532b5a9c800a843a3ebc80c089a9a2" "f50f0206244fcc73941c3da62ea6d62d679bedb311fc530d149099bdfd04c59cd507a121019d4b74" "f09454ccfdd6ca44b8c5f73c6805ea08dbe6a71769b058e158b2d4df5100"; std::string test_block3_hex = "0101b492d79f051f6fe6d9f7c14c0d5e16ba82d9ea68e4e6d6f30726854d45330aeb2fae5c1cd3fb" "7f4352010d01ff0308ffae350220f4c1c7631ecf4247688c376665df2b9dd935af6e4c027c9cddcb" "400fefec7380a4e803029e05ef9b3295e178d0f3199fca420f909f04fdab09b97c14290c8a913e42" "19c68087a70e02693641fefb1a6da81c2308370f349ef5e4adab792ae06b5da989ae3f1b7a13ca80" "d293ad0302c14d721ed8da5c98f108ef17c326737765857ddfa0b705fd4483cfa7ffeaad51808cee" "891a02f5a8e2ac24d6a9f789e5514de520c3ac28387788e130e22c4250b7d1be47460380b09dc2df" "0102cc3b2f894b416f3e09afae0395fc01cc2ec9763dff72839944e60055049ea37d80f092cbdd08" "02772df06a2cd92c174815ae1572799430ea01e903796f6a763648c7b350151ce580c089a9a2f50f" "0211c7bea98edba4fad6d3f19b330a676b8fb0391f7a99f45542e7cf52d39d6c632101e0370c5c79" "e99d772b41e0569bc41e1ebde2e563cdb7f5bdd23984899fad103200"; std::string test_block4_hex = "0101b492d79f0537da79424e1cc69d16aadf174dcf443947f8027695a5d1e30b2be4f5aa71904194" "47fe54010e01ff0407fffc1a0278eb82c9ea2f1e998906cec55caf26e347224c3391fb0aa2213bc1" "5eec4dacc580bbb021024ce32a63614269f43f698644c98fd9b7a11694dc69fd5126f6f6735ba6c5" "98dd808c8d9e0202d539ead46faf6d786964dd5106004612eb8d64778ad4fe8befa8c63e4d666f92" "808cee891a021a6c6669298dcc1c86af887804f128123d95a6d96b5884db97cfc96fa9ad018e80b0" "9dc2df0102dd3b9bbfef1eddeef8c406de9c0c4fc469c8069c910541252491df5a482fd5e380f092" "cbdd0802176a4cb411309761b7f50b0f495e99cc55cbaae70011d3c901e409a8a938f1b680c089a9" "a2f50f02bb232a77911350a1315de0b3de447142390f97e5ef25ecc1bf5837a8972b4b5e2101ef54" "5c318e38cfdd92362340fab6ec6630e4134b93cfd01db4d9a42fa945fdef00"; Semaphore blockArrivedToRemote; WaitForBlockchainHeightChangeObserver remoteHCO(blockArrivedToRemote); std::chrono::steady_clock::time_point localAdded; std::chrono::steady_clock::time_point hoplAdded; std::chrono::steady_clock::time_point remoteAdded; std::chrono::steady_clock::time_point submitInvokingStart; std::chrono::steady_clock::time_point submitInvoked; //auto height = localNode->getLastKnownBlockHeight(); CallbackHeightChangeObserver CHCOLocal([&localAdded](uint64_t new_height){localAdded = std::chrono::steady_clock::now(); }); CallbackHeightChangeObserver CHCOHop([&hoplAdded](uint64_t new_height){hoplAdded = std::chrono::steady_clock::now(); }); CallbackHeightChangeObserver CHCORemote([&remoteAdded](uint64_t new_height){remoteAdded = std::chrono::steady_clock::now(); }); localNode->addObserver(&CHCOLocal); hopNode->addObserver(&CHCOHop); remoteNode->addObserver(&CHCORemote); remoteNode->addObserver(&remoteHCO); LOG_TRACE("test_block1"); submitInvokingStart = std::chrono::steady_clock::now(); if (!nodeDaemons[1]->submitBlock(test_block1_hex)) return false; submitInvoked = std::chrono::steady_clock::now(); CHECK_AND_ASSERT_MES(blockArrivedToRemote.wait_for(std::chrono::milliseconds(10000)), false, "block 1 propagation too slow >10000ms."); LOG_TRACE("submitBlock() invocation takes: " + TO_STRING(std::chrono::duration_cast(submitInvoked - submitInvokingStart).count()) + " ms"); LOG_TRACE("HeightChangedCallback() since submit : " + TO_STRING(std::chrono::duration_cast(localAdded - submitInvoked).count()) + " ms"); LOG_TRACE("Local -> HopNode: " + TO_STRING(std::chrono::duration_cast(hoplAdded - localAdded).count()) + " ms"); LOG_TRACE("HopNode -> Remote: " + TO_STRING(std::chrono::duration_cast(remoteAdded - hoplAdded).count()) + " ms"); LOG_TRACE("Local -> Remote: " + TO_STRING(std::chrono::duration_cast(remoteAdded - localAdded).count()) + " ms"); LOG_TRACE("test_block2"); submitInvokingStart = std::chrono::steady_clock::now(); if (!nodeDaemons[1]->submitBlock(test_block2_hex)) return false; submitInvoked = std::chrono::steady_clock::now(); CHECK_AND_ASSERT_MES(blockArrivedToRemote.wait_for(std::chrono::milliseconds(10000)), false, "block 2 propagation too slow >10000ms."); LOG_TRACE("submitBlock() invocation takes: " + TO_STRING(std::chrono::duration_cast(submitInvoked - submitInvokingStart).count()) + " ms"); LOG_TRACE("HeightChangedCallback() since submit : " + TO_STRING(std::chrono::duration_cast(localAdded - submitInvoked).count()) + " ms"); LOG_TRACE("Local -> HopNode: " + TO_STRING(std::chrono::duration_cast(hoplAdded - localAdded).count()) + " ms"); LOG_TRACE("HopNode -> Remote: " + TO_STRING(std::chrono::duration_cast(remoteAdded - hoplAdded).count()) + " ms"); LOG_TRACE("Local -> Remote: " + TO_STRING(std::chrono::duration_cast(remoteAdded - localAdded).count()) + " ms"); LOG_TRACE("test_block3"); submitInvokingStart = std::chrono::steady_clock::now(); if (!nodeDaemons[1]->submitBlock(test_block3_hex)) return false; submitInvoked = std::chrono::steady_clock::now(); CHECK_AND_ASSERT_MES(blockArrivedToRemote.wait_for(std::chrono::milliseconds(10000)), false, "block 3 propagation too slow >10000ms."); LOG_TRACE("submitBlock() invocation takes: " + TO_STRING(std::chrono::duration_cast(submitInvoked - submitInvokingStart).count()) + " ms"); LOG_TRACE("HeightChangedCallback() since submit : " + TO_STRING(std::chrono::duration_cast(localAdded - submitInvoked).count()) + " ms"); LOG_TRACE("Local -> HopNode: " + TO_STRING(std::chrono::duration_cast(hoplAdded - localAdded).count()) + " ms"); LOG_TRACE("HopNode -> Remote: " + TO_STRING(std::chrono::duration_cast(remoteAdded - hoplAdded).count()) + " ms"); LOG_TRACE("Local -> Remote: " + TO_STRING(std::chrono::duration_cast(remoteAdded - localAdded).count()) + " ms"); LOG_TRACE("test_block4"); submitInvokingStart = std::chrono::steady_clock::now(); if (!nodeDaemons[1]->submitBlock(test_block4_hex)) return false; submitInvoked = std::chrono::steady_clock::now(); CHECK_AND_ASSERT_MES(blockArrivedToRemote.wait_for(std::chrono::milliseconds(10000)), false, "block 4 propagation too slow >10000ms."); LOG_TRACE("submitBlock() invocation takes: " + TO_STRING(std::chrono::duration_cast(submitInvoked - submitInvokingStart).count()) + " ms"); LOG_TRACE("HeightChangedCallback() since submit : " + TO_STRING(std::chrono::duration_cast(localAdded - submitInvoked).count()) + " ms"); LOG_TRACE("Local -> HopNode: " + TO_STRING(std::chrono::duration_cast(hoplAdded - localAdded).count()) + " ms"); LOG_TRACE("HopNode -> Remote: " + TO_STRING(std::chrono::duration_cast(remoteAdded - hoplAdded).count()) + " ms"); LOG_TRACE("Local -> Remote: " + TO_STRING(std::chrono::duration_cast(remoteAdded - localAdded).count()) + " ms"); localNode.release(); remoteNode.release(); hopNode.release(); std::this_thread::sleep_for(std::chrono::milliseconds(5000)); return true; } bool perform5() { using namespace Tests::Common; using namespace CryptoNote; const uint64_t FEE = 1000000; launchTestnetWithInprocNode(2); std::unique_ptr node1; std::unique_ptr inprocNode; nodeDaemons.front()->makeINode(node1); nodeDaemons.back()->makeINode(inprocNode); while (node1->getLastLocalBlockHeight() != inprocNode->getLastLocalBlockHeight()) { LOG_TRACE("Syncing..."); std::this_thread::sleep_for(std::chrono::seconds(1)); } LOG_TRACE("STEP 1 PASSED"); std::unique_ptr wallet1; std::unique_ptr wallet2; makeWallet(wallet1, node1); makeWallet(wallet2, inprocNode); CHECK_AND_ASSERT_MES(mineBlock(), false, "can't mine block"); CHECK_AND_ASSERT_MES(mineBlock(), false, "can't mine block"); LOG_TRACE("STEP 2 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); CHECK_AND_ASSERT_MES(mineBlock(wallet1), false, "can't mine block on wallet 1"); LOG_TRACE("STEP 3 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); Semaphore wallet1GotActual; WaitForConfirmationObserver wallet1ActualGrown(wallet1GotActual, [&wallet1](uint64_t actual)->bool {return wallet1->pendingBalance() == actual; }); wallet1->addObserver(&wallet1ActualGrown); CHECK_AND_ASSERT_MES(startMining(1), false, "startMining(1) failed"); wallet1GotActual.wait(); LOG_TRACE("STEP 4 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); CHECK_AND_ASSERT_MES(stopMining(), false, "stopMining() failed"); auto wallet1ActualBeforeTransaction = wallet1->actualBalance(); auto wallet1PendingBeforeTransaction = wallet1->pendingBalance(); auto wallet2ActualBeforeTransaction = wallet2->actualBalance(); auto wallet2PendingBeforeTransaction = wallet2->pendingBalance(); CryptoNote::WalletLegacyTransfer tr; tr.address = wallet2->getAddress(); tr.amount = wallet1ActualBeforeTransaction / 2; std::error_code result; Semaphore w2GotPending; WaitForPendingGrowObserver pgo1(w2GotPending, wallet2PendingBeforeTransaction); wallet2->addObserver(&pgo1); WaitForExternalTransactionObserver poolTxWaiter; auto future = poolTxWaiter.promise.get_future(); wallet2->addObserver(&poolTxWaiter); wallet1->sendTransaction(tr, FEE); auto txId = future.get(); w2GotPending.wait(); wallet2->removeObserver(&poolTxWaiter); CryptoNote::WalletLegacyTransaction txInfo; wallet2->getTransaction(txId, txInfo); auto wallet2PendingAfterTransaction = wallet2->pendingBalance(); auto wallet1PendingAfterTransaction = wallet1->pendingBalance(); auto w2PendingDiff = wallet2PendingAfterTransaction - wallet2PendingBeforeTransaction; auto w1PendingDiff = wallet1PendingBeforeTransaction - wallet1PendingAfterTransaction; CHECK_AND_ASSERT_MES((txInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT), false, "STEP 5 ASSERTION 1 FAILED\r\n Transaction blockHeight differs unconfirmed_tx_height"); CHECK_AND_ASSERT_MES((tr.amount == txInfo.totalAmount), false, "STEP 5 ASSERTION 2 FAILED\r\n Transfered amount " + m_currency.formatAmount(tr.amount) + " doesn't match recieved amount from pool transaction " + m_currency.formatAmount(txInfo.totalAmount)); CHECK_AND_ASSERT_MES((tr.amount == w2PendingDiff), false, "STEP 5 ASSERTION 3 FAILED\r\n Transfered amount " + m_currency.formatAmount(tr.amount) + " doesn't match recieved amount " + m_currency.formatAmount(w2PendingDiff)); CHECK_AND_ASSERT_MES((w1PendingDiff - tr.amount - FEE == 0), false, "STEP 5 ASSERTION 4 FAILED\r\n wallet1 Pending Before Transaction doesn't match wallet1 Pending After Transaction + Transfered amount + Fee " + m_currency.formatAmount(wallet1PendingBeforeTransaction) + " <> " + m_currency.formatAmount(wallet1PendingAfterTransaction) + "+" + m_currency.formatAmount(tr.amount) + "+" + m_currency.formatAmount(FEE)); LOG_TRACE("STEP 5 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); WaitForTransactionUpdated trasactionConfirmationObserver; trasactionConfirmationObserver.expectindTxId = txId; wallet2->addObserver(&trasactionConfirmationObserver); auto txUpdated = trasactionConfirmationObserver.promise.get_future(); CHECK_AND_ASSERT_MES(mineBlock(), false, "mineBlock() failed"); CHECK_AND_ASSERT_MES(mineBlock(), false, "mineBlock() failed"); txUpdated.get(); wallet2->getTransaction(txId, txInfo); wallet2->removeObserver(&trasactionConfirmationObserver); CHECK_AND_ASSERT_MES(txInfo.blockHeight <= inprocNode->getLastLocalBlockHeight(), false, "STEP 6 ASSERTION FAILED tx height confirmation failed"); LOG_TRACE("STEP 6 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); CHECK_AND_ASSERT_MES(startMining(1), false, "startMining(1) failed"); Semaphore confirmed2; Semaphore confirmed1; WaitForConfirmationObserver confirmationObserver2(confirmed2, [&wallet2](uint64_t actual)->bool {return wallet2->pendingBalance() == actual; }); WaitForConfirmationObserver confirmationObserver1(confirmed1, [&wallet1](uint64_t actual)->bool {return wallet1->pendingBalance() == actual; }); wallet2->addObserver(&confirmationObserver2); wallet1->addObserver(&confirmationObserver1); if (wallet2->pendingBalance() != wallet2->actualBalance()) confirmed2.wait(); if (wallet1->pendingBalance() != wallet1->actualBalance()) confirmed1.wait(); CHECK_AND_ASSERT_MES(stopMining(), false, "stopMining() failed"); auto wallet1ActualAfterTransactionAndConfirmation = wallet1->actualBalance(); auto wallet2ActualAfterTransactionAndConfirmation = wallet2->actualBalance(); auto w2ActualDiff = wallet2ActualAfterTransactionAndConfirmation - wallet2ActualBeforeTransaction; auto w1ActualDiff = wallet1ActualBeforeTransaction - wallet1ActualAfterTransactionAndConfirmation; CHECK_AND_ASSERT_MES((tr.amount == w2ActualDiff), false, "STEP 7 FAILED\r\n Transfered amount " + m_currency.formatAmount(tr.amount) + " doesn't match confirmed recieved amount " + m_currency.formatAmount(w2ActualDiff)); CHECK_AND_ASSERT_MES((w1ActualDiff - tr.amount - FEE == 0), false, "STEP 7 FAILED\r\n wallet1 Actual Before Transaction doesn't match wallet1 Actual After Transaction + Transfered amount + Fee " + m_currency.formatAmount(wallet1ActualBeforeTransaction) + " <> " + m_currency.formatAmount(wallet1ActualAfterTransactionAndConfirmation) + "+" + m_currency.formatAmount(tr.amount) + "+" + m_currency.formatAmount(FEE)); LOG_TRACE("STEP 7 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); wallet1->removeObserver(&wallet1ActualGrown); wallet2->removeObserver(&pgo1); wallet2->removeObserver(&confirmationObserver2); wallet1->removeObserver(&confirmationObserver1); std::this_thread::sleep_for(std::chrono::milliseconds(2000)); return true; } bool perform6() { using namespace Tests::Common; using namespace CryptoNote; const uint64_t FEE = 1000000; launchTestnetWithInprocNode(2); std::unique_ptr node1; std::unique_ptr inprocNode; nodeDaemons.front()->makeINode(node1); nodeDaemons.back()->makeINode(inprocNode); while (node1->getLastLocalBlockHeight() != inprocNode->getLastLocalBlockHeight()) { LOG_TRACE("Syncing..."); std::this_thread::sleep_for(std::chrono::seconds(1)); } LOG_TRACE("STEP 1 PASSED"); std::unique_ptr wallet1; std::unique_ptr wallet2; makeWallet(wallet1, node1); makeWallet(wallet2, inprocNode); CHECK_AND_ASSERT_MES(mineBlock(), false, "can't mine block"); CHECK_AND_ASSERT_MES(mineBlock(), false, "can't mine block"); LOG_TRACE("STEP 2 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); CHECK_AND_ASSERT_MES(mineBlock(wallet1), false, "can't mine block on wallet 1"); LOG_TRACE("STEP 3 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); Semaphore wallet1GotActual; WaitForConfirmationObserver wallet1ActualGrown(wallet1GotActual, [&wallet1](uint64_t actual)->bool {return wallet1->pendingBalance() == actual; }); wallet1->addObserver(&wallet1ActualGrown); CHECK_AND_ASSERT_MES(startMining(1), false, "startMining(1) failed"); wallet1GotActual.wait(); LOG_TRACE("STEP 4 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); CHECK_AND_ASSERT_MES(stopMining(), false, "stopMining() failed"); auto wallet1ActualBeforeTransaction = wallet1->actualBalance(); auto wallet1PendingBeforeTransaction = wallet1->pendingBalance(); auto wallet2PendingBeforeTransaction = wallet2->pendingBalance(); CryptoNote::WalletLegacyTransfer tr; tr.address = wallet2->getAddress(); tr.amount = wallet1ActualBeforeTransaction / 2; std::error_code result; Semaphore w2GotPending; WaitForPendingGrowObserver pgo1(w2GotPending, wallet2PendingBeforeTransaction); wallet2->addObserver(&pgo1); WaitForExternalTransactionObserver poolTxWaiter; auto future = poolTxWaiter.promise.get_future(); wallet2->addObserver(&poolTxWaiter); wallet1->sendTransaction(tr, FEE); auto txId = future.get(); w2GotPending.wait(); wallet2->removeObserver(&poolTxWaiter); CryptoNote::WalletLegacyTransaction txInfo; wallet2->getTransaction(txId, txInfo); auto wallet2PendingAfterTransaction = wallet2->pendingBalance(); auto wallet1PendingAfterTransaction = wallet1->pendingBalance(); auto w2PendingDiff = wallet2PendingAfterTransaction - wallet2PendingBeforeTransaction; auto w1PendingDiff = wallet1PendingBeforeTransaction - wallet1PendingAfterTransaction; CHECK_AND_ASSERT_MES((txInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT), false, "STEP 5 ASSERTION 1 FAILED\r\n Transaction blockHeight differs unconfirmed_tx_height"); CHECK_AND_ASSERT_MES((tr.amount == txInfo.totalAmount), false, "STEP 5 ASSERTION 2 FAILED\r\n Transfered amount " + m_currency.formatAmount(tr.amount) + " doesn't match recieved amount from pool transaction " + m_currency.formatAmount(txInfo.totalAmount)); CHECK_AND_ASSERT_MES((tr.amount == w2PendingDiff), false, "STEP 5 ASSERTION 3 FAILED\r\n Transfered amount " + m_currency.formatAmount(tr.amount) + " doesn't match recieved amount " + m_currency.formatAmount(w2PendingDiff)); CHECK_AND_ASSERT_MES((w1PendingDiff - tr.amount - FEE == 0), false, "STEP 5 ASSERTION 4 FAILED\r\n wallet1 Pending Before Transaction doesn't match wallet1 Pending After Transaction + Transfered amount + Fee " + m_currency.formatAmount(wallet1PendingBeforeTransaction) + " <> " + m_currency.formatAmount(wallet1PendingAfterTransaction) + "+" + m_currency.formatAmount(tr.amount) + "+" + m_currency.formatAmount(FEE)); LOG_TRACE("STEP 5 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); WaitForTransactionUpdated trasactionDeletionObserver; trasactionDeletionObserver.expectindTxId = txId; wallet2->addObserver(&trasactionDeletionObserver); auto txUpdated = trasactionDeletionObserver.promise.get_future(); txUpdated.get(); wallet2->getTransaction(txId, txInfo); wallet2->removeObserver(&trasactionDeletionObserver); CHECK_AND_ASSERT_MES(txInfo.state == WalletLegacyTransactionState::Deleted, false, "STEP 6 ASSERTION 1 FAILED tx not deleted"); CHECK_AND_ASSERT_MES(wallet2PendingBeforeTransaction == wallet2->pendingBalance(), false, "STEP 6 ASSERTION 2 FAILED current pending balance <> pending balance before transaction"); LOG_TRACE("STEP 6 PASSED"); LOG_DEBUG("Wallet1 pending: " + m_currency.formatAmount(wallet1->pendingBalance())); LOG_DEBUG("Wallet1 actual: " + m_currency.formatAmount(wallet1->actualBalance())); LOG_DEBUG("Wallet2 pending: " + m_currency.formatAmount(wallet2->pendingBalance())); LOG_DEBUG("Wallet2 actual: " + m_currency.formatAmount(wallet2->actualBalance())); wallet1->removeObserver(&wallet1ActualGrown); wallet2->removeObserver(&pgo1); std::this_thread::sleep_for(std::chrono::milliseconds(2000)); return true; } }; void testMultiVersion(const CryptoNote::Currency& currency, System::Dispatcher& d, const Tests::Common::BaseFunctionalTestsConfig& config); class SimpleTestCase : public ::testing::Test { public: SimpleTestCase() : currency(CryptoNote::CurrencyBuilder(logger).testnet(true).currency()), test(currency, dispatcher, baseCfg) { } System::Dispatcher dispatcher; Logging::ConsoleLogger logger; CryptoNote::Currency currency; SimpleTest test; }; TEST_F(SimpleTestCase, WALLET2WALLET) { ASSERT_TRUE(test.perform1()); } TEST_F(SimpleTestCase, BLOCKTHRUDAEMONS) { ASSERT_TRUE(test.perform2()); } TEST_F(SimpleTestCase, RELAYBLOCKTHRUDAEMONS) { ASSERT_TRUE(test.perform4()); } TEST_F(SimpleTestCase, TESTPOOLANDINPROCNODE) { ASSERT_TRUE(test.perform5()); } TEST_F(SimpleTestCase, TESTPOOLDELETION) { currency = CryptoNote::CurrencyBuilder(logger).testnet(true).mempoolTxLiveTime(60).currency(); ASSERT_TRUE(test.perform6()); } TEST_F(SimpleTestCase, MULTIVERSION) { ASSERT_NO_THROW(testMultiVersion(currency, dispatcher, baseCfg)); } int main(int argc, char** argv) { CLogger::Instance().init(CLogger::DEBUG); try { ::Configuration config; if (!config.handleCommandLine(argc, argv)) { return 0; //help message requested or so } baseCfg = config; ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } catch (::ConfigurationError& ex) { std::cerr << "Configuration error: " << ex.what() << std::endl; return 1; } catch (std::exception& ex) { LOG_ERROR("Fatal error: " + std::string(ex.what())); return 1; } return 0; }