346 lines
11 KiB
C++
346 lines
11 KiB
C++
|
// Copyright (c) 2012-2014, The CryptoNote developers, The Bytecoin developers
|
||
|
//
|
||
|
// This file is part of Bytecoin.
|
||
|
//
|
||
|
// Bytecoin is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// Bytecoin is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU Lesser General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Lesser General Public License
|
||
|
// along with Bytecoin. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
#include "BaseFunctionalTest.h"
|
||
|
|
||
|
#include <vector>
|
||
|
#include <thread>
|
||
|
#include <fstream>
|
||
|
#include <iostream>
|
||
|
#include <sstream>
|
||
|
#include <cstdlib>
|
||
|
|
||
|
#include <boost/lexical_cast.hpp>
|
||
|
#include <boost/filesystem.hpp>
|
||
|
|
||
|
#include "p2p/NetNodeConfig.h"
|
||
|
#include "cryptonote_core/CoreConfig.h"
|
||
|
|
||
|
#include "RPCTestNode.h"
|
||
|
#include "wallet/Wallet.h"
|
||
|
#include "Logger.h"
|
||
|
|
||
|
#if defined __linux__
|
||
|
#include <sys/wait.h>
|
||
|
#include <unistd.h>
|
||
|
#include <errno.h>
|
||
|
#endif
|
||
|
|
||
|
using namespace Tests::Common;
|
||
|
|
||
|
void BaseFunctionalTest::launchTestnet(size_t count, Topology t) {
|
||
|
if (count < 1) LOG_WARNING("Testnet has no nodes");
|
||
|
for (uint16_t i = 0; i < count; ++i) {
|
||
|
std::string dataDirPath = m_dataDir + "/node";
|
||
|
dataDirPath += boost::lexical_cast<std::string>(i);
|
||
|
boost::filesystem::create_directory(dataDirPath);
|
||
|
|
||
|
std::ofstream config(dataDirPath + "/daemon.conf", std::ios_base::trunc | std::ios_base::out);
|
||
|
|
||
|
uint16_t rpcPort = RPC_FIRST_PORT + i;
|
||
|
uint16_t p2pPort = P2P_FIRST_PORT + i;
|
||
|
|
||
|
config
|
||
|
<< "rpc-bind-port=" << rpcPort << std::endl
|
||
|
<< "p2p-bind-port=" << p2pPort << std::endl
|
||
|
<< "log-level=2" << std::endl
|
||
|
<< "log-file=test_bytecoind_" << i + 1 << ".log" << std::endl;
|
||
|
|
||
|
switch (t) {
|
||
|
case Line:
|
||
|
if (i != count - 1) config << "add-exclusive-node=127.0.0.1:" << p2pPort + 1 << std::endl;
|
||
|
if (i != 0) config << "add-exclusive-node=127.0.0.1:" << p2pPort - 1 << std::endl;
|
||
|
break;
|
||
|
case Ring: {
|
||
|
uint16_t p2pExternalPort = P2P_FIRST_PORT + (i + 1) % count;
|
||
|
config << "add-exclusive-node=127.0.0.1:" << p2pExternalPort + 1 << std::endl;
|
||
|
}
|
||
|
break;
|
||
|
case Star:
|
||
|
if (i == 0) {
|
||
|
for (size_t node = 1; node < count; ++node)
|
||
|
config << "add-exclusive-node=127.0.0.1:" << P2P_FIRST_PORT + node << std::endl;
|
||
|
}
|
||
|
else {
|
||
|
config << "add-exclusive-node=127.0.0.1:" << P2P_FIRST_PORT << std::endl;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
config.close();
|
||
|
#if defined WIN32
|
||
|
std::string commandLine = "start /MIN \"bytecoind\" \"" + m_daemonDir + "\\bytecoind.exe\" --testnet --data-dir=\"" + dataDirPath + "\" --config-file=daemon.conf";
|
||
|
LOG_DEBUG(commandLine);
|
||
|
system(commandLine.c_str());
|
||
|
#elif defined __linux__
|
||
|
auto pid = fork();
|
||
|
if( pid == 0 ) {
|
||
|
std::string pathToDaemon = "" + m_daemonDir + "/bytecoind";
|
||
|
close(1);
|
||
|
close(2);
|
||
|
std::string dataDir = "--data-dir=" + dataDirPath + "";
|
||
|
if(execl(pathToDaemon.c_str(), "bytecoind", "--testnet", dataDir.c_str(), "--config-file=daemon.conf", NULL) == -1) {
|
||
|
LOG_ERROR(TO_STRING(errno));
|
||
|
}
|
||
|
throw std::runtime_error("failed to start daemon");
|
||
|
} else if(pid > 0) {
|
||
|
pids.push_back(pid);
|
||
|
}
|
||
|
#else
|
||
|
|
||
|
#endif
|
||
|
|
||
|
nodeDaemons.push_back(
|
||
|
std::unique_ptr<TestNode>(new RPCTestNode(rpcPort, m_dispatcher))
|
||
|
);
|
||
|
}
|
||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10000)); //for initial update
|
||
|
nodeDaemons[0]->makeINode(mainNode);
|
||
|
makeWallet(workingWallet, mainNode);
|
||
|
}
|
||
|
|
||
|
void BaseFunctionalTest::launchTestnetWithInprocNode(size_t count, Topology t) {
|
||
|
if (count < 1) LOG_WARNING("Testnet has no nodes");
|
||
|
for (uint16_t i = 0; i < count-1; ++i) {
|
||
|
std::string dataDirPath = m_dataDir + "/node";
|
||
|
dataDirPath += boost::lexical_cast<std::string>(i);
|
||
|
boost::filesystem::create_directory(dataDirPath);
|
||
|
|
||
|
std::ofstream config(dataDirPath + "/daemon.conf", std::ios_base::trunc | std::ios_base::out);
|
||
|
|
||
|
uint16_t rpcPort = RPC_FIRST_PORT + i;
|
||
|
uint16_t p2pPort = P2P_FIRST_PORT + i;
|
||
|
|
||
|
config
|
||
|
<< "rpc-bind-port=" << rpcPort << std::endl
|
||
|
<< "p2p-bind-port=" << p2pPort << std::endl
|
||
|
<< "log-level=2" << std::endl
|
||
|
<< "log-file=test_bytecoind_" << i + 1 << ".log" << std::endl;
|
||
|
|
||
|
switch (t) {
|
||
|
case Line:
|
||
|
config << "add-exclusive-node=127.0.0.1:" << p2pPort + 1 << std::endl;
|
||
|
if (i != 0) config << "add-exclusive-node=127.0.0.1:" << p2pPort - 1 << std::endl;
|
||
|
break;
|
||
|
case Ring: {
|
||
|
uint16_t p2pExternalPort = P2P_FIRST_PORT + (i + 1) % count;
|
||
|
config << "add-exclusive-node=127.0.0.1:" << p2pExternalPort + 1 << std::endl;
|
||
|
}
|
||
|
break;
|
||
|
case Star:
|
||
|
if (i == 0) {
|
||
|
for (size_t node = 1; node < count; ++node)
|
||
|
config << "add-exclusive-node=127.0.0.1:" << P2P_FIRST_PORT + node << std::endl;
|
||
|
} else {
|
||
|
config << "add-exclusive-node=127.0.0.1:" << P2P_FIRST_PORT << std::endl;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
config.close();
|
||
|
#if defined WIN32
|
||
|
std::string commandLine = "start /MIN \"bytecoind\" \"" + m_daemonDir + "\\bytecoind.exe\" --testnet --data-dir=\"" + dataDirPath + "\" --config-file=daemon.conf";
|
||
|
LOG_DEBUG(commandLine);
|
||
|
system(commandLine.c_str());
|
||
|
#elif defined __linux__
|
||
|
auto pid = fork();
|
||
|
if (pid == 0) {
|
||
|
std::string pathToDaemon = "" + m_daemonDir + "/bytecoind";
|
||
|
close(1);
|
||
|
close(2);
|
||
|
std::string dataDir = "--data-dir=" + dataDirPath + "";
|
||
|
if (execl(pathToDaemon.c_str(), "bytecoind", "--testnet", dataDir.c_str(), "--config-file=daemon.conf", NULL) == -1) {
|
||
|
LOG_ERROR(TO_STRING(errno));
|
||
|
}
|
||
|
throw std::runtime_error("failed to start daemon");
|
||
|
} else if (pid > 0) {
|
||
|
pids.push_back(pid);
|
||
|
}
|
||
|
#else
|
||
|
|
||
|
#endif
|
||
|
|
||
|
nodeDaemons.push_back(
|
||
|
std::unique_ptr<TestNode>(new RPCTestNode(rpcPort, m_dispatcher))
|
||
|
);
|
||
|
}
|
||
|
|
||
|
this->core.reset(new cryptonote::core(m_currency, NULL));
|
||
|
this->protocol.reset(new cryptonote::t_cryptonote_protocol_handler<cryptonote::core>(*core, NULL));
|
||
|
this->p2pNode.reset(new nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>(*protocol));
|
||
|
protocol->set_p2p_endpoint(p2pNode.get());
|
||
|
core->set_cryptonote_protocol(protocol.get());
|
||
|
|
||
|
std::string dataDirPath = m_dataDir + "/node";
|
||
|
dataDirPath += boost::lexical_cast<std::string>(count - 1);
|
||
|
boost::filesystem::create_directory(dataDirPath);
|
||
|
|
||
|
uint16_t p2pPort = P2P_FIRST_PORT + static_cast<uint16_t>(count) - 1;
|
||
|
|
||
|
nodetool::NetNodeConfig p2pConfig;
|
||
|
p2pConfig.bindIp = "127.0.0.1";
|
||
|
p2pConfig.bindPort = boost::lexical_cast<std::string>(p2pPort);
|
||
|
nodetool::net_address addr;
|
||
|
addr.ip = 0x7f000001;
|
||
|
|
||
|
p2pConfig.externalPort = 0;
|
||
|
p2pConfig.allowLocalIp = false;
|
||
|
p2pConfig.hideMyPort = false;
|
||
|
p2pConfig.configFolder = dataDirPath;
|
||
|
|
||
|
|
||
|
switch (t) {
|
||
|
case Line:
|
||
|
addr.port = p2pPort - 1;
|
||
|
p2pConfig.exclusiveNodes.push_back(addr);
|
||
|
break;
|
||
|
case Ring:
|
||
|
addr.port = p2pPort - 1;
|
||
|
p2pConfig.exclusiveNodes.push_back(addr);
|
||
|
addr.port = P2P_FIRST_PORT;
|
||
|
p2pConfig.exclusiveNodes.push_back(addr);
|
||
|
break;
|
||
|
case Star:
|
||
|
addr.port = P2P_FIRST_PORT;
|
||
|
p2pConfig.exclusiveNodes.push_back(addr);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!p2pNode->init(p2pConfig, true)) {
|
||
|
throw std::runtime_error("Failed to init p2pNode");
|
||
|
}
|
||
|
|
||
|
protocol->init();
|
||
|
|
||
|
cryptonote::MinerConfig emptyMiner;
|
||
|
cryptonote::CoreConfig coreConfig;
|
||
|
coreConfig.configFolder = dataDirPath;
|
||
|
core->init(coreConfig, emptyMiner, true);
|
||
|
|
||
|
inprocNode.reset(new CryptoNote::InProcessNode(*core, *protocol));
|
||
|
std::promise<void> p;
|
||
|
auto future = p.get_future();
|
||
|
inprocNode->init([&p](std::error_code ec) {
|
||
|
p.set_value();
|
||
|
if (ec) {
|
||
|
std::cout << ec.message() << std::endl;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
future.get();
|
||
|
|
||
|
std::thread serverThread(
|
||
|
std::bind(
|
||
|
&nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>::run,
|
||
|
p2pNode.get()
|
||
|
)
|
||
|
);
|
||
|
serverThread.detach();
|
||
|
|
||
|
|
||
|
|
||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10000)); //for initial update
|
||
|
nodeDaemons[0]->makeINode(mainNode);
|
||
|
makeWallet(workingWallet, mainNode);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
BaseFunctionalTest::~BaseFunctionalTest() {
|
||
|
if (mainNode) {
|
||
|
mainNode->shutdown();
|
||
|
}
|
||
|
|
||
|
if (inprocNode) {
|
||
|
inprocNode->shutdown();
|
||
|
}
|
||
|
|
||
|
if (p2pNode) {
|
||
|
p2pNode->send_stop_signal();
|
||
|
}
|
||
|
|
||
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||
|
stopTestnet();
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
class WaitForCoinBaseObserver : public CryptoNote::IWalletObserver {
|
||
|
Semaphore& m_gotReward;
|
||
|
CryptoNote::IWallet& m_wallet;
|
||
|
public:
|
||
|
WaitForCoinBaseObserver(Semaphore& gotReward, CryptoNote::IWallet& wallet) : m_gotReward(gotReward), m_wallet(wallet) { }
|
||
|
virtual void externalTransactionCreated(CryptoNote::TransactionId transactionId) override {
|
||
|
CryptoNote::TransactionInfo trInfo;
|
||
|
m_wallet.getTransaction(transactionId, trInfo);
|
||
|
if (trInfo.isCoinbase) m_gotReward.notify();
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
bool BaseFunctionalTest::mineBlock(std::unique_ptr<CryptoNote::IWallet>& wallet) {
|
||
|
if (nodeDaemons.empty() || !wallet) return false;
|
||
|
if (!nodeDaemons.front()->stopMining()) return false;
|
||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
|
||
|
Semaphore gotReward;
|
||
|
WaitForCoinBaseObserver cbo(gotReward, *wallet.get());
|
||
|
wallet->addObserver(&cbo);
|
||
|
if(!nodeDaemons.front()->startMining(1, wallet->getAddress())) return false;
|
||
|
gotReward.wait();
|
||
|
if (!nodeDaemons.front()->stopMining()) return false;
|
||
|
wallet->removeObserver(&cbo);
|
||
|
return true;
|
||
|
}
|
||
|
bool BaseFunctionalTest::mineBlock() {
|
||
|
return mineBlock(workingWallet);
|
||
|
}
|
||
|
|
||
|
bool BaseFunctionalTest::startMining(size_t threads) {
|
||
|
if (nodeDaemons.empty() || !workingWallet) return false;
|
||
|
if(!stopMining()) return false;
|
||
|
return nodeDaemons.front()->startMining(threads, workingWallet->getAddress());
|
||
|
}
|
||
|
|
||
|
bool BaseFunctionalTest::stopMining() {
|
||
|
if (nodeDaemons.empty()) return false;
|
||
|
return nodeDaemons.front()->stopMining();
|
||
|
}
|
||
|
|
||
|
bool BaseFunctionalTest::makeWallet(std::unique_ptr<CryptoNote::IWallet> & wallet, std::unique_ptr<CryptoNote::INode>& node, const std::string& password) {
|
||
|
if (!node) return false;
|
||
|
wallet = std::unique_ptr<CryptoNote::IWallet>(new CryptoNote::Wallet(m_currency, *node));
|
||
|
wallet->initAndGenerate(password);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void BaseFunctionalTest::stopTestnet() {
|
||
|
for (auto& Daemon : nodeDaemons) {
|
||
|
Daemon->stopDaemon();
|
||
|
}
|
||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||
|
#ifdef __linux__
|
||
|
for (auto& pid : pids) {
|
||
|
int status;
|
||
|
while (-1 == waitpid(pid, &status, 0));
|
||
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||
|
std::cerr << "Process " << " (pid " << pid << ") failed" << std::endl;
|
||
|
exit(1);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|