2016-01-18 15:33:29 +00:00
|
|
|
// Copyright (c) 2011-2016 The Cryptonote developers
|
2015-04-23 16:07:22 +00:00
|
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
#include "BlockchainSynchronizer.h"
|
2015-07-30 15:22:07 +00:00
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
#include <functional>
|
2015-07-30 15:22:07 +00:00
|
|
|
#include <iostream>
|
2015-04-06 16:13:07 +00:00
|
|
|
#include <sstream>
|
2015-07-30 15:22:07 +00:00
|
|
|
#include <unordered_set>
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
#include "CryptoNoteCore/TransactionApi.h"
|
|
|
|
#include "CryptoNoteCore/CryptoNoteFormatUtils.h"
|
|
|
|
|
|
|
|
using namespace Crypto;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
inline std::vector<uint8_t> stringToVector(const std::string& s) {
|
|
|
|
std::vector<uint8_t> vec(
|
|
|
|
reinterpret_cast<const uint8_t*>(s.data()),
|
|
|
|
reinterpret_cast<const uint8_t*>(s.data()) + s.size());
|
|
|
|
return vec;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace CryptoNote {
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
BlockchainSynchronizer::BlockchainSynchronizer(INode& node, const Hash& genesisBlockHash) :
|
2015-12-09 13:19:03 +00:00
|
|
|
m_node(node),
|
|
|
|
m_genesisBlockHash(genesisBlockHash),
|
|
|
|
m_currentState(State::stopped),
|
|
|
|
m_futureState(State::stopped) {
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
BlockchainSynchronizer::~BlockchainSynchronizer() {
|
|
|
|
stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::addConsumer(IBlockchainConsumer* consumer) {
|
|
|
|
assert(consumer != nullptr);
|
|
|
|
assert(m_consumers.count(consumer) == 0);
|
|
|
|
|
|
|
|
if (!(checkIfStopped() && checkIfShouldStop())) {
|
|
|
|
throw std::runtime_error("Can't add consumer, because BlockchainSynchronizer isn't stopped");
|
|
|
|
}
|
2015-05-27 12:08:46 +00:00
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
m_consumers.insert(std::make_pair(consumer, std::make_shared<SynchronizationState>(m_genesisBlockHash)));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BlockchainSynchronizer::removeConsumer(IBlockchainConsumer* consumer) {
|
|
|
|
assert(consumer != nullptr);
|
|
|
|
|
|
|
|
if (!(checkIfStopped() && checkIfShouldStop())) {
|
|
|
|
throw std::runtime_error("Can't remove consumer, because BlockchainSynchronizer isn't stopped");
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_consumers.erase(consumer) > 0;
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
IStreamSerializable* BlockchainSynchronizer::getConsumerState(IBlockchainConsumer* consumer) const {
|
|
|
|
std::unique_lock<std::mutex> lk(m_consumersMutex);
|
|
|
|
return getConsumerSynchronizationState(consumer);
|
|
|
|
}
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
std::vector<Crypto::Hash> BlockchainSynchronizer::getConsumerKnownBlocks(IBlockchainConsumer& consumer) const {
|
|
|
|
std::unique_lock<std::mutex> lk(m_consumersMutex);
|
|
|
|
|
|
|
|
auto state = getConsumerSynchronizationState(&consumer);
|
|
|
|
if (state == nullptr) {
|
|
|
|
throw std::invalid_argument("Consumer not found");
|
|
|
|
}
|
|
|
|
|
|
|
|
return state->getKnownBlockHashes();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::future<std::error_code> BlockchainSynchronizer::addUnconfirmedTransaction(const ITransactionReader& transaction) {
|
|
|
|
std::unique_lock<std::mutex> lock(m_stateMutex);
|
|
|
|
|
|
|
|
if (m_currentState == State::stopped || m_futureState == State::stopped) {
|
|
|
|
throw std::runtime_error("Can't add transaction, because BlockchainSynchronizer is stopped");
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
std::promise<std::error_code> promise;
|
|
|
|
auto future = promise.get_future();
|
|
|
|
m_addTransactionTasks.emplace_back(&transaction, std::move(promise));
|
|
|
|
m_hasWork.notify_one();
|
|
|
|
|
|
|
|
return future;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::future<void> BlockchainSynchronizer::removeUnconfirmedTransaction(const Crypto::Hash& transactionHash) {
|
|
|
|
std::unique_lock<std::mutex> lock(m_stateMutex);
|
|
|
|
|
|
|
|
if (m_currentState == State::stopped || m_futureState == State::stopped) {
|
|
|
|
throw std::runtime_error("Can't remove transaction, because BlockchainSynchronizer is stopped");
|
|
|
|
}
|
|
|
|
|
|
|
|
std::promise<void> promise;
|
|
|
|
auto future = promise.get_future();
|
|
|
|
m_removeTransactionTasks.emplace_back(&transactionHash, std::move(promise));
|
|
|
|
m_hasWork.notify_one();
|
|
|
|
|
|
|
|
return future;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::error_code BlockchainSynchronizer::doAddUnconfirmedTransaction(const ITransactionReader& transaction) {
|
2015-04-06 16:13:07 +00:00
|
|
|
std::unique_lock<std::mutex> lk(m_consumersMutex);
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
std::error_code ec;
|
|
|
|
auto addIt = m_consumers.begin();
|
|
|
|
for (; addIt != m_consumers.end(); ++addIt) {
|
|
|
|
ec = addIt->first->addUnconfirmedTransaction(transaction);
|
|
|
|
if (ec) {
|
|
|
|
break;
|
|
|
|
}
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
if (ec) {
|
|
|
|
auto transactionHash = transaction.getTransactionHash();
|
|
|
|
for (auto rollbackIt = m_consumers.begin(); rollbackIt != addIt; ++rollbackIt) {
|
|
|
|
rollbackIt->first->removeUnconfirmedTransaction(transactionHash);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ec;
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
void BlockchainSynchronizer::doRemoveUnconfirmedTransaction(const Crypto::Hash& transactionHash) {
|
|
|
|
std::unique_lock<std::mutex> lk(m_consumersMutex);
|
|
|
|
|
|
|
|
for (auto& consumer : m_consumers) {
|
|
|
|
consumer.first->removeUnconfirmedTransaction(transactionHash);
|
|
|
|
}
|
|
|
|
}
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
void BlockchainSynchronizer::save(std::ostream& os) {
|
|
|
|
os.write(reinterpret_cast<const char*>(&m_genesisBlockHash), sizeof(m_genesisBlockHash));
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::load(std::istream& in) {
|
2015-07-30 15:22:07 +00:00
|
|
|
Hash genesisBlockHash;
|
2015-04-06 16:13:07 +00:00
|
|
|
in.read(reinterpret_cast<char*>(&genesisBlockHash), sizeof(genesisBlockHash));
|
|
|
|
if (genesisBlockHash != m_genesisBlockHash) {
|
|
|
|
throw std::runtime_error("Genesis block hash does not match stored state");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------- FSM ------------------------------------
|
|
|
|
|
|
|
|
bool BlockchainSynchronizer::setFutureState(State s) {
|
2015-12-09 13:19:03 +00:00
|
|
|
return setFutureStateIf(s, [this, s] { return s > m_futureState; });
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool BlockchainSynchronizer::setFutureStateIf(State s, std::function<bool(void)>&& pred) {
|
|
|
|
std::unique_lock<std::mutex> lk(m_stateMutex);
|
|
|
|
if (pred()) {
|
|
|
|
m_futureState = s;
|
2015-12-09 13:19:03 +00:00
|
|
|
m_hasWork.notify_one();
|
2015-04-06 16:13:07 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::actualizeFutureState() {
|
|
|
|
std::unique_lock<std::mutex> lk(m_stateMutex);
|
|
|
|
if (m_currentState == State::stopped && m_futureState == State::blockchainSync) { // start(), immideately attach observer
|
|
|
|
m_node.addObserver(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_futureState == State::stopped && m_currentState != State::stopped) { // stop(), immideately detach observer
|
|
|
|
m_node.removeObserver(this);
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
while (!m_removeTransactionTasks.empty()) {
|
|
|
|
auto& task = m_removeTransactionTasks.front();
|
|
|
|
const Crypto::Hash& transactionHash = *task.first;
|
|
|
|
auto detachedPromise = std::move(task.second);
|
|
|
|
m_removeTransactionTasks.pop_front();
|
|
|
|
|
|
|
|
try {
|
|
|
|
doRemoveUnconfirmedTransaction(transactionHash);
|
|
|
|
detachedPromise.set_value();
|
|
|
|
} catch (...) {
|
|
|
|
detachedPromise.set_exception(std::current_exception());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!m_addTransactionTasks.empty()) {
|
|
|
|
auto& task = m_addTransactionTasks.front();
|
|
|
|
const ITransactionReader& transaction = *task.first;
|
|
|
|
auto detachedPromise = std::move(task.second);
|
|
|
|
m_addTransactionTasks.pop_front();
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto ec = doAddUnconfirmedTransaction(transaction);
|
|
|
|
detachedPromise.set_value(ec);
|
|
|
|
} catch (...) {
|
|
|
|
detachedPromise.set_exception(std::current_exception());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
m_currentState = m_futureState;
|
|
|
|
switch (m_futureState) {
|
|
|
|
case State::stopped:
|
|
|
|
break;
|
|
|
|
case State::blockchainSync:
|
|
|
|
m_futureState = State::poolSync;
|
|
|
|
lk.unlock();
|
|
|
|
startBlockchainSync();
|
|
|
|
break;
|
|
|
|
case State::poolSync:
|
|
|
|
m_futureState = State::idle;
|
|
|
|
lk.unlock();
|
|
|
|
startPoolSync();
|
|
|
|
break;
|
|
|
|
case State::idle:
|
2015-12-09 13:19:03 +00:00
|
|
|
m_hasWork.wait(lk, [this] {
|
|
|
|
return m_futureState != State::idle || !m_removeTransactionTasks.empty() || !m_addTransactionTasks.empty();
|
|
|
|
});
|
2015-04-06 16:13:07 +00:00
|
|
|
lk.unlock();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
bool BlockchainSynchronizer::checkIfShouldStop() const {
|
2015-04-06 16:13:07 +00:00
|
|
|
std::unique_lock<std::mutex> lk(m_stateMutex);
|
|
|
|
return m_futureState == State::stopped;
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
bool BlockchainSynchronizer::checkIfStopped() const {
|
2015-04-06 16:13:07 +00:00
|
|
|
std::unique_lock<std::mutex> lk(m_stateMutex);
|
|
|
|
return m_currentState == State::stopped;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::workingProcedure() {
|
|
|
|
while (!checkIfShouldStop()) {
|
|
|
|
actualizeFutureState();
|
|
|
|
}
|
|
|
|
|
|
|
|
actualizeFutureState();
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::start() {
|
|
|
|
if (m_consumers.empty()) {
|
|
|
|
throw std::runtime_error("Can't start, because BlockchainSynchronizer has no consumers");
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
if (!setFutureStateIf(State::blockchainSync, [this] { return m_currentState == State::stopped && m_futureState == State::stopped; })) {
|
2015-04-06 16:13:07 +00:00
|
|
|
throw std::runtime_error("BlockchainSynchronizer already started");
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
workingThread.reset(new std::thread([this] { workingProcedure(); }));
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::stop() {
|
|
|
|
setFutureState(State::stopped);
|
2015-05-27 12:08:46 +00:00
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
// wait for previous processing to end
|
|
|
|
if (workingThread.get() != nullptr && workingThread->joinable()) {
|
|
|
|
workingThread->join();
|
|
|
|
}
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
workingThread.reset();
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
void BlockchainSynchronizer::localBlockchainUpdated(uint32_t /*height*/) {
|
|
|
|
setFutureState(State::blockchainSync);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::lastKnownBlockHeightUpdated(uint32_t /*height*/) {
|
2015-04-06 16:13:07 +00:00
|
|
|
setFutureState(State::blockchainSync);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::poolChanged() {
|
|
|
|
setFutureState(State::poolSync);
|
|
|
|
}
|
|
|
|
//--------------------------- FSM END ------------------------------------
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
void BlockchainSynchronizer::getPoolUnionAndIntersection(std::unordered_set<Crypto::Hash>& poolUnion, std::unordered_set<Crypto::Hash>& poolIntersection) const {
|
|
|
|
std::unique_lock<std::mutex> lk(m_consumersMutex);
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
auto itConsumers = m_consumers.begin();
|
|
|
|
poolUnion = itConsumers->first->getKnownPoolTxIds();
|
|
|
|
poolIntersection = itConsumers->first->getKnownPoolTxIds();
|
|
|
|
++itConsumers;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
for (; itConsumers != m_consumers.end(); ++itConsumers) {
|
|
|
|
const std::unordered_set<Crypto::Hash>& consumerKnownIds = itConsumers->first->getKnownPoolTxIds();
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
poolUnion.insert(consumerKnownIds.begin(), consumerKnownIds.end());
|
|
|
|
|
|
|
|
for (auto itIntersection = poolIntersection.begin(); itIntersection != poolIntersection.end();) {
|
|
|
|
if (consumerKnownIds.count(*itIntersection) == 0) {
|
|
|
|
itIntersection = poolIntersection.erase(itIntersection);
|
|
|
|
} else {
|
|
|
|
++itIntersection;
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BlockchainSynchronizer::GetBlocksRequest BlockchainSynchronizer::getCommonHistory() {
|
|
|
|
GetBlocksRequest request;
|
|
|
|
std::unique_lock<std::mutex> lk(m_consumersMutex);
|
|
|
|
if (m_consumers.empty()) {
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto shortest = m_consumers.begin();
|
|
|
|
auto syncStart = shortest->first->getSyncStart();
|
|
|
|
auto it = shortest;
|
|
|
|
++it;
|
|
|
|
for (; it != m_consumers.end(); ++it) {
|
|
|
|
if (it->second->getHeight() < shortest->second->getHeight()) {
|
|
|
|
shortest = it;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto consumerStart = it->first->getSyncStart();
|
|
|
|
syncStart.timestamp = std::min(syncStart.timestamp, consumerStart.timestamp);
|
|
|
|
syncStart.height = std::min(syncStart.height, consumerStart.height);
|
|
|
|
}
|
|
|
|
|
2015-05-27 12:08:46 +00:00
|
|
|
request.knownBlocks = shortest->second->getShortHistory(m_node.getLastLocalBlockHeight());
|
2015-04-06 16:13:07 +00:00
|
|
|
request.syncStart = syncStart;
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::startBlockchainSync() {
|
|
|
|
GetBlocksResponse response;
|
|
|
|
GetBlocksRequest req = getCommonHistory();
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (!req.knownBlocks.empty()) {
|
2015-12-09 13:19:03 +00:00
|
|
|
auto queryBlocksCompleted = std::promise<std::error_code>();
|
|
|
|
auto queryBlocksWaitFuture = queryBlocksCompleted.get_future();
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
m_node.queryBlocks(
|
|
|
|
std::move(req.knownBlocks),
|
|
|
|
req.syncStart.timestamp,
|
|
|
|
response.newBlocks,
|
|
|
|
response.startHeight,
|
|
|
|
[&queryBlocksCompleted](std::error_code ec) {
|
|
|
|
auto detachedPromise = std::move(queryBlocksCompleted);
|
|
|
|
detachedPromise.set_value(ec);
|
|
|
|
});
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
std::error_code ec = queryBlocksWaitFuture.get();
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
if (ec) {
|
2015-12-09 13:19:03 +00:00
|
|
|
setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; });
|
2015-07-30 15:22:07 +00:00
|
|
|
m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, ec);
|
2015-04-06 16:13:07 +00:00
|
|
|
} else {
|
|
|
|
processBlocks(response);
|
|
|
|
}
|
|
|
|
}
|
2015-12-09 13:19:03 +00:00
|
|
|
} catch (std::exception&) {
|
|
|
|
setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; });
|
|
|
|
m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, std::make_error_code(std::errc::invalid_argument));
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::processBlocks(GetBlocksResponse& response) {
|
|
|
|
BlockchainInterval interval;
|
|
|
|
interval.startHeight = response.startHeight;
|
|
|
|
std::vector<CompleteBlock> blocks;
|
|
|
|
|
2015-07-30 15:22:07 +00:00
|
|
|
for (auto& block : response.newBlocks) {
|
2015-04-06 16:13:07 +00:00
|
|
|
if (checkIfShouldStop()) {
|
|
|
|
break;
|
|
|
|
}
|
2015-12-09 13:19:03 +00:00
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
CompleteBlock completeBlock;
|
|
|
|
completeBlock.blockHash = block.blockHash;
|
|
|
|
interval.blocks.push_back(completeBlock.blockHash);
|
2015-07-30 15:22:07 +00:00
|
|
|
if (block.hasBlock) {
|
|
|
|
completeBlock.block = std::move(block.block);
|
|
|
|
completeBlock.transactions.push_back(createTransactionPrefix(completeBlock.block->baseTransaction));
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
try {
|
2015-07-30 15:22:07 +00:00
|
|
|
for (const auto& txShortInfo : block.txsShortInfo) {
|
2015-12-09 13:19:03 +00:00
|
|
|
completeBlock.transactions.push_back(createTransactionPrefix(txShortInfo.txPrefix, reinterpret_cast<const Hash&>(txShortInfo.txId)));
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
2015-07-30 15:22:07 +00:00
|
|
|
} catch (std::exception&) {
|
2015-12-09 13:19:03 +00:00
|
|
|
setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; });
|
|
|
|
m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, std::make_error_code(std::errc::invalid_argument));
|
2015-04-06 16:13:07 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
blocks.push_back(std::move(completeBlock));
|
|
|
|
}
|
|
|
|
|
2015-08-27 18:55:14 +00:00
|
|
|
uint32_t processedBlockCount = response.startHeight + static_cast<uint32_t>(response.newBlocks.size());
|
2015-04-06 16:13:07 +00:00
|
|
|
if (!checkIfShouldStop()) {
|
|
|
|
response.newBlocks.clear();
|
|
|
|
std::unique_lock<std::mutex> lk(m_consumersMutex);
|
|
|
|
auto result = updateConsumers(interval, blocks);
|
|
|
|
lk.unlock();
|
|
|
|
|
|
|
|
switch (result) {
|
2015-10-01 15:27:18 +00:00
|
|
|
case UpdateConsumersResult::errorOccurred:
|
2015-12-09 13:19:03 +00:00
|
|
|
if (setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; })) {
|
|
|
|
m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, std::make_error_code(std::errc::invalid_argument));
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
break;
|
2015-12-09 13:19:03 +00:00
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
case UpdateConsumersResult::nothingChanged:
|
2015-05-27 12:08:46 +00:00
|
|
|
if (m_node.getLastKnownBlockHeight() != m_node.getLastLocalBlockHeight()) {
|
2015-04-06 16:13:07 +00:00
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
2015-12-09 13:19:03 +00:00
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
case UpdateConsumersResult::addedNewBlocks:
|
2015-05-27 12:08:46 +00:00
|
|
|
setFutureState(State::blockchainSync);
|
2015-04-06 16:13:07 +00:00
|
|
|
m_observerManager.notify(
|
|
|
|
&IBlockchainSynchronizerObserver::synchronizationProgressUpdated,
|
2015-08-27 18:55:14 +00:00
|
|
|
processedBlockCount,
|
2015-05-27 12:08:46 +00:00
|
|
|
std::max(m_node.getKnownBlockCount(), m_node.getLocalBlockCount()));
|
2015-04-06 16:13:07 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!blocks.empty()) {
|
|
|
|
lastBlockId = blocks.back().blockHash;
|
|
|
|
}
|
2015-05-27 12:08:46 +00:00
|
|
|
}
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
if (checkIfShouldStop()) { //Sic!
|
2015-12-09 13:19:03 +00:00
|
|
|
m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, std::make_error_code(std::errc::interrupted));
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
/// \pre m_consumersMutex is locked
|
2015-04-06 16:13:07 +00:00
|
|
|
BlockchainSynchronizer::UpdateConsumersResult BlockchainSynchronizer::updateConsumers(const BlockchainInterval& interval, const std::vector<CompleteBlock>& blocks) {
|
|
|
|
bool smthChanged = false;
|
|
|
|
|
|
|
|
for (auto& kv : m_consumers) {
|
|
|
|
auto result = kv.second->checkInterval(interval);
|
|
|
|
|
|
|
|
if (result.detachRequired) {
|
|
|
|
kv.first->onBlockchainDetach(result.detachHeight);
|
|
|
|
kv.second->detach(result.detachHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result.hasNewBlocks) {
|
2015-07-30 15:22:07 +00:00
|
|
|
uint32_t startOffset = result.newBlockHeight - interval.startHeight;
|
2015-04-06 16:13:07 +00:00
|
|
|
// update consumer
|
2015-12-09 13:19:03 +00:00
|
|
|
if (kv.first->onNewBlocks(blocks.data() + startOffset, result.newBlockHeight, static_cast<uint32_t>(blocks.size()) - startOffset)) {
|
2015-04-06 16:13:07 +00:00
|
|
|
// update state if consumer succeeded
|
2015-12-09 13:19:03 +00:00
|
|
|
kv.second->addBlocks(interval.blocks.data() + startOffset, result.newBlockHeight, static_cast<uint32_t>(interval.blocks.size()) - startOffset);
|
2015-04-06 16:13:07 +00:00
|
|
|
smthChanged = true;
|
|
|
|
} else {
|
2015-10-01 15:27:18 +00:00
|
|
|
return UpdateConsumersResult::errorOccurred;
|
2015-05-27 12:08:46 +00:00
|
|
|
}
|
|
|
|
}
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return smthChanged ? UpdateConsumersResult::addedNewBlocks : UpdateConsumersResult::nothingChanged;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlockchainSynchronizer::startPoolSync() {
|
2015-12-09 13:19:03 +00:00
|
|
|
std::unordered_set<Crypto::Hash> unionPoolHistory;
|
|
|
|
std::unordered_set<Crypto::Hash> intersectedPoolHistory;
|
|
|
|
getPoolUnionAndIntersection(unionPoolHistory, intersectedPoolHistory);
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
GetPoolRequest unionRequest;
|
|
|
|
unionRequest.knownTxIds.assign(unionPoolHistory.begin(), unionPoolHistory.end());
|
|
|
|
unionRequest.lastKnownBlock = lastBlockId;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
GetPoolResponse unionResponse;
|
2015-04-06 16:13:07 +00:00
|
|
|
unionResponse.isLastKnownBlockActual = false;
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
std::error_code ec = getPoolSymmetricDifferenceSync(std::move(unionRequest), unionResponse);
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
if (ec) {
|
2015-12-09 13:19:03 +00:00
|
|
|
setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; });
|
|
|
|
m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, ec);
|
2015-04-06 16:13:07 +00:00
|
|
|
} else { //get union ok
|
|
|
|
if (!unionResponse.isLastKnownBlockActual) { //bc outdated
|
|
|
|
setFutureState(State::blockchainSync);
|
|
|
|
} else {
|
2015-12-09 13:19:03 +00:00
|
|
|
if (unionPoolHistory == intersectedPoolHistory) { //usual case, start pool processing
|
|
|
|
m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, processPoolTxs(unionResponse));
|
|
|
|
} else {
|
|
|
|
GetPoolRequest intersectionRequest;
|
|
|
|
intersectionRequest.knownTxIds.assign(intersectedPoolHistory.begin(), intersectedPoolHistory.end());
|
|
|
|
intersectionRequest.lastKnownBlock = lastBlockId;
|
2015-04-06 16:13:07 +00:00
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
GetPoolResponse intersectionResponse;
|
2015-04-06 16:13:07 +00:00
|
|
|
intersectionResponse.isLastKnownBlockActual = false;
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
std::error_code ec2 = getPoolSymmetricDifferenceSync(std::move(intersectionRequest), intersectionResponse);
|
2015-04-06 16:13:07 +00:00
|
|
|
|
|
|
|
if (ec2) {
|
2015-12-09 13:19:03 +00:00
|
|
|
setFutureStateIf(State::idle, [this] { return m_futureState != State::stopped; });
|
|
|
|
m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, ec2);
|
2015-04-06 16:13:07 +00:00
|
|
|
} else { //get intersection ok
|
|
|
|
if (!intersectionResponse.isLastKnownBlockActual) { //bc outdated
|
|
|
|
setFutureState(State::blockchainSync);
|
|
|
|
} else {
|
|
|
|
intersectionResponse.deletedTxIds.assign(unionResponse.deletedTxIds.begin(), unionResponse.deletedTxIds.end());
|
|
|
|
std::error_code ec3 = processPoolTxs(intersectionResponse);
|
2015-05-27 12:08:46 +00:00
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
//notify about error, or success
|
2015-12-09 13:19:03 +00:00
|
|
|
m_observerManager.notify(&IBlockchainSynchronizerObserver::synchronizationCompleted, ec3);
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
std::error_code BlockchainSynchronizer::getPoolSymmetricDifferenceSync(GetPoolRequest&& request, GetPoolResponse& response) {
|
|
|
|
auto promise = std::promise<std::error_code>();
|
|
|
|
auto future = promise.get_future();
|
|
|
|
|
|
|
|
m_node.getPoolSymmetricDifference(
|
|
|
|
std::move(request.knownTxIds),
|
|
|
|
std::move(request.lastKnownBlock),
|
|
|
|
response.isLastKnownBlockActual,
|
|
|
|
response.newTxs,
|
|
|
|
response.deletedTxIds,
|
|
|
|
[&promise](std::error_code ec) {
|
|
|
|
auto detachedPromise = std::move(promise);
|
|
|
|
detachedPromise.set_value(ec);
|
|
|
|
});
|
|
|
|
|
|
|
|
return future.get();
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::error_code BlockchainSynchronizer::processPoolTxs(GetPoolResponse& response) {
|
|
|
|
std::error_code error;
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lk(m_consumersMutex);
|
|
|
|
for (auto& consumer : m_consumers) {
|
|
|
|
if (checkIfShouldStop()) { //if stop, return immediately, without notification
|
|
|
|
return std::make_error_code(std::errc::interrupted);
|
|
|
|
}
|
|
|
|
|
|
|
|
error = consumer.first->onPoolUpdated(response.newTxs, response.deletedTxIds);
|
|
|
|
if (error) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:03 +00:00
|
|
|
///pre: m_consumersMutex is locked
|
|
|
|
SynchronizationState* BlockchainSynchronizer::getConsumerSynchronizationState(IBlockchainConsumer* consumer) const {
|
|
|
|
assert(consumer != nullptr);
|
|
|
|
|
|
|
|
if (!(checkIfStopped() && checkIfShouldStop())) {
|
|
|
|
throw std::runtime_error("Can't get consumer state, because BlockchainSynchronizer isn't stopped");
|
|
|
|
}
|
|
|
|
|
|
|
|
auto it = m_consumers.find(consumer);
|
|
|
|
if (it == m_consumers.end()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return it->second.get();
|
|
|
|
}
|
|
|
|
|
2015-04-06 16:13:07 +00:00
|
|
|
}
|