// 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 "TestNetwork.h" #include #include #include "CryptoNoteConfig.h" #include "InProcTestNode.h" #include "RPCTestNode.h" #ifdef _WIN32 const std::string daemonExec = std::string(CryptoNote::CRYPTONOTE_NAME) + "d.exe"; #else const std::string daemonExec = std::string(CryptoNote::CRYPTONOTE_NAME) + "d"; #endif namespace { using namespace Tests; void writeConfiguration(const std::string& confFile, const TestNodeConfiguration& cfg) { std::ofstream config(confFile, std::ios_base::trunc | std::ios_base::out); config << "rpc-bind-port=" << cfg.rpcPort << std::endl << "p2p-bind-port=" << cfg.p2pPort << std::endl << "log-level=4" << std::endl << "log-file=" << cfg.logFile << std::endl; for (const auto& ex : cfg.exclusiveNodes) { config << "add-exclusive-node=" << ex << std::endl; } } bool waitDaemonReady(TestNode& node) { for (size_t i = 0;; ++i) { if (node.getLocalHeight() > 0) { break; } else if (i < 2 * 60) { std::this_thread::sleep_for(std::chrono::seconds(1)); } else { return false; } } return true; } void copyBlockchainFiles(bool testnet, const std::string& from, const std::string& to) { boost::filesystem::path fromPath(from); boost::filesystem::path toPath(to); auto files = { std::make_pair("blockindexes.dat", true), std::make_pair("blocks.dat", true), std::make_pair("blockscache.dat", false), std::make_pair("blockchainindices.dat", false) }; for (const auto& item : files) { try { boost::filesystem::path filePath = std::string(testnet ? "testnet_" : "") + item.first; boost::filesystem::copy(fromPath / filePath, toPath / filePath); } catch (...) { if (item.second) { // if file is required, the rethrow error throw; } } } } } namespace Tests { TestNetworkBuilder::TestNetworkBuilder(size_t nodeCount, Topology topology, uint16_t rpcBasePort, uint16_t p2pBasePort) : nodeCount(nodeCount), topology(topology), rpcBasePort(rpcBasePort), p2pBasePort(p2pBasePort), baseDataDir("."), testnet(true) {} std::vector TestNetworkBuilder::build() { std::vector cfg; for (size_t i = 0; i < nodeCount; ++i) { cfg.push_back(buildNodeConfiguration(i)); } return cfg; } TestNetworkBuilder& TestNetworkBuilder::setDataDirectory(const std::string& dataDir) { this->baseDataDir = dataDir; return *this; } TestNetworkBuilder& TestNetworkBuilder::setBlockchain(const std::string& blockchainDir) { this->blockchainLocation = blockchainDir; return *this; } TestNetworkBuilder& TestNetworkBuilder::setTestnet(bool isTestnet) { this->testnet = isTestnet; return *this; } TestNodeConfiguration TestNetworkBuilder::buildNodeConfiguration(size_t index) { TestNodeConfiguration cfg; if (!baseDataDir.empty()) { cfg.dataDir = baseDataDir + "/node" + std::to_string(index); } if (!blockchainLocation.empty()) { cfg.blockchainLocation = blockchainLocation; } cfg.daemonPath = daemonExec; // default cfg.testnet = testnet; cfg.logFile = std::string("test_") + CryptoNote::CRYPTONOTE_NAME + "d" + std::to_string(index) + ".log"; uint16_t rpcPort = static_cast(rpcBasePort + index); uint16_t p2pPort = static_cast(p2pBasePort + index); cfg.p2pPort = p2pPort; cfg.rpcPort = rpcPort; switch (topology) { case Topology::Line: if (index != 0) { cfg.exclusiveNodes.push_back("127.0.0.1:" + std::to_string(p2pPort - 1)); } break; case Topology::Ring: { uint16_t p2pExternalPort = static_cast(p2pBasePort + (index + 1) % nodeCount); cfg.exclusiveNodes.push_back("127.0.0.1:" + std::to_string(p2pExternalPort)); break; } case Topology::Star: if (index == 0) { for (size_t node = 1; node < nodeCount; ++node) { cfg.exclusiveNodes.push_back("127.0.0.1:" + std::to_string(p2pBasePort + node)); } } break; } return cfg; } TestNetwork::TestNetwork(System::Dispatcher& dispatcher, const CryptoNote::Currency& currency) : m_dispatcher(dispatcher), m_currency(currency) { } void TestNetwork::addNodes(const std::vector& nodes) { for (const auto& n : nodes) { addNode(n); } } void TestNetwork::addNode(const TestNodeConfiguration& cfg) { std::unique_ptr node; boost::system::error_code ec; boost::filesystem::remove_all(cfg.dataDir, ec); boost::filesystem::create_directory(cfg.dataDir); if (!cfg.blockchainLocation.empty()) { copyBlockchainFiles(cfg.testnet, cfg.blockchainLocation, cfg.dataDir); } switch (cfg.nodeType) { case NodeType::InProcess: node.reset(new InProcTestNode(cfg, m_currency)); break; case NodeType::RPC: node = startDaemon(cfg); break; } nodes.push_back(std::make_pair(std::move(node), cfg)); } void TestNetwork::waitNodesReady() { for (auto& node : nodes) { if (!waitDaemonReady(*node.first)) { throw std::runtime_error("Daemon startup failure (timeout)"); } } } TestNode& TestNetwork::getNode(size_t index) { if (index >= nodes.size()) { throw std::runtime_error("Invalid node index"); } return *nodes[index].first; } void TestNetwork::shutdown() { for (auto& node : nodes) { node.first->stopDaemon(); } for (auto& daemon : m_daemons) { daemon.wait(); } std::this_thread::sleep_for(std::chrono::seconds(1)); for (auto& node : nodes) { if (node.second.cleanupDataDir) { boost::filesystem::remove_all(node.second.dataDir); } } } std::unique_ptr TestNetwork::startDaemon(const TestNodeConfiguration& cfg) { if (!boost::filesystem::exists(cfg.daemonPath)) { throw std::runtime_error("daemon binary wasn't found"); } writeConfiguration(cfg.dataDir + "/daemon.conf", cfg); Process process; std::vector daemonArgs = { "--data-dir=" + cfg.dataDir, "--config-file=daemon.conf" }; if (cfg.testnet) { daemonArgs.emplace_back("--testnet"); } process.startChild(cfg.daemonPath, daemonArgs); std::unique_ptr node(new RPCTestNode(cfg.rpcPort, m_dispatcher)); m_daemons.push_back(process); return node; } }