2019-12-01 22:36:19 +02:00
# include "BlockProc.h"
2019-11-18 20:29:21 +02:00
# include "BTC.h"
2019-11-18 00:21:34 +02:00
# include "Controller.h"
2019-12-04 23:53:45 +02:00
# include "TXO.h"
2019-11-18 00:21:34 +02:00
2019-12-05 13:02:43 +02:00
# include "robin_hood/robin_hood.h"
2019-12-02 13:50:18 +02:00
# include <algorithm>
2019-11-25 22:34:11 +02:00
# include <cassert>
# include <iterator>
# include <list>
2019-11-27 02:38:39 +02:00
# include <map>
2019-11-25 22:34:11 +02:00
2019-11-18 08:29:33 +02:00
Controller : : Controller ( const std : : shared_ptr < Options > & o )
: Mgr ( nullptr ) , options ( o )
2019-11-18 00:21:34 +02:00
{
2019-11-18 08:29:33 +02:00
setObjectName ( " Controller " ) ;
_thread . setObjectName ( objectName ( ) ) ;
2019-11-18 00:21:34 +02:00
}
2019-11-25 22:34:11 +02:00
Controller : : ~ Controller ( ) { Debug ( " %s " , __FUNCTION__ ) ; cleanup ( ) ; }
2019-11-18 00:21:34 +02:00
void Controller : : startup ( )
{
2019-12-05 12:07:27 +02:00
stopFlag = false ;
2019-11-29 14:42:17 +02:00
storage = std : : make_unique < Storage > ( options ) ;
storage - > startup ( ) ; // may throw here
2019-11-18 00:21:34 +02:00
bitcoindmgr = std : : make_unique < BitcoinDMgr > ( options - > bitcoind . first , options - > bitcoind . second , options - > rpcuser , options - > rpcpassword ) ;
2019-11-18 08:29:33 +02:00
{
// some setup code that waits for bitcoind to be ready before kicking off our "process" method
2019-11-18 09:47:18 +02:00
auto waitForBitcoinD = [ this ] {
auto constexpr waitTimer = " wait4bitcoind " , callProcessTimer = " callProcess " ;
int constexpr msgPeriod = 10000 , // 10sec
smallDelay = 100 ;
2019-11-25 22:34:11 +02:00
stopTimer ( pollTimerName ) ;
2019-11-18 09:47:18 +02:00
stopTimer ( callProcessTimer ) ;
callOnTimerSoon ( msgPeriod , waitTimer , [ ] { Log ( " Waiting for bitcoind... " ) ; return true ; } , false , Qt : : TimerType : : VeryCoarseTimer ) ;
// connection to kick off our 'process' method once the first auth is received
auto connPtr = std : : make_shared < QMetaObject : : Connection > ( ) ;
* connPtr = connect ( bitcoindmgr . get ( ) , & BitcoinDMgr : : gotFirstGoodConnection , this , [ this , connPtr ] ( quint64 id ) mutable {
if ( connPtr ) {
stopTimer ( waitTimer ) ;
if ( ! disconnect ( * connPtr ) ) Fatal ( ) < < " Failed to disconnect 'authenticated' signal! FIXME! " ; // this should never happen but if it does, app quits.
connPtr . reset ( ) ; // clear connPtr right away to 1. delte it asap and 2. so we are guaranteed not to reenter this block for this connection should there be a spurious signal emitted.
Debug ( ) < < " Auth recvd from bicoind with id: " < < id < < " , proceeding with processing ... " ;
callOnTimerSoonNoRepeat ( smallDelay , callProcessTimer , [ this ] { process ( ) ; } , true ) ;
}
} ) ;
} ;
waitForBitcoinD ( ) ;
conns + = connect ( bitcoindmgr . get ( ) , & BitcoinDMgr : : allConnectionsLost , this , waitForBitcoinD ) ;
2019-11-24 18:23:34 +02:00
conns + = connect ( bitcoindmgr . get ( ) , & BitcoinDMgr : : inWarmUp , this , [ last = - 1.0 ] ( const QString & msg ) mutable {
// just print a message to the log as to why we keep dropping conn. -- if bitcoind is still warming up
auto now = Util : : getTimeSecs ( ) ;
if ( now - last > = 1.0 ) { // throttled to not spam log
last = now ;
Log ( ) < < " bitcoind is still warming up: " < < msg ;
}
} ) ;
2019-11-18 08:29:33 +02:00
}
2019-11-18 00:21:34 +02:00
bitcoindmgr - > startup ( ) ; // may throw
2019-11-27 14:07:09 +02:00
// We defer listening for connections until we hit the "upToDate" state at least once, to prevent problems
// for clients.
2019-11-27 14:46:17 +02:00
auto connPtr = std : : make_shared < QMetaObject : : Connection > ( ) ;
* connPtr = connect ( this , & Controller : : upToDate , this , [ this , connPtr ] {
if ( connPtr ) disconnect ( * connPtr ) ;
2019-11-27 14:07:09 +02:00
if ( ! srvmgr ) {
if ( ! origThread ) {
Fatal ( ) < < " INTERNAL ERROR: Controller's creation thread is null; cannot start SrvMgr, exiting! " ;
return ;
}
srvmgr = std : : make_unique < SrvMgr > ( options - > interfaces ) ;
// this object will live on our creation thread (normally the main thread)
srvmgr - > moveToThread ( origThread ) ;
// now, start it up on our creation thread (normally the main thread)
Util : : VoidFuncOnObjectNoThrow ( srvmgr . get ( ) , [ this ] {
// creation thread (normally the main thread)
try {
srvmgr - > startup ( ) ; // may throw Exception, waits for servers to bind
} catch ( const Exception & e ) {
// exit app on bind/listen failure.
Fatal ( ) < < e . what ( ) ;
}
} ) ; // wait for srvmgr's thread (usually the main thread)
}
} , Qt : : QueuedConnection ) ;
2019-11-18 08:29:33 +02:00
start ( ) ; // start our thread
2019-11-18 00:21:34 +02:00
}
void Controller : : cleanup ( )
{
2019-12-05 12:07:27 +02:00
stopFlag = true ;
2019-11-18 08:29:33 +02:00
stop ( ) ;
2019-12-01 00:43:03 +02:00
tasks . clear ( ) ; // deletes all tasks asap
2019-11-18 00:21:34 +02:00
if ( srvmgr ) { Log ( " Stopping SrvMgr ... " ) ; srvmgr - > cleanup ( ) ; srvmgr . reset ( ) ; }
if ( bitcoindmgr ) { Log ( " Stopping BitcoinDMgr ... " ) ; bitcoindmgr - > cleanup ( ) ; bitcoindmgr . reset ( ) ; }
2019-11-29 14:42:17 +02:00
if ( storage ) { Log ( " Closing storage ... " ) ; storage - > cleanup ( ) ; storage . reset ( ) ; }
2019-11-18 08:29:33 +02:00
sm . reset ( ) ;
}
2019-11-27 12:12:16 +02:00
/// Encapsulates basically the data returned from bitcoind by the getblockchaininfo RPC method.
/// This has been separated out into its own struct for future use to detect blockchain changes.
/// TODO: Refactor this out to storage, etc to detect when blockchain changed.
struct ChainInfo {
2019-11-27 12:03:12 +02:00
QString toString ( ) const ;
QString chain = " " ;
int blocks = 0 , headers = - 1 ;
QByteArray bestBlockhash ; ///< decoded bytes
double difficulty = 0.0 ;
int64_t mtp = 0 ;
double verificationProgress = 0.0 ;
bool initialBlockDownload = false ;
QByteArray chainWork ; ///< decoded bytes
size_t sizeOnDisk = 0 ;
bool pruned = false ;
QString warnings ;
2019-11-25 22:34:11 +02:00
} ;
2019-11-27 12:12:16 +02:00
struct GetChainInfoTask : public CtlTask
{
GetChainInfoTask ( Controller * ctl_ ) : CtlTask ( ctl_ , " Task.GetChainInfo " ) { }
2019-12-01 00:43:03 +02:00
~ GetChainInfoTask ( ) override { stop ( ) ; } // paranoia
2019-11-27 12:12:16 +02:00
void process ( ) override ;
ChainInfo info ;
} ;
2019-11-27 12:03:12 +02:00
void GetChainInfoTask : : process ( )
2019-11-25 22:34:11 +02:00
{
2019-11-27 12:03:12 +02:00
submitRequest ( " getblockchaininfo " , { } , [ this ] ( const RPC : : Message & resp ) {
const auto Err = [ this , id = resp . id . toInt ( ) ] ( const QString & thing ) {
const auto msg = QString ( " Failed to parse %1 " ) . arg ( thing ) ;
errorCode = id ;
errorMessage = msg ;
throw Exception ( msg ) ;
} ;
try {
bool ok = false ;
const auto map = resp . result ( ) . toMap ( ) ;
if ( map . isEmpty ( ) ) Err ( " response; expected map " ) ;
2019-11-27 12:12:16 +02:00
info . blocks = map . value ( " blocks " ) . toInt ( & ok ) ;
if ( ! ok | | info . blocks < 0 ) Err ( " blocks " ) ; // enforce positive blocks number
2019-11-27 12:03:12 +02:00
2019-11-27 12:12:16 +02:00
info . chain = map . value ( " chain " ) . toString ( ) ;
if ( info . chain . isEmpty ( ) ) Err ( " chain " ) ;
2019-11-27 12:03:12 +02:00
2019-11-27 12:12:16 +02:00
info . headers = map . value ( " headers " ) . toInt ( ) ; // error ignored here
2019-11-27 12:03:12 +02:00
2019-11-27 12:12:16 +02:00
info . bestBlockhash = Util : : ParseHexFast ( map . value ( " bestblockhash " ) . toByteArray ( ) ) ;
2019-12-07 15:04:45 +02:00
if ( info . bestBlockhash . size ( ) ! = HashLen ) Err ( " bestblockhash " ) ;
2019-11-27 12:03:12 +02:00
2019-11-27 12:12:16 +02:00
info . difficulty = map . value ( " difficulty " ) . toDouble ( ) ; // error ignored here
info . mtp = map . value ( " mediantime " ) . toLongLong ( ) ; // error ok
info . verificationProgress = map . value ( " verificationprogress " ) . toDouble ( ) ; // error ok
2019-11-27 12:03:12 +02:00
if ( auto v = map . value ( " initialblockdownload " ) ; v . canConvert < bool > ( ) )
2019-11-27 12:12:16 +02:00
info . initialBlockDownload = v . toBool ( ) ;
2019-11-27 12:03:12 +02:00
else
Err ( " initialblockdownload " ) ;
2019-11-27 12:12:16 +02:00
info . chainWork = Util : : ParseHexFast ( map . value ( " chainwork " ) . toByteArray ( ) ) ; // error ok
info . sizeOnDisk = map . value ( " size_on_disk " ) . toULongLong ( ) ; // error ok
info . pruned = map . value ( " pruned " ) . toBool ( ) ; // error ok
info . warnings = map . value ( " warnings " ) . toString ( ) ; // error ok
2019-11-27 12:03:12 +02:00
2019-11-27 12:12:16 +02:00
if ( Trace : : isEnabled ( ) ) Trace ( ) < < info . toString ( ) ;
2019-11-27 12:03:12 +02:00
2019-11-25 22:34:11 +02:00
emit success ( ) ;
2019-11-27 12:03:12 +02:00
} catch ( const Exception & e ) {
Error ( ) < < " INTERNAL ERROR: " < < e . what ( ) ;
emit errored ( ) ;
2019-11-25 22:34:11 +02:00
}
} ) ;
}
2019-11-27 12:12:16 +02:00
QString ChainInfo : : toString ( ) const
2019-11-27 12:03:12 +02:00
{
QString ret ;
{
QTextStream ts ( & ret , QIODevice : : WriteOnly | QIODevice : : Truncate ) ;
2019-11-27 12:12:16 +02:00
ts < < " (ChainInfo "
2019-11-27 12:03:12 +02:00
< < " chain: \" " < < chain < < " \" "
< < " blocks: " < < blocks
< < " headers: " < < headers
< < " bestBlockHash: " < < bestBlockhash . toHex ( )
< < " difficulty: " < < QString : : number ( difficulty , ' f ' , 9 )
< < " mtp: " < < mtp
< < " verificationProgress: " < < QString : : number ( verificationProgress , ' f ' , 6 )
< < " ibd: " < < initialBlockDownload
< < " chainWork: " < < chainWork . toHex ( )
< < " sizeOnDisk: " < < sizeOnDisk
< < " pruned: " < < pruned
< < " warnings: \" " < < warnings < < " \" "
< < " ) " ;
}
return ret ;
}
2019-11-30 22:07:35 +02:00
struct DownloadBlocksTask : public CtlTask
2019-11-25 22:34:11 +02:00
{
2019-11-30 22:07:35 +02:00
DownloadBlocksTask ( unsigned from , unsigned to , unsigned stride , Controller * ctl ) ;
2019-12-01 00:43:03 +02:00
~ DownloadBlocksTask ( ) override { stop ( ) ; } // paranoia
2019-11-25 22:34:11 +02:00
void process ( ) override ;
2019-11-26 00:18:28 +02:00
2019-11-30 22:07:35 +02:00
const unsigned from = 0 , to = 0 , stride = 1 , expectedCt = 1 ;
2019-11-25 22:34:11 +02:00
unsigned next = 0 ;
2019-11-30 22:07:35 +02:00
std : : atomic_uint goodCt = 0 ;
2019-11-26 00:18:28 +02:00
bool maybeDone = false ;
2019-11-25 22:34:11 +02:00
2019-11-26 00:18:28 +02:00
int q_ct = 0 ;
2019-11-30 22:07:35 +02:00
static constexpr int max_q = /*16;*/ BitcoinDMgr : : N_CLIENTS + 1 ; // todo: tune this
2019-11-25 22:34:11 +02:00
2019-11-26 00:18:28 +02:00
static const int HEADER_SIZE ;
2019-11-25 22:34:11 +02:00
2019-12-01 00:43:03 +02:00
std : : atomic < size_t > nTx = 0 , nIns = 0 , nOuts = 0 ;
2019-12-01 00:20:15 +02:00
2019-11-30 22:07:35 +02:00
void do_get ( unsigned height ) ;
2019-11-25 22:34:11 +02:00
2019-11-30 22:07:35 +02:00
// basically computes expectedCt. Use expectedCt member to get the actual expected ct. this is used only by c'tor as a utility function
static size_t nToDL ( unsigned from , unsigned to , unsigned stride ) { return size_t ( ( ( ( to - from ) + 1 ) + stride - 1 ) / qMax ( stride , 1U ) ) ; }
2019-11-25 22:34:11 +02:00
// thread safe, this is a rough estimate and not 100% accurate
2019-11-30 22:07:35 +02:00
size_t nSoFar ( double prog = - 1 ) const { if ( prog < 0. ) prog = lastProgress ; return size_t ( qRound ( expectedCt * prog ) ) ; }
// given a position in the headers array, return the height
size_t index2Height ( size_t index ) { return size_t ( from + ( index * stride ) ) ; }
// given a block height, return the index into our array
size_t height2Index ( size_t h ) { return size_t ( ( ( h - from ) + stride - 1 ) / stride ) ; }
2019-11-25 22:34:11 +02:00
} ;
2019-11-30 12:29:32 +02:00
2019-12-03 21:53:32 +02:00
/*static*/ const int DownloadBlocksTask : : HEADER_SIZE = BTC : : GetBlockHeaderSize ( ) ;
2019-11-25 22:34:11 +02:00
2019-11-30 22:07:35 +02:00
DownloadBlocksTask : : DownloadBlocksTask ( unsigned from , unsigned to , unsigned stride , Controller * ctl_ )
: CtlTask ( ctl_ , QString ( " Task.DL %1 -> %2 " ) . arg ( from ) . arg ( to ) ) , from ( from ) , to ( to ) , stride ( stride ) , expectedCt ( unsigned ( nToDL ( from , to , stride ) ) )
2019-11-25 22:34:11 +02:00
{
2019-11-30 22:07:35 +02:00
FatalAssert ( ( to > = from ) & & ( ctl_ ) & & ( stride > 0 ) ) < < " Invalid params to DonloadBlocksTask c'tor, FIXME! " ;
2019-11-25 22:34:11 +02:00
next = from ;
}
2019-11-30 22:07:35 +02:00
void DownloadBlocksTask : : process ( )
2019-11-25 22:34:11 +02:00
{
if ( next > to ) {
if ( maybeDone ) {
2019-12-02 13:50:18 +02:00
if ( goodCt > = expectedCt )
2019-11-25 22:34:11 +02:00
emit success ( ) ;
2019-12-02 13:50:18 +02:00
else {
errorCode = int ( expectedCt - goodCt ) ;
2019-12-02 16:27:42 +02:00
errorMessage = QString ( " missing %1 blocks " ) . arg ( errorCode ) ;
2019-11-25 22:34:11 +02:00
emit errored ( ) ;
}
}
return ;
}
2019-11-30 22:07:35 +02:00
do_get ( next ) ;
next + = stride ;
2019-11-25 22:34:11 +02:00
}
2019-11-30 12:29:32 +02:00
void DownloadBlocksTask : : do_get ( unsigned int bnum )
{
2019-12-05 12:07:27 +02:00
if ( ctl - > isStopping ( ) ) return ; // short-circuit early return if controller is stopping
2019-12-08 19:45:00 +02:00
if ( unsigned msec = ctl - > downloadTaskRecommendedThrottleTimeMsec ( bnum ) ; msec > 0 ) {
2019-12-08 19:54:26 +02:00
// Controller told us to back off because it is backlogged.
// Schedule ourselves to run again soon and return.
2019-12-08 19:14:05 +02:00
Util : : AsyncOnObject ( this , [ this , bnum ] {
do_get ( bnum ) ;
2019-12-08 19:45:00 +02:00
} , msec ) ;
2019-12-08 19:14:05 +02:00
return ;
}
2019-12-02 11:42:10 +02:00
submitRequest ( " getblockhash " , { bnum } , [ this , bnum ] ( const RPC : : Message & resp ) {
2019-11-30 12:29:32 +02:00
QVariant var = resp . result ( ) ;
const auto hash = Util : : ParseHexFast ( var . toByteArray ( ) ) ;
2019-12-07 15:04:45 +02:00
if ( hash . length ( ) = = HashLen ) {
2019-12-02 11:42:10 +02:00
submitRequest ( " getblock " , { var , false } , [ this , bnum , hash ] ( const RPC : : Message & resp ) {
2019-11-30 12:29:32 +02:00
QVariant var = resp . result ( ) ;
2019-12-01 00:20:15 +02:00
const auto rawblock = Util : : ParseHexFast ( var . toByteArray ( ) ) ;
const auto header = rawblock . left ( HEADER_SIZE ) ; // we need a deep copy of this anyway so might as well take it now.
2019-11-30 12:29:32 +02:00
QByteArray chkHash ;
if ( bool sizeOk = header . length ( ) = = HEADER_SIZE ; sizeOk & & ( chkHash = BTC : : HashRev ( header ) ) = = hash ) {
2019-12-02 13:50:18 +02:00
auto ppb = PreProcessedBlock : : makeShared ( bnum , size_t ( rawblock . size ( ) ) , BTC : : Deserialize < bitcoin : : CBlock > ( rawblock ) ) ; // this is here to test performance
2019-12-02 09:43:03 +02:00
// TESTING --
//if (bnum == 60000) Debug() << ppb->toDebugString();
if ( Trace : : isEnabled ( ) ) Trace ( ) < < " block " < < bnum < < " size: " < < rawblock . size ( ) < < " nTx: " < < ppb - > txInfos . size ( ) ;
2019-12-02 11:42:10 +02:00
// update some stats for /stats endpoint
2019-12-02 09:43:03 +02:00
nTx + = ppb - > txInfos . size ( ) ;
nOuts + = ppb - > outputs . size ( ) ;
nIns + = ppb - > inputs . size ( ) ;
2019-12-02 11:42:10 +02:00
2019-11-30 22:07:35 +02:00
const size_t index = height2Index ( bnum ) ;
2019-11-30 12:29:32 +02:00
+ + goodCt ;
q_ct = qMax ( q_ct - 1 , 0 ) ;
2019-11-30 22:07:35 +02:00
lastProgress = double ( index ) / double ( expectedCt ) ;
if ( ! ( bnum % 1000 ) & & bnum ) {
emit progress ( lastProgress ) ;
2019-11-30 12:29:32 +02:00
}
if ( Trace : : isEnabled ( ) ) Trace ( ) < < resp . method < < " : header for height: " < < bnum < < " len: " < < header . length ( ) ;
2019-12-02 11:42:10 +02:00
ctl - > putBlock ( this , ppb ) ; // send the block off to the Controller thread for further processing and for save to db
2019-11-30 12:29:32 +02:00
if ( goodCt > = expectedCt ) {
// flag state to maybeDone to do checks when process() called again
maybeDone = true ;
AGAIN ( ) ;
return ;
}
while ( goodCt + unsigned ( q_ct ) < expectedCt & & q_ct < max_q ) {
// queue multiple at once
AGAIN ( ) ;
+ + q_ct ;
}
} else if ( ! sizeOk ) {
Warning ( ) < < resp . method < < " : at height " < < bnum < < " header not valid (decoded size: " < < header . length ( ) < < " ) " ;
errorCode = int ( bnum ) ;
errorMessage = QString ( " bad size for height %1 " ) . arg ( bnum ) ;
emit errored ( ) ;
} else {
Warning ( ) < < resp . method < < " : at height " < < bnum < < " header not valid (expected hash: " < < hash . toHex ( ) < < " , got hash: " < < chkHash . toHex ( ) < < " ) " ;
errorCode = int ( bnum ) ;
errorMessage = QString ( " hash mismatch for height %1 " ) . arg ( bnum ) ;
emit errored ( ) ;
}
} ) ;
} else {
Warning ( ) < < resp . method < < " : at height " < < bnum < < " hash not valid (decoded size: " < < hash . length ( ) < < " ) " ;
errorCode = int ( bnum ) ;
errorMessage = QString ( " invalid hash for height %1 " ) . arg ( bnum ) ;
emit errored ( ) ;
}
} ) ;
}
2019-11-18 08:29:33 +02:00
struct Controller : : StateMachine
{
2019-11-18 20:29:21 +02:00
enum State {
2019-12-02 13:50:18 +02:00
Begin = 0 , GetBlocks , DownloadingBlocks , FinishedDL , End , Failure , IBD
2019-11-18 20:29:21 +02:00
} ;
State state = Begin ;
int ht = - 1 ;
2019-11-25 22:34:11 +02:00
2019-12-05 13:02:43 +02:00
robin_hood : : unordered_flat_map < unsigned , PreProcessedBlockPtr > ppBlocks ; // mapping of height -> PreProcessedBlock (we use an unordered_flat_map because it's faster for frequent updates)
2019-12-08 19:14:05 +02:00
unsigned startheight = 0 , ///< the height we started at
2019-12-02 16:21:22 +02:00
endHeight = 0 ; ///< the final (inclusive) block height we expect to receive to pronounce the synch done
2019-12-02 11:42:10 +02:00
2019-12-08 19:14:05 +02:00
std : : atomic < unsigned > ppBlkHtNext = 0 ; ///< the next unprocessed block height we need to process in series
2019-11-26 00:18:28 +02:00
// todo: tune this
2019-12-01 00:20:15 +02:00
const size_t DL_CONCURRENCY = qMax ( Util : : getNPhysicalProcessors ( ) - 1 , 1U ) ; //size_t(qMin(qMax(int(Util::getNPhysicalProcessors())-BitcoinDMgr::N_CLIENTS, BitcoinDMgr::N_CLIENTS), 32));
2019-11-26 00:18:28 +02:00
2019-12-01 00:43:03 +02:00
size_t nTx = 0 , nIns = 0 , nOuts = 0 ;
2019-12-01 00:20:15 +02:00
2019-12-02 16:21:22 +02:00
/// used to suppress progress log display if we get out-of-order heights
size_t lastProgHt = 0 ;
2019-11-25 22:34:11 +02:00
const char * stateStr ( ) const {
2019-12-02 13:50:18 +02:00
static constexpr const char * stateStrings [ ] = { " Begin " , " GetBlocks " , " DownloadingBlocks " , " FinishedDL " , " End " , " Failure " , " IBD " ,
2019-11-25 22:34:11 +02:00
" Unknown " /* this should always be last */ } ;
auto idx = qMin ( size_t ( state ) , std : : size ( stateStrings ) - 1 ) ;
return stateStrings [ idx ] ;
}
2019-11-18 08:29:33 +02:00
} ;
2019-12-08 19:45:00 +02:00
unsigned Controller : : downloadTaskRecommendedThrottleTimeMsec ( unsigned bnum ) const
2019-12-08 19:14:05 +02:00
{
2019-12-08 19:45:00 +02:00
std : : shared_lock g ( smProgressLock ) ;
2019-12-08 22:07:01 +02:00
if ( sm ) {
const unsigned maxBackLog = bnum < 100000 ? 1000 : 100 ; // <--- TODO: have this be a more dynamic value based on current average blocksize.
const int diff = int ( bnum ) - int ( sm - > ppBlkHtNext ) ;
if ( diff > 0 & & sm - > ppBlocks . size ( ) > maxBackLog ) {
// make the backoff time be 10ms minimum up to 100ms, depending on how far ahead the request is of where
// we are.
2019-12-08 22:48:48 +02:00
return 10 + ( diff > int ( maxBackLog ) ? 10 : 0 ) ; // TODO: also have this be tuneable.
2019-12-08 22:07:01 +02:00
}
2019-12-08 19:14:05 +02:00
}
return 0 ;
}
2019-11-25 22:34:11 +02:00
void Controller : : rmTask ( CtlTask * t )
2019-11-18 08:29:33 +02:00
{
2019-11-25 22:34:11 +02:00
if ( auto it = tasks . find ( t ) ; it ! = tasks . end ( ) ) {
tasks . erase ( it ) ; // will delete object immediately
return ;
}
Error ( ) < < __FUNCTION__ < < " : Task ' " < < t - > objectName ( ) < < " ' not found! FIXME! " ;
}
2019-11-18 08:29:33 +02:00
2019-11-25 22:34:11 +02:00
bool Controller : : isTaskDeleted ( CtlTask * t ) const { return tasks . count ( t ) = = 0 ; }
2019-11-24 22:28:26 +02:00
2019-11-25 22:34:11 +02:00
void Controller : : add_DLHeaderTask ( unsigned int from , unsigned int to , size_t nTasks )
{
2019-11-30 22:07:35 +02:00
DownloadBlocksTask * t = newTask < DownloadBlocksTask > ( false , unsigned ( from ) , unsigned ( to ) , unsigned ( nTasks ) , this ) ;
2019-12-02 13:50:18 +02:00
connect ( t , & CtlTask : : success , this , [ t , this ] {
2019-11-25 22:34:11 +02:00
if ( UNLIKELY ( ! sm | | isTaskDeleted ( t ) ) ) return ; // task was stopped from underneath us, this is stale.. abort.
2019-12-01 00:20:15 +02:00
sm - > nTx + = t - > nTx ;
2019-12-01 00:43:03 +02:00
sm - > nIns + = t - > nIns ;
sm - > nOuts + = t - > nOuts ;
2019-12-02 16:27:42 +02:00
Debug ( ) < < " Got all blocks from: " < < t - > objectName ( ) < < " blockCt: " < < t - > goodCt
2019-12-01 00:43:03 +02:00
< < " nTx,nInp,nOutp: " < < t - > nTx < < " , " < < t - > nIns < < " , " < < t - > nOuts < < " totals: "
< < sm - > nTx < < " , " < < sm - > nIns < < " , " < < sm - > nOuts ;
2019-11-25 22:34:11 +02:00
} ) ;
2019-12-02 11:46:34 +02:00
connect ( t , & CtlTask : : errored , this , [ t , this ] {
2019-11-25 22:34:11 +02:00
if ( UNLIKELY ( ! sm | | isTaskDeleted ( t ) ) ) return ; // task was stopped from underneath us, this is stale.. abort.
2019-11-27 12:03:12 +02:00
if ( sm - > state = = StateMachine : : State : : Failure ) return ; // silently ignore if we are already in failure
2019-12-02 11:46:34 +02:00
Error ( ) < < " Task errored: " < < t - > objectName ( ) < < " , error: " < < t - > errorMessage ;
genericTaskErrored ( ) ;
2019-11-25 22:34:11 +02:00
} ) ;
2019-12-03 06:38:19 +02:00
connect ( t , & CtlTask : : progress , this , [ t , this ] ( double prog ) { // this runs in "this" thread context
2019-11-25 22:34:11 +02:00
if ( UNLIKELY ( ! sm | | isTaskDeleted ( t ) ) ) return ; // task was stopped from underneath us, this is stale.. abort.
2019-12-02 14:10:44 +02:00
const size_t ht = t - > index2Height ( unsigned ( t - > expectedCt * prog ) ) ;
2019-12-02 16:21:22 +02:00
if ( ht < sm - > lastProgHt )
// suppress "backwards" progress displays. this can happen because we have multiple tasks sending us
// progress info and block heights arrive here out of order
return ;
sm - > lastProgHt = ht ;
2019-12-03 06:38:19 +02:00
QString extraInfo = " " ;
2019-12-03 06:52:38 +02:00
QString pctDisplay = QString : : number ( ( ht * 1e2 ) / qMax ( t - > to , 1U ) , ' f ' , 1 ) + " % " ;
if ( size_t backlog = 0 ; pctDisplay . startsWith ( " 100 " ) & & ( backlog = sm - > ppBlocks . size ( ) ) ) {
extraInfo = QString ( " . Waiting for %1 out-of-order blocks to arrive ... " ) . arg ( backlog ) ;
2019-12-03 06:38:19 +02:00
}
2019-12-03 06:52:38 +02:00
Log ( ) < < " Downloaded height: " < < ht < < " , " < < pctDisplay < < extraInfo ;
2019-11-30 22:07:35 +02:00
} ) ;
2019-11-25 22:34:11 +02:00
}
2019-11-27 12:03:12 +02:00
void Controller : : genericTaskErrored ( )
{
if ( sm & & sm - > state ! = StateMachine : : State : : Failure ) {
sm - > state = StateMachine : : State : : Failure ;
AGAIN ( ) ;
}
}
2019-11-27 20:32:50 +02:00
template < typename CtlTaskT , typename . . . Args , typename /* enable_if... */ >
CtlTaskT * Controller : : newTask ( bool connectErroredSignal , Args & & . . . args )
{
CtlTaskT * task = new CtlTaskT ( std : : forward < Args > ( args ) . . . ) ;
tasks . emplace ( task , task ) ;
if ( connectErroredSignal )
connect ( task , & CtlTask : : errored , this , & Controller : : genericTaskErrored ) ;
QTimer : : singleShot ( 0 , this , [ task , this ] {
if ( ! isTaskDeleted ( task ) )
task - > start ( ) ;
} ) ;
return task ;
}
2019-11-25 22:34:11 +02:00
void Controller : : process ( bool beSilentIfUpToDate )
{
2019-12-05 12:07:27 +02:00
if ( stopFlag ) return ;
2019-11-25 22:34:11 +02:00
bool enablePollTimer = false ;
2019-11-27 12:03:12 +02:00
auto polltimeout = polltime_ms ;
2019-11-25 22:34:11 +02:00
stopTimer ( pollTimerName ) ;
2019-12-02 13:50:18 +02:00
//Debug() << "Process called...";
2019-11-25 22:34:11 +02:00
if ( ! sm ) sm = std : : make_unique < StateMachine > ( ) ;
using State = StateMachine : : State ;
if ( sm - > state = = State : : Begin ) {
2019-11-27 20:32:50 +02:00
auto task = newTask < GetChainInfoTask > ( true , this ) ;
2019-11-25 22:34:11 +02:00
connect ( task , & CtlTask : : success , this , [ this , task , beSilentIfUpToDate ] {
if ( UNLIKELY ( ! sm | | isTaskDeleted ( task ) ) ) return ; // task was stopped from underneath us, this is stale.. abort.
2019-11-27 12:12:16 +02:00
if ( task - > info . initialBlockDownload ) {
2019-11-27 12:03:12 +02:00
sm - > state = State : : IBD ;
AGAIN ( ) ;
return ;
}
2019-11-30 01:33:27 +02:00
if ( const auto dbchain = storage - > getChain ( ) ; dbchain . isEmpty ( ) & & ! task - > info . chain . isEmpty ( ) ) {
storage - > setChain ( task - > info . chain ) ;
} else if ( dbchain ! = task - > info . chain ) {
Fatal ( ) < < " Bitcoind reports chain: \" " < < task - > info . chain < < " \" , which differs from our database: \" "
< < dbchain < < " \" . You may have connected to the wrong bitcoind. To fix this issue either "
< < " connect to a different bitcoind or delete this program's datadir to resynch. " ;
return ;
}
2019-11-27 12:03:12 +02:00
// TODO: detect reorgs here -- to be implemented later after we figure out data model more, etc.
2019-12-07 15:54:56 +02:00
const auto old = storage - > latestTip ( ) . first ;
2019-11-27 12:12:16 +02:00
sm - > ht = task - > info . blocks ;
2019-11-25 22:34:11 +02:00
if ( old = = sm - > ht ) {
2019-11-27 14:07:09 +02:00
if ( ! beSilentIfUpToDate ) {
Log ( ) < < " Block height " < < sm - > ht < < " , up-to-date " ;
emit upToDate ( ) ;
}
2019-11-25 22:34:11 +02:00
sm - > state = State : : End ;
2019-11-30 01:33:27 +02:00
} else if ( old > sm - > ht ) {
Fatal ( ) < < " We have height " < < old < < " , but bitcoind reports height " < < sm - > ht < < " . "
< < " Possible reasons: A massive reorg, your node is acting funny, you are on the wrong chain "
< < " (testnet vs mainnet), or there is a bug in this program. Cowardly giving up and exiting... " ;
return ;
2019-11-25 22:34:11 +02:00
} else {
2019-12-02 16:27:42 +02:00
Log ( ) < < " Block height " < < sm - > ht < < " , downloading new blocks ... " ;
2019-11-27 14:07:09 +02:00
emit synchronizing ( ) ;
2019-12-01 00:43:03 +02:00
sm - > state = State : : GetBlocks ;
2019-11-25 22:34:11 +02:00
}
AGAIN ( ) ;
} ) ;
2019-12-01 00:43:03 +02:00
} else if ( sm - > state = = State : : GetBlocks ) {
2019-12-02 13:50:18 +02:00
FatalAssert ( sm - > ht > = 0 ) < < " Inconsistent state -- sm->ht cannot be negative in State::GetBlocks! FIXME! " ; // paranoia
2019-12-07 15:54:56 +02:00
const size_t base = size_t ( storage - > latestTip ( ) . first + 1 ) ;
2019-11-25 22:34:11 +02:00
const size_t num = size_t ( sm - > ht + 1 ) - base ;
2019-12-02 13:50:18 +02:00
FatalAssert ( num > 0 ) < < " Cannot download 0 blocks! FIXME! " ; // more paranoia
2019-11-30 22:07:35 +02:00
const size_t nTasks = qMin ( num , sm - > DL_CONCURRENCY ) ;
2019-12-02 13:50:18 +02:00
sm - > ppBlkHtNext = sm - > startheight = unsigned ( base ) ;
2019-12-02 16:21:22 +02:00
sm - > endHeight = unsigned ( sm - > ht ) ;
2019-12-08 22:48:48 +02:00
storage - > setSaveInterval ( 0 ) ; // disable auto-save
2019-11-25 22:34:11 +02:00
for ( size_t i = 0 ; i < nTasks ; + + i ) {
2019-11-30 22:07:35 +02:00
add_DLHeaderTask ( unsigned ( base + i ) , unsigned ( sm - > ht ) , nTasks ) ;
2019-11-25 22:34:11 +02:00
}
2019-12-02 13:50:18 +02:00
sm - > state = State : : DownloadingBlocks ; // advance state now. we will be called back by download task in putBlock()
} else if ( sm - > state = = State : : DownloadingBlocks ) {
process_DownloadingBlocks ( ) ;
2019-11-25 22:34:11 +02:00
} else if ( sm - > state = = State : : FinishedDL ) {
2019-12-02 16:21:22 +02:00
size_t N = sm - > endHeight - sm - > startheight + 1 ;
2019-12-01 01:10:54 +02:00
Log ( ) < < " Processed " < < N < < " new " < < Util : : Pluralize ( " block " , N ) < < " with " < < sm - > nTx < < " " < < Util : : Pluralize ( " tx " , sm - > nTx )
< < " ( " < < sm - > nIns < < " " < < Util : : Pluralize ( " input " , sm - > nIns ) < < " & " < < sm - > nOuts < < " " < < Util : : Pluralize ( " output " , sm - > nOuts ) < < " ) "
2019-12-02 13:50:18 +02:00
< < " , verified ok. " ;
2019-12-08 19:14:05 +02:00
{
2019-12-08 19:45:00 +02:00
std : : lock_guard g ( smProgressLock ) ;
2019-12-08 19:14:05 +02:00
sm . reset ( ) ; // go back to "Begin" state to check if any new headers arrived in the meantime
}
2019-11-25 22:34:11 +02:00
AGAIN ( ) ;
2019-12-08 17:21:39 +02:00
storage - > save ( Storage : : SaveItem : : Blocks ) ; // enqueue a commit to db ...
2019-12-08 22:16:52 +02:00
Util : : AsyncOnObject ( storage . get ( ) , [ this ] { storage - > compactifyUtxoSet ( ) ; } , 10 ) ; // enqueue a compactification ...
2019-11-25 22:34:11 +02:00
} else if ( sm - > state = = State : : Failure ) {
2019-11-25 22:56:46 +02:00
// We will try again later via the pollTimer
2019-12-02 16:27:42 +02:00
Error ( ) < < " Failed to download blocks " ;
2019-12-08 19:14:05 +02:00
{
2019-12-08 19:45:00 +02:00
std : : lock_guard g ( smProgressLock ) ;
2019-12-08 19:14:05 +02:00
sm . reset ( ) ;
}
2019-11-25 22:34:11 +02:00
enablePollTimer = true ;
2019-11-27 14:07:09 +02:00
emit synchFailure ( ) ;
2019-11-25 22:34:11 +02:00
} else if ( sm - > state = = State : : End ) {
2019-12-08 19:14:05 +02:00
{
2019-12-08 19:45:00 +02:00
std : : lock_guard g ( smProgressLock ) ;
2019-12-08 19:14:05 +02:00
sm . reset ( ) ; // great success!
}
2019-11-25 22:34:11 +02:00
enablePollTimer = true ;
2019-12-08 22:07:01 +02:00
storage - > setSaveInterval ( 100 ) ;
2019-11-27 12:03:12 +02:00
} else if ( sm - > state = = State : : IBD ) {
2019-12-08 19:14:05 +02:00
{
2019-12-08 19:45:00 +02:00
std : : lock_guard g ( smProgressLock ) ;
2019-12-08 19:14:05 +02:00
sm . reset ( ) ; // great success!
}
2019-11-27 12:03:12 +02:00
enablePollTimer = true ;
Warning ( ) < < " bitcoind is in initial block download, will try again in 1 minute " ;
polltimeout = 60 * 1000 ; // try again every minute
2019-11-27 14:07:09 +02:00
emit synchFailure ( ) ;
2019-11-25 22:34:11 +02:00
}
if ( enablePollTimer )
2019-11-27 12:03:12 +02:00
callOnTimerSoonNoRepeat ( polltimeout , pollTimerName , [ this ] { if ( ! sm ) process ( true ) ; } ) ;
2019-11-25 22:34:11 +02:00
}
2019-12-02 11:42:10 +02:00
void Controller : : putBlock ( CtlTask * task , PreProcessedBlockPtr p )
{
2019-12-02 13:50:18 +02:00
// returns right away
Util : : AsyncOnObject ( this , [ this , task , p ] {
2019-12-05 12:07:27 +02:00
if ( ! sm | | isTaskDeleted ( task ) | | sm - > state = = StateMachine : : State : : Failure | | stopFlag ) {
2019-12-02 11:42:10 +02:00
Debug ( ) < < " Ignoring block " < < p - > height < < " for now-defunct task " ;
return ;
2019-12-02 13:50:18 +02:00
} else if ( sm - > state ! = StateMachine : : State : : DownloadingBlocks ) {
Warning ( ) < < " Ignoring putBlocks request for block " < < p - > height < < " -- state is not \" DownloadingBlocks \" but rather is: \" " < < sm - > stateStr ( ) < < " \" " ;
return ;
2019-12-02 11:42:10 +02:00
}
2019-12-08 19:14:05 +02:00
{
2019-12-08 19:45:00 +02:00
std : : lock_guard g ( smProgressLock ) ;
2019-12-08 19:14:05 +02:00
sm - > ppBlocks [ p - > height ] = p ;
}
2019-12-02 14:10:44 +02:00
//AGAIN(); // queue up, return right away -- turns out this spams events. better to call the process function directly here.
2019-12-02 16:21:22 +02:00
process_DownloadingBlocks ( ) ;
2019-12-02 11:42:10 +02:00
} ) ;
}
2019-11-25 22:34:11 +02:00
2019-12-02 13:50:18 +02:00
void Controller : : process_DownloadingBlocks ( )
{
unsigned ct = 0 ;
2019-12-08 19:14:05 +02:00
2019-12-05 12:07:27 +02:00
for ( auto it = sm - > ppBlocks . find ( sm - > ppBlkHtNext ) ; it ! = sm - > ppBlocks . end ( ) & & ! stopFlag ; it = sm - > ppBlocks . find ( sm - > ppBlkHtNext ) ) {
2019-12-02 13:50:18 +02:00
auto ppb = it - > second ;
2019-12-05 12:07:27 +02:00
assert ( ppb - > height = = sm - > ppBlkHtNext ) ; // paranoia -- should never happen
2019-12-02 13:50:18 +02:00
+ + ct ;
2019-12-08 19:45:00 +02:00
{
std : : lock_guard g ( smProgressLock ) ;
+ + sm - > ppBlkHtNext ;
sm - > ppBlocks . erase ( it ) ; // remove immediately from q
}
2019-12-02 13:50:18 +02:00
// process & add it if it's good
if ( ! process_VerifyAndAddBlock ( ppb ) )
// error encountered.. abort!
return ;
2019-12-02 16:21:22 +02:00
if ( sm - > ppBlkHtNext > sm - > endHeight ) {
sm - > state = StateMachine : : State : : FinishedDL ;
AGAIN ( ) ;
return ;
}
2019-12-02 13:50:18 +02:00
}
// testing debug
//if (auto backlog = sm->ppBlocks.size(); backlog < 100 || ct > 100) {
// Debug() << "ppblk - processed: " << ct << ", backlog: " << backlog;
//}
}
bool Controller : : process_VerifyAndAddBlock ( PreProcessedBlockPtr ppb )
{
assert ( sm ) ;
2019-12-07 20:34:19 +02:00
const auto nLeft = qMax ( sm - > endHeight - ( sm - > ppBlkHtNext - 1 ) , 0U ) ;
2019-12-05 12:07:27 +02:00
2019-12-07 20:34:19 +02:00
const QString err = storage - > addBlock ( ppb , nLeft ) ;
2019-12-05 12:07:27 +02:00
2019-12-07 20:34:19 +02:00
if ( ! err . isEmpty ( ) ) {
Fatal ( ) < < err ; // TODO: see about more graceful error and not a fatal exit. (although if we do get an error here it's pretty non-recoverable!)
sm - > state = StateMachine : : State : : Failure ;
AGAIN ( ) ; // schedule us again to do cleanup
return false ;
2019-12-04 23:53:45 +02:00
}
2019-12-02 13:50:18 +02:00
return true ;
}
2019-11-25 22:34:11 +02:00
// -- CtlTask
CtlTask : : CtlTask ( Controller * ctl , const QString & name )
: QObject ( nullptr ) , ctl ( ctl )
{
setObjectName ( name ) ;
_thread . setObjectName ( name ) ;
}
CtlTask : : ~ CtlTask ( ) {
Debug ( " %s (%s) " , __FUNCTION__ , objectName ( ) . isEmpty ( ) ? " " : objectName ( ) . toUtf8 ( ) . constData ( ) ) ;
stop ( ) ;
}
void CtlTask : : on_started ( )
{
ThreadObjectMixin : : on_started ( ) ;
conns + = connect ( this , & CtlTask : : success , ctl , [ this ] { stop ( ) ; } ) ;
conns + = connect ( this , & CtlTask : : errored , ctl , [ this ] { stop ( ) ; } ) ;
conns + = connect ( this , & CtlTask : : finished , ctl , [ this ] { ctl - > rmTask ( this ) ; } ) ;
process ( ) ;
emit started ( ) ;
}
void CtlTask : : on_finished ( )
{
ThreadObjectMixin : : on_finished ( ) ;
emit finished ( ) ;
}
void CtlTask : : on_error ( const RPC : : Message & resp )
{
Warning ( ) < < resp . method < < " : error response: " < < resp . toJsonString ( ) ;
errorCode = resp . errorCode ( ) ;
errorMessage = resp . errorMessage ( ) ;
emit errored ( ) ;
}
void CtlTask : : on_failure ( const RPC : : Message : : Id & id , const QString & msg )
{
Warning ( ) < < id . toString ( ) < < " : FAIL: " < < msg ;
errorCode = id . toInt ( ) ;
errorMessage = msg ;
emit errored ( ) ;
}
quint64 CtlTask : : submitRequest ( const QString & method , const QVariantList & params , const BitcoinDMgr : : ResultsF & resultsFunc )
{
quint64 id = IdMixin : : newId ( ) ;
ctl - > bitcoindmgr - > submitRequest ( this , id , method , params ,
resultsFunc ,
[ this ] ( const RPC : : Message & r ) { on_error ( r ) ; } ,
[ this ] ( const RPC : : Message : : Id & id , const QString & msg ) { on_failure ( id , msg ) ; } ) ;
return id ;
}
// --- Controller stats
auto Controller : : stats ( ) const - > Stats
{
// "Servers"
2019-11-27 14:07:09 +02:00
auto st = QVariantMap { { " Servers " , srvmgr ? srvmgr - > statsSafe ( ) : QVariant ( ) } } ;
2019-11-25 22:34:11 +02:00
// "BitcoinD's"
2019-11-27 14:07:09 +02:00
st [ " Bitcoin Daemon " ] = bitcoindmgr - > statsSafe ( ) ;
2019-11-25 22:34:11 +02:00
// "Controller" (self)
QVariantMap m ;
2019-12-07 15:54:56 +02:00
const auto tipInfo = storage - > latestTip ( ) ;
2019-12-07 20:34:19 +02:00
m [ " Header count " ] = tipInfo . first + 1 ;
m [ " Chain tip " ] = tipInfo . second . toHex ( ) ;
m [ " UTXO set size " ] = qlonglong ( storage - > utxoSet ( ) . first . size ( ) ) ;
m [ " TxNum " ] = qlonglong ( storage - > getTxNum ( ) ) ;
2019-11-25 22:34:11 +02:00
if ( sm ) {
QVariantMap m2 ;
m2 [ " State " ] = sm - > stateStr ( ) ;
m2 [ " Height " ] = sm - > ht ;
if ( const auto nDL = nHeadersDownloadedSoFar ( ) ; nDL > 0 )
2019-12-07 20:34:19 +02:00
m2 [ " Headers downloaded this run " ] = qlonglong ( nDL ) ;
2019-12-01 00:43:03 +02:00
if ( const auto [ ntx , nin , nout ] = nTxInOutSoFar ( ) ; ntx > 0 ) {
2019-12-07 20:34:19 +02:00
m2 [ " Txs seen this run " ] = QVariantMap ( {
2019-12-01 00:43:03 +02:00
{ " nTx " , qlonglong ( ntx ) } ,
{ " nIns " , qlonglong ( nin ) } ,
{ " nOut " , qlonglong ( nout ) }
} ) ;
}
2019-12-02 13:50:18 +02:00
const size_t backlogBlocks = sm - > ppBlocks . size ( ) ;
if ( backlogBlocks ) {
2019-12-07 20:34:19 +02:00
QVariantMap m3 ;
m3 [ " numBlocks " ] = qulonglong ( backlogBlocks ) ;
2019-12-02 13:50:18 +02:00
size_t backlogBytes = 0 , backlogTxs = 0 , backlogInMemoryBytes = 0 ;
for ( const auto & [ height , ppb ] : sm - > ppBlocks ) {
backlogBytes + = ppb - > sizeBytes ;
backlogTxs + = ppb - > txInfos . size ( ) ;
backlogInMemoryBytes + = ppb - > estimatedThisSizeBytes ;
}
2019-12-07 20:34:19 +02:00
m3 [ " in-memory (est.) " ] = QString ( " %1 MiB " ) . arg ( QString : : number ( double ( backlogInMemoryBytes ) / 1e6 , ' f ' , 3 ) ) ;
m3 [ " block bytes " ] = QString ( " %1 MiB " ) . arg ( QString : : number ( double ( backlogBytes ) / 1e6 , ' f ' , 3 ) ) ;
m3 [ " numTxs " ] = qulonglong ( backlogTxs ) ;
m2 [ " BackLog " ] = m3 ;
} else {
m2 [ " BackLog " ] = QVariant ( ) ; // null
2019-12-02 13:50:18 +02:00
}
2019-11-25 22:34:11 +02:00
m [ " StateMachine " ] = m2 ;
} else
m [ " StateMachine " ] = QVariant ( ) ; // null
QVariantMap timerMap ;
for ( const auto & timer : _timerMap ) {
timerMap . insert ( timer - > objectName ( ) , timer - > interval ( ) ) ;
}
m [ " activeTimers " ] = timerMap ;
QVariantList l ;
{ // task list
const auto now = Util : : getTime ( ) ;
2019-11-27 02:38:39 +02:00
for ( const auto & [ task , ign ] : tasks ) {
Q_UNUSED ( ign )
2019-11-27 14:07:09 +02:00
l . push_back ( QVariantMap { { task - > objectName ( ) , QVariantMap {
{ " age " , QString ( " %1 sec " ) . arg ( double ( ( now - task - > ts ) / 1e3 ) ) } ,
{ " progress " , QString ( " %1% " ) . arg ( QString : : number ( task - > lastProgress * 100.0 , ' f ' , 1 ) ) } }
} } ) ;
2019-11-25 22:34:11 +02:00
}
Util : : updateMap ( m , QVariantMap { { " tasks " , l } } ) ;
2019-11-18 20:29:21 +02:00
}
2019-11-25 22:34:11 +02:00
st [ " Controller " ] = m ;
return st ;
}
2019-11-18 20:29:21 +02:00
2019-11-25 22:34:11 +02:00
size_t Controller : : nHeadersDownloadedSoFar ( ) const
{
size_t ret = 0 ;
2019-11-27 02:38:39 +02:00
for ( const auto & [ task , ign ] : tasks ) {
Q_UNUSED ( ign )
2019-11-30 22:07:35 +02:00
auto t = dynamic_cast < DownloadBlocksTask * > ( task ) ;
2019-11-25 22:34:11 +02:00
if ( t )
ret + = t - > nSoFar ( ) ;
}
return ret ;
2019-11-18 00:21:34 +02:00
}
2019-12-01 00:20:15 +02:00
2019-12-01 00:43:03 +02:00
std : : tuple < size_t , size_t , size_t > Controller : : nTxInOutSoFar ( ) const
2019-12-01 00:20:15 +02:00
{
2019-12-01 00:43:03 +02:00
size_t nTx = 0 , nIn = 0 , nOut = 0 ;
2019-12-01 00:20:15 +02:00
for ( const auto & [ task , ign ] : tasks ) {
Q_UNUSED ( ign )
auto t = dynamic_cast < DownloadBlocksTask * > ( task ) ;
2019-12-01 00:43:03 +02:00
if ( t ) {
nTx + = t - > nTx ;
nIn + = t - > nIns ;
nOut + = t - > nOuts ;
}
2019-12-01 00:20:15 +02:00
}
2019-12-01 00:43:03 +02:00
return { nTx , nIn , nOut } ;
2019-12-01 00:20:15 +02:00
}