danicoin/src/BlockchainExplorer/BlockchainExplorer.cpp
2016-01-18 15:33:29 +00:00

737 lines
23 KiB
C++
Executable file

// 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 "BlockchainExplorer.h"
#include <future>
#include <functional>
#include <memory>
#include "CryptoNoteCore/CryptoNoteFormatUtils.h"
#include "CryptoNoteConfig.h"
#include "BlockchainExplorerErrors.h"
#include "ITransaction.h"
using namespace Logging;
using namespace Crypto;
namespace CryptoNote {
class ContextCounterHolder
{
public:
ContextCounterHolder(BlockchainExplorer::AsyncContextCounter& counter) : counter(counter) {}
~ContextCounterHolder() { counter.delAsyncContext(); }
private:
BlockchainExplorer::AsyncContextCounter& counter;
};
class NodeRequest {
public:
NodeRequest(const std::function<void(const INode::Callback&)>& request) : requestFunc(request) {}
std::error_code performBlocking() {
std::promise<std::error_code> promise;
std::future<std::error_code> future = promise.get_future();
requestFunc([&](std::error_code c){
blockingCompleteionCallback(std::move(promise), c);
});
return future.get();
}
void performAsync(BlockchainExplorer::AsyncContextCounter& asyncContextCounter, const INode::Callback& callback) {
asyncContextCounter.addAsyncContext();
requestFunc(std::bind(&NodeRequest::asyncCompleteionCallback, callback, std::ref(asyncContextCounter), std::placeholders::_1));
}
private:
void blockingCompleteionCallback(std::promise<std::error_code> promise, std::error_code ec) {
promise.set_value(ec);
}
static void asyncCompleteionCallback(const INode::Callback& callback, BlockchainExplorer::AsyncContextCounter& asyncContextCounter, std::error_code ec) {
ContextCounterHolder counterHolder(asyncContextCounter);
try {
callback(ec);
} catch (...) {
return;
}
}
const std::function<void(const INode::Callback&)> requestFunc;
};
BlockchainExplorer::PoolUpdateGuard::PoolUpdateGuard() :
m_state(State::NONE) {
}
bool BlockchainExplorer::PoolUpdateGuard::beginUpdate() {
auto state = m_state.load();
for (;;) {
switch (state) {
case State::NONE:
if (m_state.compare_exchange_weak(state, State::UPDATING)) {
return true;
}
break;
case State::UPDATING:
if (m_state.compare_exchange_weak(state, State::UPDATE_REQUIRED)) {
return false;
}
break;
case State::UPDATE_REQUIRED:
return false;
default:
assert(false);
return false;
}
}
}
bool BlockchainExplorer::PoolUpdateGuard::endUpdate() {
auto state = m_state.load();
for (;;) {
assert(state != State::NONE);
if (m_state.compare_exchange_weak(state, State::NONE)) {
return state == State::UPDATE_REQUIRED;
}
}
}
class ScopeExitHandler {
public:
ScopeExitHandler(std::function<void()>&& handler) :
m_handler(std::move(handler)),
m_cancelled(false) {
}
~ScopeExitHandler() {
if (!m_cancelled) {
m_handler();
}
}
void reset() {
m_cancelled = true;
}
private:
std::function<void()> m_handler;
bool m_cancelled;
};
BlockchainExplorer::BlockchainExplorer(INode& node, Logging::ILogger& logger) :
node(node),
logger(logger, "BlockchainExplorer"),
state(NOT_INITIALIZED),
synchronized(false),
observersCounter(0) {
}
BlockchainExplorer::~BlockchainExplorer() {}
bool BlockchainExplorer::addObserver(IBlockchainObserver* observer) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
observersCounter.fetch_add(1);
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));
}
if (observersCounter.load() != 0) {
observersCounter.fetch_sub(1);
}
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);
asyncContextCounter.waitAsyncContextsFinish();
state.store(NOT_INITIALIZED);
}
bool BlockchainExplorer::getBlocks(const std::vector<uint32_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<uint32_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<Hash>& 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<Hash>&,
std::vector<BlockDetails>&,
const INode::Callback&
)
>(&INode::getBlocks),
std::ref(node),
std::cref(reinterpret_cast<const std::vector<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::getBlocks(uint64_t timestampBegin, uint64_t timestampEnd, uint32_t blocksNumberLimit, std::vector<BlockDetails>& blocks, uint32_t& blocksNumberWithinTimestamps) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
logger(DEBUGGING) << "Get blocks by timestamp request came.";
NodeRequest request(
std::bind(
static_cast<
void(INode::*)(
uint64_t,
uint64_t,
uint32_t,
std::vector<BlockDetails>&,
uint32_t&,
const INode::Callback&
)
>(&INode::getBlocks),
std::ref(node),
timestampBegin,
timestampEnd,
blocksNumberLimit,
std::ref(blocks),
std::ref(blocksNumberWithinTimestamps),
std::placeholders::_1
)
);
std::error_code ec = request.performBlocking();
if (ec) {
logger(ERROR) << "Can't get blocks by timestamp: " << ec.message();
throw std::system_error(ec);
}
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.";
uint32_t lastHeight = node.getLastLocalBlockHeight();
std::vector<uint32_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<Hash>& 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 by hash request came.";
NodeRequest request(
std::bind(
static_cast<
void(INode::*)(
const std::vector<Hash>&,
std::vector<TransactionDetails>&,
const INode::Callback&
)
>(&INode::getTransactions),
std::ref(node),
std::cref(reinterpret_cast<const std::vector<Hash>&>(transactionHashes)),
std::ref(transactions),
std::placeholders::_1
)
);
std::error_code ec = request.performBlocking();
if (ec) {
logger(ERROR) << "Can't get transactions by hash: " << ec.message();
throw std::system_error(ec);
}
return true;
}
bool BlockchainExplorer::getPoolTransactions(uint64_t timestampBegin, uint64_t timestampEnd, uint32_t transactionsNumberLimit, std::vector<TransactionDetails>& transactions, uint64_t& transactionsNumberWithinTimestamps) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
logger(DEBUGGING) << "Get transactions by timestamp request came.";
NodeRequest request(
std::bind(
&INode::getPoolTransactions,
std::ref(node),
timestampBegin,
timestampEnd,
transactionsNumberLimit,
std::ref(transactions),
std::ref(transactionsNumberWithinTimestamps),
std::placeholders::_1
)
);
std::error_code ec = request.performBlocking();
if (ec) {
logger(ERROR) << "Can't get transactions by timestamp: " << ec.message();
throw std::system_error(ec);
}
return true;
}
bool BlockchainExplorer::getTransactionsByPaymentId(const Hash& paymentId, std::vector<TransactionDetails>& transactions) {
if (state.load() != INITIALIZED) {
throw std::system_error(make_error_code(CryptoNote::error::BlockchainExplorerErrorCodes::NOT_INITIALIZED));
}
logger(DEBUGGING) << "Get transactions by payment id request came.";
NodeRequest request(
std::bind(
&INode::getTransactionsByPaymentId,
std::ref(node),
std::cref(reinterpret_cast<const Hash&>(paymentId)),
std::ref(transactions),
std::placeholders::_1
)
);
std::error_code ec = request.performBlocking();
if (ec) {
logger(ERROR) << "Can't get transactions by payment id: " << ec.message();
throw std::system_error(ec);
}
return true;
}
bool BlockchainExplorer::getPoolState(const std::vector<Hash>& knownPoolTransactionHashes, Hash knownBlockchainTopHash, bool& isBlockchainActual, std::vector<TransactionDetails>& newTransactions, std::vector<Hash>& 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<std::unique_ptr<ITransactionReader>> rawNewTransactions;
NodeRequest request(
[&](const INode::Callback& callback) {
std::vector<Hash> hashes;
for (Hash hash : knownPoolTransactionHashes) {
hashes.push_back(std::move(hash));
}
node.getPoolSymmetricDifference(
std::move(hashes),
reinterpret_cast<Hash&>(knownBlockchainTopHash),
isBlockchainActual,
rawNewTransactions,
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<Hash> newTransactionsHashes;
for (const auto& rawTransaction : rawNewTransactions) {
Hash transactionHash = rawTransaction->getTransactionHash();
newTransactionsHashes.push_back(std::move(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));
}
return parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE;
}
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);
}
synchronized.store(syncStatus);
return syncStatus;
}
void BlockchainExplorer::poolChanged() {
logger(DEBUGGING) << "Got poolChanged notification.";
if (!synchronized.load() || observersCounter.load() == 0) {
return;
}
if (!poolUpdateGuard.beginUpdate()) {
return;
}
ScopeExitHandler poolUpdateEndGuard(std::bind(&BlockchainExplorer::poolUpdateEndHandler, this));
std::unique_lock<std::mutex> lock(mutex);
std::shared_ptr<std::vector<std::unique_ptr<ITransactionReader>>> rawNewTransactionsPtr = std::make_shared<std::vector<std::unique_ptr<ITransactionReader>>>();
std::shared_ptr<std::vector<Hash>> removedTransactionsPtr = std::make_shared<std::vector<Hash>>();
std::shared_ptr<bool> isBlockchainActualPtr = std::make_shared<bool>(false);
NodeRequest request(
[this, rawNewTransactionsPtr, removedTransactionsPtr, isBlockchainActualPtr](const INode::Callback& callback) {
std::vector<Hash> hashes;
for (const Hash& hash : knownPoolState) {
hashes.push_back(std::move(hash));
}
node.getPoolSymmetricDifference(
std::move(hashes),
reinterpret_cast<Hash&>(knownBlockchainTop.hash),
*isBlockchainActualPtr,
*rawNewTransactionsPtr,
*removedTransactionsPtr,
callback
);
}
);
request.performAsync(asyncContextCounter,
[this, rawNewTransactionsPtr, removedTransactionsPtr, isBlockchainActualPtr](std::error_code ec) {
ScopeExitHandler poolUpdateEndGuard(std::bind(&BlockchainExplorer::poolUpdateEndHandler, this));
if (ec) {
logger(ERROR) << "Can't send poolChanged notification because can't get pool symmetric difference: " << ec.message();
return;
}
std::unique_lock<std::mutex> lock(mutex);
std::shared_ptr<std::vector<Hash>> newTransactionsHashesPtr = std::make_shared<std::vector<Hash>>();
for (const auto& rawTransaction : *rawNewTransactionsPtr) {
auto hash = rawTransaction->getTransactionHash();
Hash transactionHash = reinterpret_cast<const Hash&>(hash);
bool inserted = knownPoolState.emplace(transactionHash).second;
if (inserted) {
newTransactionsHashesPtr->push_back(std::move(transactionHash));
}
}
std::shared_ptr<std::vector<std::pair<Hash, TransactionRemoveReason>>> removedTransactionsHashesPtr = std::make_shared<std::vector<std::pair<Hash, TransactionRemoveReason>>>();
for (const Hash hash : *removedTransactionsPtr) {
auto iter = knownPoolState.find(hash);
if (iter != knownPoolState.end()) {
removedTransactionsHashesPtr->push_back({
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(
static_cast<
void(INode::*)(
const std::vector<Hash>&,
std::vector<TransactionDetails>&,
const INode::Callback&
)
>(&INode::getTransactions),
std::ref(node),
std::cref(*newTransactionsHashesPtr),
std::ref(*newTransactionsPtr),
std::placeholders::_1
)
);
request.performAsync(asyncContextCounter,
[this, newTransactionsHashesPtr, newTransactionsPtr, removedTransactionsHashesPtr](std::error_code ec) {
ScopeExitHandler poolUpdateEndGuard(std::bind(&BlockchainExplorer::poolUpdateEndHandler, this));
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.";
}
}
);
poolUpdateEndGuard.reset();
}
);
poolUpdateEndGuard.reset();
}
void BlockchainExplorer::poolUpdateEndHandler() {
if (poolUpdateGuard.endUpdate()) {
poolChanged();
}
}
void BlockchainExplorer::blockchainSynchronized(uint32_t topHeight) {
logger(DEBUGGING) << "Got blockchainSynchronized notification.";
synchronized.store(true);
if (observersCounter.load() == 0) {
return;
}
std::shared_ptr<std::vector<uint32_t>> blockHeightsPtr = std::make_shared<std::vector<uint32_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<uint32_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(asyncContextCounter,
[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(uint32_t height) {
logger(DEBUGGING) << "Got localBlockchainUpdated notification.";
if (observersCounter.load() == 0) {
knownBlockchainTopHeight = height;
return;
}
std::unique_lock<std::mutex> lock(mutex);
assert(height >= knownBlockchainTopHeight);
std::shared_ptr<std::vector<uint32_t>> blockHeightsPtr = std::make_shared<std::vector<uint32_t>>();
std::shared_ptr<std::vector<std::vector<BlockDetails>>> blocksPtr = std::make_shared<std::vector<std::vector<BlockDetails>>>();
for (uint32_t i = knownBlockchainTopHeight; i <= height; ++i) {
blockHeightsPtr->push_back(i);
}
knownBlockchainTopHeight = height;
NodeRequest request(
std::bind(
static_cast<
void(INode::*)(
const std::vector<uint32_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(asyncContextCounter,
[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.";
}
);
}
}