// 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 . #include #include #include #include #include #include #include #include #include #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(); // 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 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 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 blocks; node.queryBlocks(std::list(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 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 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 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 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 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 blocks; std::cout << "Requesting timestamp: " << timestamp << std::endl; NodeCallback cb; std::list history = { currency.genesisBlockHash() }; mainNode->queryBlocks(std::list(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 blocks; // random genesis block hash -> error auto randomHash = crypto::rand(); 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 history = { knownBlockHash, currency.genesisBlockHash() }; mainNode->queryBlocks(std::list(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 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 blocks; std::cout << "Requesting timestamp: " << timestamp << std::endl; NodeCallback cb; std::list history = { currency.genesisBlockHash() }; mainNode->queryBlocks(std::list(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 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; } }