danicoin/src/BlockchainExplorer/BlockchainExplorer.cpp

524 lines
18 KiB
C++
Raw Normal View History

2015-07-15 12:23:00 +00:00
// 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 "BlockchainExplorer.h"
#include <future>
#include <functional>
#include <memory>
#include "cryptonote_core/cryptonote_format_utils.h"
#include "BlockchainExplorerErrors.h"
using namespace Logging;
namespace CryptoNote {
class NodeRequest {
public:
NodeRequest(const std::function<void(const INode::Callback&)>& request) : requestFunc(request) {}
std::error_code performBlocking() {
requestFunc(std::bind(&NodeRequest::completeionCallback, this, std::placeholders::_1));
return promise.get_future().get();
}
void performAsync(const INode::Callback& callback) {
requestFunc(callback);
}
private:
void completeionCallback(std::error_code ec) {
promise.set_value(ec);
}
std::promise<std::error_code> promise;
const std::function<void(const INode::Callback&)> requestFunc;
};
BlockchainExplorer::BlockchainExplorer(INode& node, Logging::ILogger& logger) : node(node), logger(logger, "BlockchainExplorer"), state(NOT_INITIALIZED) {}
BlockchainExplorer::~BlockchainExplorer() {}
bool BlockchainExplorer::addObserver(IBlockchainObserver* observer) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
return observerManager.add(observer);
}
bool BlockchainExplorer::removeObserver(IBlockchainObserver* observer) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
return observerManager.remove(observer);
}
void BlockchainExplorer::init() {
if (state.load() != NOT_INITIALIZED) {
logger(ERROR) << "Init called on already initialized BlockchainExplorer.";
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::ALREADY_INITIALIZED));
}
if (node.addObserver(this)) {
state.store(INITIALIZED);
} else {
logger(ERROR) << "Can't add observer to node.";
state.store(NOT_INITIALIZED);
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::INTERNAL_ERROR));
}
if (getBlockchainTop(knownBlockchainTop)) {
knownBlockchainTopHeight = knownBlockchainTop.height;
} else {
logger(ERROR) << "Can't get blockchain top.";
state.store(NOT_INITIALIZED);
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::INTERNAL_ERROR));
}
}
void BlockchainExplorer::shutdown() {
if (state.load() != INITIALIZED) {
logger(ERROR) << "Shutdown called on not initialized BlockchainExplorer.";
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
node.removeObserver(this);
state.store(NOT_INITIALIZED);
}
bool BlockchainExplorer::getBlocks(const std::vector<uint64_t>& blockHeights, std::vector<std::vector<BlockDetails>>& blocks) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
logger(DEBUGGING) << "Get blocks by height request came.";
NodeRequest request(
std::bind(
static_cast<
void(INode::*)(
const std::vector<uint64_t>&,
std::vector<std::vector<BlockDetails>>&,
const INode::Callback&
)
>(&INode::getBlocks),
std::ref(node),
std::cref(blockHeights),
std::ref(blocks),
std::placeholders::_1
)
);
std::error_code ec = request.performBlocking();
if (ec) {
logger(ERROR) << "Can't get blocks by height: " << ec.message();
throw std::system_error(ec);
}
assert(blocks.size() == blockHeights.size());
return true;
}
bool BlockchainExplorer::getBlocks(const std::vector<std::array<uint8_t, 32>>& blockHashes, std::vector<BlockDetails>& blocks) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
logger(DEBUGGING) << "Get blocks by hash request came.";
NodeRequest request(
std::bind(
static_cast<
void(INode::*)(
const std::vector<crypto::hash>&,
std::vector<BlockDetails>&,
const INode::Callback&
)
>(&INode::getBlocks),
std::ref(node),
std::cref(reinterpret_cast<const std::vector<crypto::hash>&>(blockHashes)),
std::ref(blocks),
std::placeholders::_1
)
);
std::error_code ec = request.performBlocking();
if (ec) {
logger(ERROR) << "Can't get blocks by hash: " << ec.message();
throw std::system_error(ec);
}
assert(blocks.size() == blockHashes.size());
return true;
}
bool BlockchainExplorer::getBlockchainTop(BlockDetails& topBlock) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
logger(DEBUGGING) << "Get blockchain top request came.";
uint64_t lastHeight = node.getLastLocalBlockHeight();
std::vector<uint64_t> heights;
heights.push_back(std::move(lastHeight));
std::vector<std::vector<BlockDetails>> blocks;
if (!getBlocks(heights, blocks)) {
logger(ERROR) << "Can't get blockchain top.";
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::INTERNAL_ERROR));
}
assert(blocks.size() == heights.size() && blocks.size() == 1);
bool gotMainchainBlock = false;
for (const BlockDetails& block : blocks.back()) {
if (!block.isOrphaned) {
topBlock = block;
gotMainchainBlock = true;
break;
}
}
if (!gotMainchainBlock) {
logger(ERROR) << "Can't get blockchain top: all blocks on height " << lastHeight << " are orphaned.";
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::INTERNAL_ERROR));
}
return true;
}
bool BlockchainExplorer::getTransactions(const std::vector<std::array<uint8_t, 32>>& transactionHashes, std::vector<TransactionDetails>& transactions) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
logger(DEBUGGING) << "Get transactions request came.";
NodeRequest request(
std::bind(
&INode::getTransactions,
std::ref(node),
std::cref(reinterpret_cast<const std::vector<crypto::hash>&>(transactionHashes)),
std::ref(transactions),
std::placeholders::_1
)
);
std::error_code ec = request.performBlocking();
if (ec) {
logger(ERROR) << "Can't get transactions: " << ec.message();
throw std::system_error(ec);
}
return true;
}
bool BlockchainExplorer::getPoolState(const std::vector<std::array<uint8_t, 32>>& knownPoolTransactionHashes, std::array<uint8_t, 32> knownBlockchainTopHash, bool& isBlockchainActual, std::vector<TransactionDetails>& newTransactions, std::vector<std::array<uint8_t, 32>>& removedTransactions) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
logger(DEBUGGING) << "Get pool state request came.";
std::vector<Transaction> rawNewTransactions;
NodeRequest request(
[&](const INode::Callback& callback) {
std::vector<crypto::hash> hashes;
for (const std::array<uint8_t, 32>& hash : knownPoolTransactionHashes) {
hashes.push_back(std::move(reinterpret_cast<const crypto::hash&>(hash)));
}
node.getPoolSymmetricDifference(
std::move(hashes),
reinterpret_cast<crypto::hash&>(knownBlockchainTopHash),
isBlockchainActual,
rawNewTransactions,
reinterpret_cast<std::vector<crypto::hash>&>(removedTransactions),
callback
);
}
);
std::error_code ec = request.performBlocking();
if (ec) {
logger(ERROR) << "Can't get pool state: " << ec.message();
throw std::system_error(ec);
}
std::vector<std::array<uint8_t, 32>> newTransactionsHashes;
for (const Transaction& rawTransaction : rawNewTransactions) {
crypto::hash transactionHash = get_transaction_hash(rawTransaction);
newTransactionsHashes.push_back(std::move(reinterpret_cast<const std::array<uint8_t, 32>&>(transactionHash)));
}
return getTransactions(newTransactionsHashes, newTransactions);
}
uint64_t BlockchainExplorer::getRewardBlocksWindow() {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
return parameters::CRYPTONOTE_REWARD_BLOCKS_WINDOW;
}
uint64_t BlockchainExplorer::getFullRewardMaxBlockSize(uint8_t majorVersion) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
if (majorVersion > 1) {
return parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE;
} else {
return parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1;
}
}
bool BlockchainExplorer::isSynchronized() {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
logger(DEBUGGING) << "Synchronization status request came.";
bool syncStatus = false;
NodeRequest request(
std::bind(
&INode::isSynchronized,
std::ref(node),
std::ref(syncStatus),
std::placeholders::_1
)
);
std::error_code ec = request.performBlocking();
if (ec) {
logger(ERROR) << "Can't get synchronization status: " << ec.message();
throw std::system_error(ec);
}
return syncStatus;
}
void BlockchainExplorer::poolChanged() {
logger(DEBUGGING) << "Got poolChanged notification.";
std::unique_lock<std::mutex> lock(mutex);
std::shared_ptr<std::vector<Transaction>> rawNewTransactionsPtr = std::make_shared<std::vector<Transaction>>();
std::shared_ptr<std::vector<crypto::hash>> removedTransactionsPtr = std::make_shared<std::vector<crypto::hash>>();
std::shared_ptr<bool> isBlockchainActualPtr = std::make_shared<bool>(false);
NodeRequest request(
[this, rawNewTransactionsPtr, removedTransactionsPtr, isBlockchainActualPtr](const INode::Callback& callback) {
std::vector<crypto::hash> hashes;
for (const crypto::hash& hash : knownPoolState) {
hashes.push_back(std::move(hash));
}
node.getPoolSymmetricDifference(
std::move(hashes),
reinterpret_cast<crypto::hash&>(knownBlockchainTop.hash),
*isBlockchainActualPtr,
*rawNewTransactionsPtr,
*removedTransactionsPtr,
callback
);
}
);
request.performAsync(
[this, rawNewTransactionsPtr, removedTransactionsPtr, isBlockchainActualPtr](std::error_code ec) {
if (ec) {
logger(ERROR) << "Can't send poolChanged notification because can't get pool symmetric difference: " << ec.message();
return;
}
if (!*isBlockchainActualPtr) {
logger(WARNING) << "Blockchain not actual.";
}
std::unique_lock<std::mutex> lock(mutex);
std::shared_ptr<std::vector<crypto::hash>> newTransactionsHashesPtr = std::make_shared<std::vector<crypto::hash>>();
for (const Transaction& rawTransaction : *rawNewTransactionsPtr) {
crypto::hash transactionHash = get_transaction_hash(rawTransaction);
bool inserted = knownPoolState.emplace(transactionHash).second;
if (inserted) {
newTransactionsHashesPtr->push_back(std::move(transactionHash));
}
}
std::shared_ptr<std::vector<std::pair<std::array<uint8_t, 32>, TransactionRemoveReason>>> removedTransactionsHashesPtr = std::make_shared<std::vector<std::pair<std::array<uint8_t, 32>, TransactionRemoveReason>>>();
for (const crypto::hash hash : *removedTransactionsPtr) {
auto iter = knownPoolState.find(hash);
if (iter != knownPoolState.end()) {
removedTransactionsHashesPtr->push_back(
std::move(std::make_pair(
reinterpret_cast<const std::array<uint8_t, 32>&>(hash),
TransactionRemoveReason::INCLUDED_IN_BLOCK //Can't have real reason here.
))
);
knownPoolState.erase(iter);
}
}
std::shared_ptr<std::vector<TransactionDetails>> newTransactionsPtr = std::make_shared<std::vector<TransactionDetails>>();
NodeRequest request(
std::bind(
&INode::getTransactions,
std::ref(node),
std::cref(*newTransactionsHashesPtr),
std::ref(*newTransactionsPtr),
std::placeholders::_1
)
);
request.performAsync(
[this, newTransactionsHashesPtr, newTransactionsPtr, removedTransactionsHashesPtr](std::error_code ec) {
if (ec) {
logger(ERROR) << "Can't send poolChanged notification because can't get transactions: " << ec.message();
return;
}
if (!newTransactionsPtr->empty() || !removedTransactionsHashesPtr->empty()) {
observerManager.notify(&IBlockchainObserver::poolUpdated, *newTransactionsPtr, *removedTransactionsHashesPtr);
logger(DEBUGGING) << "poolUpdated notification was successfully sent.";
}
}
);
}
);
}
void BlockchainExplorer::blockchainSynchronized(uint64_t topHeight) {
logger(DEBUGGING) << "Got blockchainSynchronized notification.";
std::shared_ptr<std::vector<uint64_t>> blockHeightsPtr = std::make_shared<std::vector<uint64_t>>();
std::shared_ptr<std::vector<std::vector<BlockDetails>>> blocksPtr = std::make_shared<std::vector<std::vector<BlockDetails>>>();
blockHeightsPtr->push_back(topHeight);
NodeRequest request(
std::bind(
static_cast<
void(INode::*)(
const std::vector<uint64_t>&,
std::vector<std::vector<BlockDetails>>&,
const INode::Callback&
)
>(&INode::getBlocks),
std::ref(node),
std::cref(*blockHeightsPtr),
std::ref(*blocksPtr),
std::placeholders::_1
)
);
request.performAsync(
[this, blockHeightsPtr, blocksPtr, topHeight](std::error_code ec) {
if (ec) {
logger(ERROR) << "Can't send blockchainSynchronized notification because can't get blocks by height: " << ec.message();
return;
}
assert(blocksPtr->size() == blockHeightsPtr->size() && blocksPtr->size() == 1);
BlockDetails topMainchainBlock;
bool gotMainchainBlock = false;
for (const BlockDetails& block : blocksPtr->back()) {
if (!block.isOrphaned) {
topMainchainBlock = block;
gotMainchainBlock = true;
break;
}
}
if (!gotMainchainBlock) {
logger(ERROR) << "Can't send blockchainSynchronized notification because can't get blockchain top: all blocks on height " << topHeight << " are orphaned.";
return;
}
observerManager.notify(&IBlockchainObserver::blockchainSynchronized, topMainchainBlock);
logger(DEBUGGING) << "blockchainSynchronized notification was successfully sent.";
}
);
}
void BlockchainExplorer::localBlockchainUpdated(uint64_t height) {
logger(DEBUGGING) << "Got localBlockchainUpdated notification.";
std::unique_lock<std::mutex> lock(mutex);
assert(height >= knownBlockchainTopHeight);
std::shared_ptr<std::vector<uint64_t>> blockHeightsPtr = std::make_shared<std::vector<uint64_t>>();
std::shared_ptr<std::vector<std::vector<BlockDetails>>> blocksPtr = std::make_shared<std::vector<std::vector<BlockDetails>>>();
for (size_t i = knownBlockchainTopHeight; i <= height; ++i) {
blockHeightsPtr->push_back(i);
}
knownBlockchainTopHeight = height;
NodeRequest request(
std::bind(
static_cast<
void(INode::*)(
const std::vector<uint64_t>&,
std::vector<std::vector<BlockDetails>>&,
const INode::Callback&
)
>(&INode::getBlocks),
std::ref(node),
std::cref(*blockHeightsPtr),
std::ref(*blocksPtr),
std::placeholders::_1
)
);
request.performAsync(
[this, blockHeightsPtr, blocksPtr](std::error_code ec) {
if (ec) {
logger(ERROR) << "Can't send blockchainUpdated notification because can't get blocks by height: " << ec.message();
return;
}
assert(blocksPtr->size() == blockHeightsPtr->size());
std::unique_lock<std::mutex> lock(mutex);
BlockDetails topMainchainBlock;
bool gotTopMainchainBlock = false;
uint64_t topHeight = 0;
std::vector<BlockDetails> newBlocks;
std::vector<BlockDetails> orphanedBlocks;
for (const std::vector<BlockDetails>& sameHeightBlocks : *blocksPtr) {
for (const BlockDetails& block : sameHeightBlocks) {
if (topHeight < block.height) {
topHeight = block.height;
gotTopMainchainBlock = false;
}
if (block.isOrphaned) {
orphanedBlocks.push_back(block);
} else {
if (block.height > knownBlockchainTop.height || block.hash != knownBlockchainTop.hash) {
newBlocks.push_back(block);
}
if (!gotTopMainchainBlock) {
topMainchainBlock = block;
gotTopMainchainBlock = true;
}
}
}
}
if (!gotTopMainchainBlock) {
logger(ERROR) << "Can't send localBlockchainUpdated notification because can't get blockchain top: all blocks on height " << topHeight << " are orphaned.";
return;
}
knownBlockchainTop = topMainchainBlock;
observerManager.notify(&IBlockchainObserver::blockchainUpdated, newBlocks, orphanedBlocks);
logger(DEBUGGING) << "localBlockchainUpdated notification was successfully sent.";
}
);
}
}