danicoin/tests/integration_tests/Node.cpp
2015-05-27 13:28:09 +01:00

470 lines
13 KiB
C++

// Copyright (c) 2012-2015, 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 <gtest/gtest.h>
#include <fstream>
#include <future>
#include <Logging/ConsoleLogger.h>
#include <System/Dispatcher.h>
#include <System/Timer.h>
#include <serialization/JsonOutputStreamSerializer.h>
#include <serialization/JsonInputStreamSerializer.h>
#include <serialization/SerializationOverloads.h>
#include "cryptonote_core/Currency.h"
#include "cryptonote_core/cryptonote_format_utils.h"
#include "wallet/MultiWallet.h"
#include "../integration_test_lib/TestNetwork.h"
#include "../integration_test_lib/NodeObserver.h"
#include "BlockchainInfo.h"
using namespace Tests;
using namespace CryptoNote;
extern System::Dispatcher globalDispatcher;
class NodeCallback {
public:
INode::Callback callback() {
prom = std::promise<std::error_code>(); // reset std::promise
return [this](std::error_code ec) {
prom.set_value(ec);
};
}
std::error_code get() {
return prom.get_future().get();
}
private:
std::promise<std::error_code> prom;
};
class NodeTest: public testing::Test {
public:
NodeTest() :
currency(CryptoNote::CurrencyBuilder(logger).testnet(true).currency()),
network(globalDispatcher, currency) {
}
protected:
virtual void TearDown() override {
network.shutdown();
}
void startNetworkWithBlockchain(const std::string& sourcePath, size_t nodes = 2);
void readBlockchainInfo(INode& node, BlockchainInfo& bc);
void dumpBlockchainInfo(INode& node);
Logging::ConsoleLogger logger;
CryptoNote::Currency currency;
TestNetwork network;
};
void NodeTest::startNetworkWithBlockchain(const std::string& sourcePath, size_t nodes) {
auto networkCfg = TestNetworkBuilder(nodes, Topology::Ring).build();
for (auto& node : networkCfg) {
node.blockchainLocation = sourcePath;
}
network.addNodes(networkCfg);
network.waitNodesReady();
}
void NodeTest::readBlockchainInfo(INode& node, BlockchainInfo& bc) {
std::vector<crypto::hash> history = { currency.genesisBlockHash() };
uint64_t timestamp = 0;
uint64_t startHeight = 0;
size_t itemsAdded = 0;
NodeCallback cb;
bc.blocks = {
BlockCompleteEntry{ currency.genesisBlockHash(), block_to_blob(currency.genesisBlock()) }
};
do {
itemsAdded = 0;
std::list<BlockCompleteEntry> blocks;
node.queryBlocks(std::list<crypto::hash>(history.rbegin(), history.rend()), timestamp, blocks, startHeight, cb.callback());
ASSERT_TRUE(cb.get() == std::error_code());
uint64_t currentHeight = startHeight;
for (auto& entry : blocks) {
if (currentHeight < history.size()) {
// detach no expected
ASSERT_EQ(entry.blockHash, history[currentHeight]);
} else {
CryptoNote::Block block;
CryptoNote::parse_and_validate_block_from_blob(entry.block, block);
auto txHash = get_transaction_hash(block.minerTx);
std::vector<uint64_t> globalIndices;
node.getTransactionOutsGlobalIndices(txHash, globalIndices, cb.callback());
ASSERT_TRUE(!cb.get());
bc.globalOutputs.insert(std::make_pair(txHash, std::move(globalIndices)));
bc.blocks.push_back(entry);
history.push_back(entry.blockHash);
++itemsAdded;
}
++currentHeight;
}
} while (itemsAdded > 0);
}
void NodeTest::dumpBlockchainInfo(INode& node) {
BlockchainInfo bc;
ASSERT_NO_FATAL_FAILURE(readBlockchainInfo(node, bc));
storeBlockchainInfo("blocks.js", bc);
}
//TEST_F(NodeTest, generateBlockchain) {
//
// auto networkCfg = TestNetworkBuilder(2, Topology::Ring).build();
// networkCfg[0].cleanupDataDir = false;
// network.addNodes(networkCfg);
// network.waitNodesReady();
//
// auto& daemon = network.getNode(0);
//
// {
// std::unique_ptr<INode> mainNode;
// ASSERT_TRUE(daemon.makeINode(mainNode));
//
// std::string password = "pass";
// CryptoNote::MultiWallet wallet(globalDispatcher, currency, *mainNode);
//
// wallet.initialize(password);
//
// std::string minerAddress = wallet.createAddress();
// daemon.startMining(1, minerAddress);
//
// System::Timer timer(globalDispatcher);
//
// while (daemon.getLocalHeight() < 300) {
// std::cout << "Waiting for block..." << std::endl;
// timer.sleep(std::chrono::seconds(10));
// }
//
// daemon.stopMining();
//
// std::ofstream walletFile("wallet.bin", std::ios::binary | std::ios::trunc);
// wallet.save(walletFile);
// wallet.shutdown();
//
// dumpBlockchainInfo(*mainNode);
// }
//}
//
//
//TEST_F(NodeTest, addMoreBlocks) {
// auto networkCfg = TestNetworkBuilder(2, Topology::Ring).build();
// networkCfg[0].cleanupDataDir = false;
// networkCfg[0].blockchainLocation = "testnet_300";
// networkCfg[1].blockchainLocation = "testnet_300";
// network.addNodes(networkCfg);
// network.waitNodesReady();
//
// auto& daemon = network.getNode(0);
//
// {
// std::unique_ptr<INode> mainNode;
// ASSERT_TRUE(daemon.makeINode(mainNode));
//
// auto startHeight = daemon.getLocalHeight();
//
// std::string password = "pass";
// CryptoNote::MultiWallet wallet(globalDispatcher, currency, *mainNode);
//
// {
// std::ifstream walletFile("wallet.bin", std::ios::binary);
// wallet.load(walletFile, password);
// }
//
// std::string minerAddress = wallet.getAddress(0);
// daemon.startMining(1, minerAddress);
//
// System::Timer timer(globalDispatcher);
//
// while (daemon.getLocalHeight() <= startHeight + 3) {
// std::cout << "Waiting for block..." << std::endl;
// timer.sleep(std::chrono::seconds(1));
// }
//
// daemon.stopMining();
//
// std::ofstream walletFile("wallet.bin", std::ios::binary | std::ios::trunc);
// wallet.save(walletFile);
// wallet.shutdown();
//
// dumpBlockchainInfo(*mainNode);
// }
//}
//TEST_F(NodeTest, dumpBlockchain) {
// startNetworkWithBlockchain("testnet_300", 2);
// auto& daemon = network.getNode(0);
// std::unique_ptr<INode> mainNode;
// ASSERT_TRUE(daemon.makeINode(mainNode));
// dumpBlockchainInfo(*mainNode);
//}
class QueryBlocksTest : public NodeTest {
public:
virtual void SetUp() override {
NodeTest::SetUp();
loadBlockchainInfo("blocks.js", knownBc);
startNetworkWithBlockchain("testnet_300", 2);
auto& daemon = network.getNode(0);
// check full sync
ASSERT_TRUE(daemon.makeINode(mainNode));
}
virtual void TearDown() override {
mainNode.reset();
NodeTest::TearDown();
}
BlockchainInfo knownBc;
std::unique_ptr<INode> mainNode;
};
TEST_F(QueryBlocksTest, fullSync) {
BlockchainInfo nodeBc;
ASSERT_NO_FATAL_FAILURE(readBlockchainInfo(*mainNode, nodeBc));
ASSERT_EQ(knownBc, nodeBc);
}
TEST_F(QueryBlocksTest, queryByTimestamp) {
size_t pivotBlockIndex = knownBc.blocks.size() / 3 * 2;
Block block;
auto iter = knownBc.blocks.begin();
std::advance(iter, pivotBlockIndex);
parse_and_validate_block_from_blob(iter->block, block);
auto timestamp = block.timestamp - 1;
uint64_t startHeight = 0;
std::list<BlockCompleteEntry> blocks;
std::cout << "Requesting timestamp: " << timestamp << std::endl;
NodeCallback cb;
std::list<crypto::hash> history = { currency.genesisBlockHash() };
mainNode->queryBlocks(std::list<crypto::hash>(history), timestamp, blocks, startHeight, cb.callback());
ASSERT_TRUE(!cb.get());
EXPECT_EQ(0, startHeight);
EXPECT_EQ(knownBc.blocks.begin()->blockHash, blocks.begin()->blockHash);
EXPECT_EQ(knownBc.blocks.size(), blocks.size());
auto startBlockIter = std::find_if(blocks.begin(), blocks.end(), [](const BlockCompleteEntry& e) { return !e.block.empty(); });
ASSERT_TRUE(startBlockIter != blocks.end());
Block startBlock;
ASSERT_TRUE(parse_and_validate_block_from_blob(startBlockIter->block, startBlock));
std::cout << "Starting block timestamp: " << startBlock.timestamp << std::endl;
auto startFullIndex = std::distance(blocks.begin(), startBlockIter);
auto it = blocks.begin();
for (const auto& item : knownBc.blocks) {
ASSERT_EQ(item.blockHash, it->blockHash);
++it;
}
ASSERT_EQ(pivotBlockIndex, startFullIndex);
}
TEST_F(QueryBlocksTest, queryHistory) {
NodeCallback cb;
uint64_t startHeight = 0;
std::list<BlockCompleteEntry> blocks;
// random genesis block hash -> error
auto randomHash = crypto::rand<crypto::hash>();
mainNode->queryBlocks({ randomHash }, 0, blocks, startHeight, cb.callback());
ASSERT_FALSE(!cb.get());
// unknown block - start from first known
mainNode->queryBlocks({ randomHash, currency.genesisBlockHash() }, 0, blocks, startHeight, cb.callback());
ASSERT_TRUE(!cb.get());
ASSERT_EQ(0, startHeight);
ASSERT_GT(blocks.size(), 1);
ASSERT_EQ(currency.genesisBlockHash(), blocks.begin()->blockHash);
for (size_t idx = 10; idx <= 100; idx += 10) {
blocks.clear();
startHeight = 0;
const auto& knownBlockHash = knownBc.blocks[idx].blockHash;
std::list<crypto::hash> history = { knownBlockHash, currency.genesisBlockHash() };
mainNode->queryBlocks(std::list<crypto::hash>(history), 0, blocks, startHeight, cb.callback());
ASSERT_TRUE(!cb.get());
EXPECT_EQ(idx, startHeight);
EXPECT_EQ(knownBlockHash, blocks.begin()->blockHash);
}
}
TEST_F(NodeTest, queryBlocks) {
BlockchainInfo knownBc, nodeBc;
loadBlockchainInfo("blocks.js", knownBc);
startNetworkWithBlockchain("testnet_300", 2);
auto& daemon = network.getNode(0);
std::unique_ptr<INode> mainNode;
// check full sync
ASSERT_TRUE(daemon.makeINode(mainNode));
ASSERT_NO_FATAL_FAILURE(readBlockchainInfo(*mainNode, nodeBc));
ASSERT_EQ(knownBc, nodeBc);
// check query with timestamp
size_t pivotBlockIndex = knownBc.blocks.size() / 3 * 2;
Block block;
auto iter = knownBc.blocks.begin();
std::advance(iter, pivotBlockIndex);
parse_and_validate_block_from_blob(iter->block, block);
auto timestamp = block.timestamp - 1;
uint64_t startHeight = 0;
std::list<BlockCompleteEntry> blocks;
std::cout << "Requesting timestamp: " << timestamp << std::endl;
NodeCallback cb;
std::list<crypto::hash> history = { currency.genesisBlockHash() };
mainNode->queryBlocks(std::list<crypto::hash>(history), timestamp, blocks, startHeight, cb.callback());
ASSERT_TRUE(!cb.get());
EXPECT_EQ(0, startHeight);
EXPECT_EQ(knownBc.blocks.begin()->blockHash, blocks.begin()->blockHash);
EXPECT_EQ(knownBc.blocks.size(), blocks.size());
auto startBlockIter = std::find_if(blocks.begin(), blocks.end(), [](const BlockCompleteEntry& e) { return !e.block.empty(); });
ASSERT_TRUE(startBlockIter != blocks.end());
Block startBlock;
ASSERT_TRUE(parse_and_validate_block_from_blob(startBlockIter->block, startBlock));
std::cout << "Starting block timestamp: " << startBlock.timestamp << std::endl;
auto startFullIndex = std::distance(blocks.begin(), startBlockIter);
auto it = blocks.begin();
for (const auto& item : knownBc.blocks) {
ASSERT_EQ(item.blockHash, it->blockHash);
++it;
}
ASSERT_EQ(pivotBlockIndex, startFullIndex);
}
TEST_F(NodeTest, observerHeightNotifications) {
BlockchainInfo extraBlocks;
loadBlockchainInfo("blocks_extra.js", extraBlocks);
startNetworkWithBlockchain("testnet_300");
auto& daemon = network.getNode(0);
{
std::unique_ptr<INode> mainNode;
daemon.makeINode(mainNode);
NodeObserver observer(*mainNode);
std::chrono::seconds timeout(10);
uint64_t knownHeight = 0;
uint64_t localHeight = 0;
size_t peerCount = 0;
EXPECT_TRUE(observer.m_localHeight.waitFor(timeout, localHeight));
EXPECT_TRUE(observer.m_knownHeight.waitFor(timeout, knownHeight));
EXPECT_TRUE(observer.m_peerCount.waitFor(timeout, peerCount));
EXPECT_GT(localHeight, 0);
EXPECT_GT(knownHeight, 0);
EXPECT_GT(peerCount, 0);
std::cout << "Local height = " << localHeight << std::endl;
std::cout << "Known height = " << knownHeight << std::endl;
std::cout << "Peer count = " << peerCount << std::endl;
EXPECT_EQ(localHeight, mainNode->getLastLocalBlockHeight());
EXPECT_EQ(knownHeight, mainNode->getLastKnownBlockHeight());
// submit 1 block and check observer
uint64_t newKnownHeight = 0;
uint64_t newLocalHeight = 0;
auto blockData = extraBlocks.blocks.begin()->block;
ASSERT_TRUE(daemon.submitBlock(Common::toHex(blockData.data(), blockData.size())));
ASSERT_TRUE(observer.m_localHeight.waitFor(timeout, newLocalHeight));
ASSERT_TRUE(observer.m_knownHeight.waitFor(timeout, newKnownHeight));
size_t blocksSubmitted = 1;
EXPECT_EQ(localHeight + blocksSubmitted, newLocalHeight);
EXPECT_EQ(knownHeight + blocksSubmitted, newKnownHeight);
EXPECT_EQ(newLocalHeight, mainNode->getLastLocalBlockHeight());
EXPECT_EQ(newKnownHeight, mainNode->getLastKnownBlockHeight());
std::cout << "Local height = " << newLocalHeight << std::endl;
std::cout << "Known height = " << newKnownHeight << std::endl;
}
}