Fulcrum/Controller.h
Calin Culianu 9b351f29ae
More efficiencies added to mempool synch, plus ENABLED NOTIFICATIONS
Cleaned up the code a bit.  Still a WIP and needs performance tuning.
2019-12-31 13:14:27 +02:00

196 lines
9.1 KiB
C++

//
// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash
// Copyright (C) 2019-2020 Calin A. Culianu <calin.culianu@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program (see LICENSE.txt). If not, see
// <https://www.gnu.org/licenses/>.
//
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include "BitcoinD.h"
#include "BlockProc.h"
#include "Mixins.h"
#include "Options.h"
#include "Storage.h"
#include "SrvMgr.h"
#include "robin_hood/robin_hood.h"
#include <atomic>
#include <memory>
#include <tuple>
#include <shared_mutex>
#include <type_traits>
#include <vector>
class CtlTask;
class Controller : public Mgr, public ThreadObjectMixin, public TimersByNameMixin, public ProcessAgainMixin
{
Q_OBJECT
public:
explicit Controller(const std::shared_ptr<Options> & options);
~Controller() override;
void startup() override; ///< may throw
void cleanup() override;
const int polltimeMS; ///< the amount of time for polling bitcoind for new headers -- comes from options->pollTimeSecs
/// thread-safe -- call this from the slave task thread to submit a block
/// Note: we had to make this a public member but it's not really intended to be used from code outside this subsystem.
void putBlock(CtlTask *sender, PreProcessedBlockPtr);
inline bool isStopping() const { return stopFlag; }
/// Returns a positive nonzero value if the calling download task should throttle because the backlog is too large.
/// In that case the caller should try again in the returned value ms.
/// If the return value is 0, the caller may proceed immediately to continue downloading headers.
/// This function is not intended to be used by code outside this subsystem -- it is intended to be called by the
/// internal DownloadBlocksTask only.
unsigned downloadTaskRecommendedThrottleTimeMsec(unsigned forBlockHeight) const;
QVariantMap statsDebug(const QMap<QString, QString> & params) const;
/// Helper for log printing mempool status. Called this instance (from a timer), also called from the SynchMempoolTask
/// for debug printing when it receives new mempool tx's.
static void printMempoolStatusToLog(size_t newSize, size_t numAddresses, bool useDebugLogger, bool force = false);
signals:
/// Emitted whenever bitcoind is detected to be up-to-date, and everything is synched up.
/// note this is not emitted during regular polling, but only after `synchronizing` was emitted previously.
void upToDate();
/// Emitted whenever we begin synching to bitcoind. After this completes successfully, upToDate will be emitted
/// exactly once.
/// This signal may be emitted multiple times if there were errors and we are periodically retrying.
void synchronizing();
/// Emitted whenever we failed to synchronize to bitcoind.
void synchFailure();
/// Emitted wheneever upToDate() is emitted. This is identical except the header info is also sent. This is used by
/// blockchain.headers.subscribe system. See Servers.cpp.
void newHeader(unsigned height, const QByteArray & header);
protected:
Stats stats() const override; // from StatsMixin
Stats debug(const StatsParams &) const override; // from StatsMixin
protected slots:
void process(bool beSilentIfUpToDate); ///< generic callback to advance state
void process() override { process(false); } ///< from ProcessAgainMixin
private:
friend class CtlTask;
/// \brief newTask - Create a specific task using this template factory function. The task will be auto-started the
/// next time this thread enters the event loop, via a QTimer::singleShot(0,...).
///
/// \param connectErroredSignal If true, auto-connect signal CtlTask::errored() to this->genericTaskErrored()
/// \param args The rest of the args get passed to the c'tor of the concrete class specified (in the template arg).
/// \return Returns the newly constructed CtrlTask* subclass. Note the task will start as soon as control returns
/// to this thread's event loop, and the task is already emplaced into the `tasks` map when this function
/// returns.
template <typename CtlTaskT, typename ...Args,
typename = std::enable_if_t<std::is_base_of_v<CtlTask, CtlTaskT>> >
CtlTaskT *newTask(bool connectErroredSignal, Args && ...args);
/// remove and stop a task (called after task finished() signal fires)
void rmTask(CtlTask *);
/// returns true iff t is not in the tasks list
bool isTaskDeleted(CtlTask *t) const;
/// The default 'errored' handler used if a task was created with connectErroredSignal=true in newTask above.
void genericTaskErrored();
static constexpr auto pollTimerName = "pollForNewHeaders";
const std::shared_ptr<Options> options;
std::shared_ptr<Storage> storage; ///< shared with srvmgr, but we control its lifecycle
std::shared_ptr<BitcoinDMgr> bitcoindmgr; ///< shared with srvmgr, but we control its lifecycle
std::unique_ptr<SrvMgr> srvmgr; ///< NB: this may be nullptr if we haven't yet synched up and started listening. Additionally, this should be destructed before storage or bitcoindmgr.
struct StateMachine;
std::unique_ptr<StateMachine> sm;
mutable std::shared_mutex smLock;
robin_hood::unordered_flat_map<CtlTask *, std::unique_ptr<CtlTask>> tasks;
void add_DLHeaderTask(unsigned from, unsigned to, size_t nTasks);
void process_DownloadingBlocks();
bool process_VerifyAndAddBlock(PreProcessedBlockPtr); ///< helper called from within DownloadingBlocks state -- makes sure block is sane and adds it to db
void process_PrintProgress(unsigned height, size_t nTx, size_t nIO);
void process_DoUndoAndRetry(); ///< internal -- calls storage->undoLatestBlock() and schedules a task death and retry.
size_t nBlocksDownloadedSoFar() const; ///< not 100% accurate. call this only from this thread
std::tuple<size_t, size_t, size_t> nTxInOutSoFar() const; ///< not 100% accurate. call this only from this thread
volatile bool stopFlag = false;
bool lostConn = true;
/// Master subscription notification flag. Initially we don't do notifications. However, after we start the srvmgr,
/// this gets set to true permanently, and future blocks/undoes/mempool changes notify the app-wide SubsMgr, which
/// notifies subscribed clients (if any).
bool masterNotifySubsFlag = false;
/// takes locks, prints to Log() every 30 seconds if there were changes
void printMempoolStatusToLog() const;
};
/// Abstract base class for our private internal tasks. Concrete implementations are in Controller.cpp.
class CtlTask : public QObject, public ThreadObjectMixin, public ProcessAgainMixin
{
Q_OBJECT
public:
CtlTask(Controller *ctl, const QString &name = "UnnamedTask");
~CtlTask() override;
int errorCode = 0;
QString errorMessage = "";
std::atomic<double> lastProgress = 0.0;
const qint64 ts = Util::getTime(); ///< timestamp -- when the task was created
using ThreadObjectMixin::start;
using ThreadObjectMixin::stop;
signals:
void started();
// the below 4 signals are all exit points. After they are emitted the task will stop.
/// Emitted after one of: success(), errored() or retryRecommended() to indicate the task thread has stopped.
/// The task has or will soon remove itself after this has been emitted.
void finished();
/// Emitted if the task has completed successfully. finished() will be emitted afterwards.
void success();
/// Emitted if the task has encountered an error. finished() will be emitted afterwards.
void errored();
/// Only the SynchMempoolTask emits this when it thinks that the mempool looks funny and like a new block may have
/// arrived. After this is emitted the task will stop. The Controller listens for this and immediately retries
/// a full poll cycle of bitcoind. finished() will be emitted afterwards.
void retryRecommended();
void progress(double); ///< some tasks emit this to indicate progress. may be a number from 0->1.0 or anything else (task specific)
protected:
void on_started() override;
void on_finished() override;
void process() override = 0; ///< from ProcessAgainMixin -- here to illustrate it's still pure virtual
virtual void on_error(const RPC::Message &);
virtual void on_failure(const RPC::Message::Id &, const QString &msg);
quint64 submitRequest(const QString &method, const QVariantList &params, const BitcoinDMgr::ResultsF &resultsFunc);
Controller * const ctl; ///< initted in c'tor. Is always valid since all tasks' lifecycles are managed by the Controller.
};
#endif // CONTROLLER_H