// 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 "../IntegrationTestLib/BaseFunctionalTests.h" #include #include #include "CryptoNoteCore/Account.h" #include "WalletLegacyObserver.h" #undef ERROR using namespace CryptoNote; using namespace Logging; inline std::string shortAddress(const std::string& addr) { return addr.substr(0, 6); } class MultiVersionTest : Tests::Common::BaseFunctionalTests { public: MultiVersionTest(const CryptoNote::Currency& currency, System::Dispatcher& d, const Tests::Common::BaseFunctionalTestsConfig& config, Logging::ILogger& log) : BaseFunctionalTests(currency, d, config), m_config(config), m_nodeCount(config.daemons.size()), logger(log, "MultiVersion") {} void run() { if (m_config.daemons.empty()) { logger(ERROR, BRIGHT_RED) << "No daemons configured, exiting"; return; } launchTestnet(m_nodeCount, Tests::Common::BaseFunctionalTests::Line); createWallets(); miningTest(); // create some address for mining CryptoNote::AccountBase stashAddress; stashAddress.generate(); auto stashAddressStr = m_currency.accountAddressAsString(stashAddress); unlockMoney(stashAddressStr); std::vector balances; for (auto& o : m_observers) { balances.push_back(o->totalBalance()); } printBalances(); // transfer money from each wallet to each other for (size_t i = 0; i < m_nodeCount; ++i) { auto& srcWallet = *m_wallets[i]; for (size_t wi = 0; wi < m_nodeCount; ++wi) { if (i != wi) { CryptoNote::WalletLegacyTransfer transfer; transfer.address = m_wallets[wi]->getAddress(); transfer.amount = (i * 1000 + wi * 100) * m_currency.coin(); logger(INFO, BRIGHT_YELLOW) << "Sending from " << shortAddress(srcWallet.getAddress()) << " to " << shortAddress(transfer.address) << " amount = " << m_currency.formatAmount(transfer.amount); auto txid = srcWallet.sendTransaction(transfer, m_currency.minimumFee()); balances[i] -= transfer.amount + m_currency.minimumFee(); balances[wi] += transfer.amount; auto res = m_observers[i]->waitSendResult(txid); if (res) { logger(ERROR, BRIGHT_RED) << "Failed to send transaction: " << res.message(); throw std::runtime_error("Failed to send transaction: " + res.message()); } logger(INFO) << "Sent successfully"; } } } nodeDaemons[0]->startMining(1, stashAddressStr); for (size_t i = 0; i < m_nodeCount; ++i) { uint64_t total; logger(INFO) << i << " Expected target balance: " << m_currency.formatAmount(balances[i]); while ((total = m_wallets[i]->pendingBalance() + m_wallets[i]->actualBalance()) != balances[i]) { logger(INFO) << i << " - total: " << m_currency.formatAmount(total) << ", waiting"; m_observers[i]->waitTotalBalanceChange(); } } nodeDaemons[0]->stopMining(); printBalances(); } void miningTest() { auto prevHeight = nodeDaemons[0]->getLocalHeight(); // mine block from each node to each wallet for (size_t i = 0; i < m_nodeCount; ++i) { for (size_t shift = 0; shift < m_nodeCount; ++shift) { logger(INFO, BRIGHT_YELLOW) << "Starting mining from node " << i << " -> wallet at node " << shift; while (nodeDaemons[i]->getLocalHeight() != prevHeight) { std::this_thread::sleep_for(std::chrono::seconds(1)); } logger(INFO) << "Starting mining at height " << prevHeight; nodeDaemons[i]->startMining(1, m_wallets[shift]->getAddress()); uint64_t newHeight = 0; while ((newHeight = nodeDaemons[i]->getLocalHeight()) == prevHeight) { std::this_thread::sleep_for(std::chrono::seconds(1)); } nodeDaemons[i]->stopMining(); logger(INFO) << "Block mined, new height = " << newHeight; prevHeight = nodeDaemons[i]->getLocalHeight(); logger(INFO, BRIGHT_YELLOW) << "Waiting for balance to change"; auto res = m_observers[shift]->waitPendingBalanceChangeFor(std::chrono::seconds(m_currency.difficultyTarget() * 5)); if (!res.first) { logger(ERROR, BRIGHT_RED) << "Timeout waiting for balance to change!"; throw std::runtime_error("Timeout"); } } } } void unlockMoney(const std::string& miningAddress) { logger(INFO, BRIGHT_YELLOW) << "Starting to mine blocks to unlock money"; // unlock money nodeDaemons[0]->startMining(1, miningAddress); for (auto& o : m_observers) { o->waitActualBalanceChange(); } nodeDaemons[0]->stopMining(); logger(INFO, BRIGHT_YELLOW) << "Unlocked all, waiting for all daemons to sync blockchain"; auto minerHeight = nodeDaemons[0]->getLocalHeight(); logger(INFO) << "Miner height: " << minerHeight; bool unsynced = true; while (unsynced) { unsynced = false; for (auto& o : m_observers) { if (o->getCurrentHeight() < minerHeight) { unsynced = true; break; } } if (unsynced) { std::this_thread::sleep_for(std::chrono::seconds(1)); } } logger(INFO) << "OK"; } void printBalances() { for (auto& w : m_wallets) { auto pending = w->pendingBalance(); auto actual = w->actualBalance(); logger(INFO, BRIGHT_GREEN) << "Wallet " << shortAddress(w->getAddress()) << ": " << m_currency.formatAmount(actual) << " / " << m_currency.formatAmount(pending) << " total = " << m_currency.formatAmount(pending + actual); } } void createWallets() { for (auto& daemon : nodeDaemons) { std::unique_ptr node; std::unique_ptr wallet; daemon->makeINode(node); makeWallet(wallet, node); std::unique_ptr observer(new WalletLegacyObserver); wallet->addObserver(observer.get()); m_nodes.push_back(std::move(node)); m_wallets.push_back(std::move(wallet)); m_observers.push_back(std::move(observer)); } } void startShiftedMining(size_t shift) { for (size_t i = 0; i < m_nodeCount; ++i) { nodeDaemons[i]->startMining(1, m_wallets[(i + shift) % m_nodeCount]->getAddress()); } } void waitAllPendingBalancesChange() { for (auto& o : m_observers) { o->waitPendingBalanceChange(); } } void stopAllMining() { for (auto& n : nodeDaemons) { n->stopMining(); } } private: const size_t m_nodeCount; const Tests::Common::BaseFunctionalTestsConfig& m_config; std::vector> m_nodes; std::vector> m_wallets; std::vector> m_observers; Logging::LoggerRef logger; }; void testMultiVersion(const CryptoNote::Currency& currency, System::Dispatcher& d, const Tests::Common::BaseFunctionalTestsConfig& config) { Logging::ConsoleLogger log; MultiVersionTest test(currency, d, config, log); test.run(); }