danicoin/tests/IntegrationTests/Node.cpp
2016-01-18 15:33:29 +00:00

368 lines
10 KiB
C++

// 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 <gtest/gtest.h>
#include <fstream>
#include <Common/StringTools.h>
#include <System/Dispatcher.h>
#include <System/Timer.h>
#include <Serialization/JsonOutputStreamSerializer.h>
#include <Serialization/JsonInputStreamSerializer.h>
#include <Serialization/SerializationOverloads.h>
#include "CryptoNoteCore/Currency.h"
#include "CryptoNoteCore/CryptoNoteTools.h"
#include "Wallet/WalletGreen.h"
#include "../IntegrationTestLib/TestNetwork.h"
#include "../IntegrationTestLib/NodeObserver.h"
#include "../IntegrationTestLib/NodeCallback.h"
#include "BaseTests.h"
using namespace Tests;
using namespace CryptoNote;
namespace CryptoNote {
void serialize(BlockShortEntry& v, ISerializer& s) {
s(v.blockHash, "hash");
if (s.type() == ISerializer::INPUT) {
std::string blockBinary;
if (s.binary(blockBinary, "block")) {
fromBinaryArray(v.block, Common::asBinaryArray(blockBinary));
v.hasBlock = true;
}
} else {
if (v.hasBlock) {
std::string blockBinary(Common::asString(toBinaryArray(v.block)));
s.binary(blockBinary, "block");
}
}
s(v.txsShortInfo, "transactions");
}
void serialize(TransactionShortInfo& v, ISerializer& s) {
s(v.txId, "hash");
s(v.txPrefix, "prefix");
}
bool operator == (const BlockShortEntry& a, const BlockShortEntry& b) {
return
a.blockHash == b.blockHash &&
a.hasBlock == b.hasBlock &&
// a.block == b.block &&
a.txsShortInfo == b.txsShortInfo;
}
bool operator == (const TransactionShortInfo& a, const TransactionShortInfo& b) {
return a.txId == b.txId;
}
}
struct BlockchainInfo {
std::list<BlockShortEntry> blocks;
std::unordered_map<Crypto::Hash, std::vector<uint32_t>> globalOutputs;
bool operator == (const BlockchainInfo& other) const {
return blocks == other.blocks && globalOutputs == other.globalOutputs;
}
void serialize(ISerializer& s) {
s(blocks, "blocks");
s(globalOutputs, "outputs");
}
};
void storeBlockchainInfo(const std::string& filename, BlockchainInfo& bc) {
JsonOutputStreamSerializer s;
serialize(bc, s);
std::ofstream jsonBlocks(filename, std::ios::trunc);
jsonBlocks << s.getValue();
}
void loadBlockchainInfo(const std::string& filename, BlockchainInfo& bc) {
std::ifstream jsonBlocks(filename);
JsonInputStreamSerializer s(jsonBlocks);
serialize(bc, s);
}
class NodeTest: public BaseTest {
protected:
void startNetworkWithBlockchain(const std::string& sourcePath);
void readBlockchainInfo(INode& node, BlockchainInfo& bc);
void dumpBlockchainInfo(INode& node);
};
void NodeTest::startNetworkWithBlockchain(const std::string& sourcePath) {
auto networkCfg = TestNetworkBuilder(2, 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;
uint32_t startHeight = 0;
size_t itemsAdded = 0;
NodeCallback cb;
bc.blocks = {
BlockShortEntry{ currency.genesisBlockHash(), true, currency.genesisBlock() }
};
do {
itemsAdded = 0;
std::vector<BlockShortEntry> blocks;
node.queryBlocks(std::vector<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 {
auto txHash = getObjectHash(entry.block.baseTransaction);
std::vector<uint32_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::WalletGreen wallet(dispatcher, currency, *mainNode);
wallet.initialize(password);
std::string minerAddress = wallet.createAddress();
daemon.startMining(1, minerAddress);
System::Timer timer(dispatcher);
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, dumpBlockchain) {
startNetworkWithBlockchain("testnet_300");
auto& daemon = network.getNode(0);
auto mainNode = network.getNode(0).makeINode();
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::WalletGreen wallet(dispatcher, 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(dispatcher);
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, queryBlocks) {
BlockchainInfo knownBc, nodeBc;
loadBlockchainInfo("blocks.js", knownBc);
startNetworkWithBlockchain("testnet_300");
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;
auto iter = knownBc.blocks.begin();
std::advance(iter, pivotBlockIndex);
ASSERT_TRUE(iter->hasBlock);
auto timestamp = iter->block.timestamp - 1;
uint32_t startHeight = 0;
std::vector<BlockShortEntry> blocks;
std::cout << "Requesting timestamp: " << timestamp << std::endl;
NodeCallback cb;
mainNode->queryBlocks({ currency.genesisBlockHash() }, 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 BlockShortEntry& e) { return e.hasBlock; });
ASSERT_TRUE(startBlockIter != blocks.end());
const Block& startBlock = startBlockIter->block;
std::cout << "Starting block timestamp: " << startBlock.timestamp << std::endl;
auto startFullIndex = std::distance(blocks.begin(), startBlockIter);
EXPECT_EQ(pivotBlockIndex, startFullIndex);
auto it = blocks.begin();
for (const auto& item : knownBc.blocks) {
ASSERT_EQ(item.blockHash, it->blockHash);
++it;
}
}
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);
uint32_t knownHeight = 0;
uint32_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, 0U);
EXPECT_GT(knownHeight, 0U);
EXPECT_GT(peerCount, 0U);
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
uint32_t newKnownHeight = 0;
uint32_t newLocalHeight = 0;
auto blockData = toBinaryArray(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());
}
}