2019-12-28 23:46:05 +02:00
//
2019-12-28 23:48:24 +02:00
// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash
2019-12-28 23:46:05 +02:00
// 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/>.
//
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-28 15:10:07 +02:00
# include "Mempool.h"
2019-12-22 01:20:24 +02:00
# include "Merkle.h"
2019-12-30 14:58:16 +02:00
# include "SubsMgr.h"
2019-12-04 23:53:45 +02:00
# include "TXO.h"
2019-11-18 00:21:34 +02:00
2019-12-28 15:10:07 +02:00
# include "bitcoin/transaction.h"
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-12-31 13:14:27 +02:00
2019-11-18 08:29:33 +02:00
Controller : : Controller ( const std : : shared_ptr < Options > & o )
2019-12-28 23:26:16 +02:00
: Mgr ( nullptr ) , polltimeMS ( int ( o - > pollTimeSecs * 1000 ) ) , 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-12-18 11:58:25 +02:00
storage = std : : make_shared < Storage > ( options ) ;
2019-11-29 14:42:17 +02:00
storage - > startup ( ) ; // may throw here
2019-12-19 16:56:26 +02:00
bitcoindmgr = std : : make_shared < BitcoinDMgr > ( options - > bitcoind . first , options - > bitcoind . second , options - > rpcuser , options - > rpcpassword ) ;
2019-11-18 08:29:33 +02:00
{
2019-12-13 18:54:30 +02:00
auto constexpr waitTimer = " wait4bitcoind " , callProcessTimer = " callProcess " ;
int constexpr msgPeriod = 10000 , // 10sec
smallDelay = 100 ;
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 ] {
2019-12-13 18:54:30 +02:00
lostConn = true ;
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 ) ;
} ;
waitForBitcoinD ( ) ;
conns + = connect ( bitcoindmgr . get ( ) , & BitcoinDMgr : : allConnectionsLost , this , waitForBitcoinD ) ;
2019-12-13 18:54:30 +02:00
conns + = connect ( bitcoindmgr . get ( ) , & BitcoinDMgr : : gotFirstGoodConnection , this , [ this ] ( quint64 id ) {
// connection to kick off our 'process' method once the first auth is received
if ( lostConn ) {
lostConn = false ;
stopTimer ( waitTimer ) ;
Debug ( ) < < " Auth recvd from bicoind with id: " < < id < < " , proceeding with processing ... " ;
callOnTimerSoonNoRepeat ( smallDelay , callProcessTimer , [ this ] { process ( ) ; } , true ) ;
}
} ) ;
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 ] {
2019-12-29 09:47:46 +02:00
// the below code runs precisely once after the first upToDate signal
2019-11-27 14:46:17 +02:00
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 ;
}
2019-12-31 13:14:27 +02:00
masterNotifySubsFlag = true ; // permanently latch this to true. notifications enabled.
2019-12-30 10:33:35 +02:00
srvmgr = std : : make_unique < SrvMgr > ( options , storage , bitcoindmgr ) ;
2019-11-27 14:07:09 +02:00
// 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)
2019-12-18 17:53:16 +02:00
// connect the header subscribe signal
conns + = connect ( this , & Controller : : newHeader , srvmgr . get ( ) , & SrvMgr : : newHeader ) ;
2019-11-27 14:07:09 +02:00
}
} , Qt : : QueuedConnection ) ;
2019-11-18 08:29:33 +02:00
2019-12-29 09:47:46 +02:00
{
// logging/stats timers stuff
constexpr const char * mempoolLogTimer = " mempoolLogTimer " ;
constexpr int mempoolLogTimerTimeout = 10000 ; // 10 secs (the actual printing happens once every 30 seconds if changed)
// set up the mempool status log timer
conns + = connect ( this , & Controller : : upToDate , this , [ this ] {
callOnTimerSoon ( mempoolLogTimerTimeout , mempoolLogTimer , [ this ] {
printMempoolStatusToLog ( ) ;
return true ;
} , Qt : : TimerType : : VeryCoarseTimer ) ;
} ) ;
conns + = connect ( this , & Controller : : synchronizing , this , [ this ] { stopTimer ( mempoolLogTimer ) ; } ) ;
conns + = connect ( bitcoindmgr . get ( ) , & BitcoinDMgr : : allConnectionsLost , this , [ this ] { stopTimer ( mempoolLogTimer ) ; } ) ;
}
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-12-29 11:38:24 +02:00
const bool TRACE = Trace : : isEnabled ( ) ;
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-11 00:57:52 +02:00
} , msec , Qt : : TimerType : : PreciseTimer ) ;
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
2019-12-29 11:38:24 +02:00
if ( TRACE ) 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
}
2019-12-29 11:38:24 +02:00
if ( TRACE ) 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-12-28 15:10:07 +02:00
struct SynchMempoolTask : public CtlTask
{
SynchMempoolTask ( Controller * ctl_ , std : : shared_ptr < Storage > storage )
2019-12-31 13:14:27 +02:00
: CtlTask ( ctl_ , " SynchMempool " ) , storage ( storage )
{ scriptHashesAffected . reserve ( SubsMgr : : kRecommendedPendingNotificationsReserveSize ) ; }
2019-12-28 15:10:07 +02:00
~ SynchMempoolTask ( ) override ;
void process ( ) override ;
std : : shared_ptr < Storage > storage ;
bool isdlingtxs = false ;
Mempool : : TxMap txsNeedingDownload , txsWaitingForResponse ;
using DldTxsMap = robin_hood : : unordered_flat_map < TxHash , std : : pair < Mempool : : TxRef , bitcoin : : CTransactionRef > , HashHasher > ;
DldTxsMap txsDownloaded ;
unsigned expectedNumTxsDownloaded = 0 ;
2019-12-29 11:38:24 +02:00
const bool TRACE = Trace : : isEnabled ( ) ; // set this to true to print more debug
2019-12-28 15:10:07 +02:00
2019-12-31 13:14:27 +02:00
/// The scriptHashes that were affected by this refresh/synch cycle. Used for notifications.
std : : unordered_set < HashX , HashHasher > scriptHashesAffected ;
2019-12-28 15:10:07 +02:00
void clear ( ) {
isdlingtxs = false ;
txsNeedingDownload . clear ( ) ; txsWaitingForResponse . clear ( ) ;
txsDownloaded . clear ( ) ;
expectedNumTxsDownloaded = 0 ;
}
void doGetRawMempool ( ) ;
void doDLNextTx ( ) ;
void processResults ( ) ;
} ;
SynchMempoolTask : : ~ SynchMempoolTask ( ) { stop ( ) ; } // paranoia
void SynchMempoolTask : : process ( )
{
if ( ctl - > isStopping ( ) )
return ; // short-circuit early return if controller is stopping
if ( ! isdlingtxs )
doGetRawMempool ( ) ;
else if ( ! txsNeedingDownload . empty ( ) ) {
doDLNextTx ( ) ;
} else if ( txsWaitingForResponse . empty ( ) ) {
try {
processResults ( ) ;
} catch ( const std : : exception & e ) {
Error ( ) < < " Caught exception when processing mempool tx's: " < < e . what ( ) ;
emit errored ( ) ;
return ;
}
} else {
Error ( ) < < " Unexpected state in " < < __PRETTY_FUNCTION__ < < " . FIXME! " ;
emit errored ( ) ;
return ;
}
}
2019-12-29 09:47:46 +02:00
/// takes locks, prints to Log() every 30 seconds if there were changes
void Controller : : printMempoolStatusToLog ( ) const
{
if ( storage ) {
size_t newSize , numAddresses ;
{
auto [ mempool , lock ] = storage - > mempool ( ) ;
newSize = mempool . txs . size ( ) ;
numAddresses = mempool . hashXTxs . size ( ) ;
} // release mempool lock
printMempoolStatusToLog ( newSize , numAddresses , false ) ;
}
}
// static
void Controller : : printMempoolStatusToLog ( size_t newSize , size_t numAddresses , bool isDebug , bool force )
{
2019-12-31 00:50:51 +02:00
static std : : atomic_size_t oldSize = 0 , oldNumAddresses = 0 ;
static std : : atomic < double > lastTS = 0. ;
2019-12-29 09:47:46 +02:00
static std : : mutex mut ;
constexpr double interval = 30. ;
double now = Util : : getTimeSecs ( ) ;
std : : lock_guard g ( mut ) ;
2019-12-31 00:50:51 +02:00
if ( force | | ( newSize > 0 & & ( oldSize ! = newSize | | oldNumAddresses ! = numAddresses ) & & now - lastTS > = interval ) ) {
2019-12-29 09:47:46 +02:00
std : : unique_ptr < Log > logger ( isDebug ? new Debug : new Log ) ;
Log & log ( * logger ) ;
log < < newSize < < Util : : Pluralize ( " mempool tx " , newSize ) < < " involving " < < numAddresses
< < Util : : Pluralize ( " address " , numAddresses ) ;
if ( ! force ) {
oldSize = newSize ;
2019-12-31 00:50:51 +02:00
oldNumAddresses = numAddresses ;
2019-12-29 09:47:46 +02:00
lastTS = now ;
}
}
}
2019-12-28 15:10:07 +02:00
void SynchMempoolTask : : processResults ( )
{
if ( txsDownloaded . size ( ) ! = expectedNumTxsDownloaded ) {
Error ( ) < < __PRETTY_FUNCTION__ < < " : Expected to downlaod " < < expectedNumTxsDownloaded < < " , instead got " < < txsDownloaded . size ( ) < < " . FIXME! " ;
emit errored ( ) ;
return ;
}
2019-12-31 13:14:27 +02:00
size_t oldSize = 0 , newSize = 0 , oldNumAddresses = 0 , newNumAddresses = 0 ;
2019-12-31 04:17:06 +02:00
{
auto [ mempool , lock ] = storage - > mutableMempool ( ) ; // grab mempool struct exclusively
2019-12-31 13:14:27 +02:00
oldSize = mempool . txs . size ( ) ;
oldNumAddresses = mempool . hashXTxs . size ( ) ;
2019-12-31 04:17:06 +02:00
// first, do new outputs for all tx's, and put the new tx's in the mempool struct
for ( auto & [ hash , pair ] : txsDownloaded ) {
auto & [ tx , ctx ] = pair ;
assert ( hash = = tx - > hash ) ;
mempool . txs [ tx - > hash ] = tx ; // save tx right away
IONum n = 0 ;
2019-12-31 16:22:52 +02:00
const auto numTxo = ctx - > vout . size ( ) ;
if ( LIKELY ( tx - > txos . size ( ) ! = numTxo ) ) {
// we do it this way (reserve then resize) to avoid the automatic 2^N prealloc of normal vector .resize()
tx - > txos . reserve ( numTxo ) ;
tx - > txos . resize ( numTxo ) ;
}
2019-12-31 04:17:06 +02:00
for ( const auto & out : ctx - > vout ) {
const auto & script = out . scriptPubKey ;
if ( ! BTC : : IsOpReturn ( script ) ) {
// UTXO only if it's not OP_RETURN -- can't do 'continue' here as that would throw off the 'n' counter
HashX sh = BTC : : HashXFromCScript ( out . scriptPubKey ) ;
2019-12-31 16:22:52 +02:00
TXOInfo & txoInfo = tx - > txos [ n ] ;
txoInfo = TXOInfo { out . nValue , sh , { } , { } } ;
2019-12-31 04:17:06 +02:00
tx - > hashXs [ sh ] . utxo . insert ( n ) ;
mempool . hashXTxs [ sh ] . push_back ( tx ) ; // save tx to hashx -> tx vector (amortized constant time insert at end -- we will sort and uniqueify this at end of this function)
2019-12-31 13:14:27 +02:00
scriptHashesAffected . insert ( sh ) ;
2019-12-31 16:22:52 +02:00
assert ( txoInfo . isValid ( ) ) ;
2019-12-31 04:17:06 +02:00
}
+ + n ;
2019-12-29 10:33:22 +02:00
}
2019-12-31 16:22:52 +02:00
assert ( n = = numTxo ) ;
// . <-- at this point the .txos vec is built, with everything isValid() except for the OP_RETURN outs, which are all !isValid()
2019-12-28 15:10:07 +02:00
}
2019-12-31 04:17:06 +02:00
// next, do new inputs for all tx's, debiting/crediting either a mempool tx or querying db for the relevant utxo
for ( auto & [ hash , pair ] : txsDownloaded ) {
auto & [ tx , ctx ] = pair ;
assert ( hash = = tx - > hash ) ;
IONum inNum = 0 ;
for ( const auto & in : ctx - > vin ) {
const IONum prevN = IONum ( in . prevout . GetN ( ) ) ;
const TxHash prevTxId = BTC : : Hash2ByteArrayRev ( in . prevout . GetTxId ( ) ) ;
const TXO prevTXO { prevTxId , prevN } ;
TXOInfo prevInfo ;
QByteArray sh ; // shallow copy of prevInfo.hashX
if ( tx - > depends . count ( prevTxId ) ) {
// prev is a mempool tx
auto it = mempool . txs . find ( prevTxId ) ;
if ( it = = mempool . txs . end ( ) )
2019-12-31 16:22:52 +02:00
throw InternalError ( QString ( " FAILED TO FIND PREVIOUS TX IN MEMPOOL! Fixme! TxHash: %1 " )
. arg ( QString ( prevTxId . toHex ( ) ) ) ) ;
2019-12-31 04:17:06 +02:00
auto prevTxRef = it - > second ;
assert ( bool ( prevTxRef ) ) ;
2019-12-31 16:22:52 +02:00
if ( prevN > = prevTxRef - > txos . size ( )
| | ! ( prevInfo = prevTxRef - > txos [ prevN ] ) . isValid ( ) )
// defensive programming paranoia
throw InternalError ( QString ( " FAILED TO FIND A VALID PREVIOUS TXOUTN %1 IN MEMPOOL for TxHash: %2 " )
. arg ( prevN ) . arg ( QString ( prevTxId . toHex ( ) ) ) ) ;
2019-12-31 04:17:06 +02:00
sh = prevInfo . hashX ;
tx - > hashXs [ sh ] . unconfirmedSpends [ prevTXO ] = prevInfo ;
prevTxRef - > hashXs [ sh ] . utxo . erase ( prevN ) ; // remove this spend from utxo set for prevTx in mempool
if ( TRACE ) Debug ( ) < < hash . toHex ( ) < < " unconfirmed spend: " < < prevTXO . toString ( ) < < " " < < prevInfo . amount . ToString ( ) . c_str ( ) ;
} else {
// prev is a confirmed tx
prevInfo = storage - > utxoGetFromDB ( prevTXO , true ) . value ( ) ; // will throw if missing
sh = prevInfo . hashX ;
tx - > hashXs [ sh ] . confirmedSpends [ prevTXO ] = prevInfo ;
if ( TRACE ) Debug ( ) < < hash . toHex ( ) < < " confirmed spend: " < < prevTXO . toString ( ) < < " " < < prevInfo . amount . ToString ( ) . c_str ( ) ;
}
assert ( sh = = prevInfo . hashX ) ;
2019-12-31 13:14:27 +02:00
mempool . hashXTxs [ sh ] . push_back ( tx ) ; // mark this hashX as having been "touched" because of this input (note we push dupes here out of order but sort and uniqueify at the end)
scriptHashesAffected . insert ( sh ) ;
2019-12-31 04:17:06 +02:00
+ + inNum ;
2019-12-28 15:10:07 +02:00
}
}
2019-12-31 04:17:06 +02:00
2019-12-31 13:14:27 +02:00
// now, sort and uniqueify data structures made temporarily inconsistent above (have dupes, are out-of-order)
2019-12-31 04:17:06 +02:00
for ( const auto & sh : scriptHashesAffected ) {
if ( auto it = mempool . hashXTxs . find ( sh ) ; LIKELY ( it ! = mempool . hashXTxs . end ( ) ) )
Util : : sortAndUniqueify < Mempool : : TxRefOrdering > ( it - > second ) ;
else
throw InternalError ( QString ( " Unable to find sh %1 in hashXTXs map! FIXME! " ) . arg ( QString ( sh . toHex ( ) ) ) ) ;
}
2019-12-31 13:14:27 +02:00
newSize = mempool . txs . size ( ) ;
newNumAddresses = mempool . hashXTxs . size ( ) ;
2019-12-31 04:17:06 +02:00
} // release mempool lock
2019-12-31 13:14:27 +02:00
if ( oldSize ! = newSize & & Debug : : isEnabled ( ) ) {
Controller : : printMempoolStatusToLog ( newSize , newNumAddresses , true , true ) ;
}
2019-12-28 15:10:07 +02:00
emit success ( ) ;
}
void SynchMempoolTask : : doDLNextTx ( )
{
Mempool : : TxRef tx ;
if ( auto it = txsNeedingDownload . begin ( ) ; it = = txsNeedingDownload . end ( ) ) {
2019-12-28 16:34:50 +02:00
Error ( ) < < " FIXME -- txsNeedingDownload is empty in " < < __FUNCTION__ ;
2019-12-28 15:10:07 +02:00
emit errored ( ) ;
return ;
} else {
tx = it - > second ;
2019-12-31 16:22:52 +02:00
it = txsNeedingDownload . erase ( it ) ; // pop it off the front
2019-12-28 15:10:07 +02:00
}
assert ( bool ( tx ) ) ;
const auto hashHex = Util : : ToHexFast ( tx - > hash ) ;
txsWaitingForResponse [ tx - > hash ] = tx ;
submitRequest ( " getrawtransaction " , { hashHex , false } , [ this , hashHex , tx ] ( const RPC : : Message & resp ) {
QByteArray txdata = resp . result ( ) . toString ( ) . toUtf8 ( ) ;
const int expectedLen = txdata . length ( ) / 2 ;
txdata = Util : : ParseHexFast ( txdata ) ;
if ( txdata . length ( ) ! = expectedLen ) {
2019-12-28 16:34:50 +02:00
Error ( ) < < " Received tx data is of the wrong length -- bad hex? FIXME " ;
2019-12-28 15:10:07 +02:00
emit errored ( ) ;
return ;
} else if ( BTC : : HashRev ( txdata ) ! = tx - > hash ) {
2019-12-28 16:34:50 +02:00
Error ( ) < < " Received tx data appears to not match requested tx! FIXME!! " ;
2019-12-28 15:10:07 +02:00
emit errored ( ) ;
return ;
}
2019-12-29 11:38:24 +02:00
if ( TRACE )
Debug ( ) < < " got reply for tx: " < < hashHex < < " " < < txdata . length ( ) < < " bytes " ;
2019-12-29 10:21:19 +02:00
{
// tmp mutable object will be moved into CTransactionRef below via a move constructor
bitcoin : : CMutableTransaction ctx = BTC : : Deserialize < bitcoin : : CMutableTransaction > ( txdata ) ;
txsDownloaded [ tx - > hash ] = { tx , bitcoin : : MakeTransactionRef ( std : : move ( ctx ) ) } ;
}
2019-12-28 15:10:07 +02:00
txsWaitingForResponse . erase ( tx - > hash ) ;
AGAIN ( ) ;
} ) ;
}
void SynchMempoolTask : : doGetRawMempool ( )
{
submitRequest ( " getrawmempool " , { true } , [ this ] ( const RPC : : Message & resp ) {
const int tipHeight = storage - > latestTip ( ) . first ;
int newCt = 0 ;
const QVariantMap vm = resp . result ( ) . toMap ( ) ;
auto [ mempool , lock ] = storage - > mutableMempool ( ) ; // grab the mempool data struct and lock it exclusively
2019-12-28 23:26:16 +02:00
const auto oldCt = mempool . txs . size ( ) ;
2019-12-28 15:10:07 +02:00
auto droppedTxs = Util : : keySet < std : : unordered_set < TxHash , HashHasher > > ( mempool . txs ) ;
for ( auto it = vm . begin ( ) ; it ! = vm . end ( ) ; + + it ) {
const TxHash hash = Util : : ParseHexFast ( it . key ( ) . toUtf8 ( ) ) ;
if ( hash . length ( ) ! = HashLen ) {
2019-12-28 16:34:50 +02:00
Error ( ) < < resp . method < < " : got an empty tx hash " ;
2019-12-28 15:10:07 +02:00
emit errored ( ) ;
return ;
}
droppedTxs . erase ( hash ) ; // mark this tx as "not dropped"
const QVariantMap m = it . value ( ) . toMap ( ) ;
if ( m . isEmpty ( ) ) {
2019-12-28 16:34:50 +02:00
Error ( ) < < resp . method < < " : got an empty dict for tx hash " < < hash . toHex ( ) ;
2019-12-28 15:10:07 +02:00
emit errored ( ) ;
return ;
}
Mempool : : TxRef tx ;
static const QVariantList EmptyList ; // avoid constructng this for each iteration
if ( auto it = mempool . txs . find ( hash ) ; it ! = mempool . txs . end ( ) ) {
tx = it - > second ;
2019-12-29 11:38:24 +02:00
if ( TRACE ) Debug ( ) < < " Existing mempool tx: " < < hash . toHex ( ) ;
2019-12-28 15:10:07 +02:00
} else {
2019-12-29 11:38:24 +02:00
if ( TRACE ) Debug ( ) < < " New mempool tx: " < < hash . toHex ( ) ;
2019-12-28 15:10:07 +02:00
+ + newCt ;
tx = std : : make_shared < Mempool : : Tx > ( ) ;
tx - > hash = hash ;
2019-12-28 23:26:16 +02:00
tx - > ordinal = mempool . nextOrdinal + + ;
2019-12-28 15:10:07 +02:00
tx - > sizeBytes = m . value ( " size " , 0 ) . toUInt ( ) ;
tx - > fee = int64_t ( m . value ( " fee " , 0.0 ) . toDouble ( ) * ( bitcoin : : COIN / bitcoin : : Amount : : satoshi ( ) ) ) * bitcoin : : Amount : : satoshi ( ) ;
tx - > time = int64_t ( m . value ( " time " , 0 ) . toULongLong ( ) ) ;
tx - > height = m . value ( " height " , 0 ) . toUInt ( ) ;
// Note mempool tx's may have any height in the past because they may not confirm when new blocks arrive...
if ( tx - > height > unsigned ( tipHeight ) ) {
Debug ( ) < < resp . method < < " : tx height " < < tx - > height < < " > current height " < < tipHeight < < " , assuming a new block has arrived, aborting mempool synch ... " ;
mempool . clear ( ) ;
2019-12-28 23:26:16 +02:00
emit retryRecommended ( ) ; // this is an exit point for this task
2019-12-28 15:10:07 +02:00
return ;
}
// save ancestor count exactly once. this should never change unless there is a reorg, at which point
// our in-mempory mempool is wiped anyway.
tx - > ancestorCount = m . value ( " ancestorcount " , 0 ) . toUInt ( ) ;
if ( ! tx - > ancestorCount ) {
2019-12-28 16:34:50 +02:00
Error ( ) < < resp . method < < " : failed to parse ancestor count for tx " < < hash . toHex ( ) ;
2019-12-28 15:10:07 +02:00
emit errored ( ) ;
return ;
}
// we only build the depends list once per tx instantiation
const auto depends = m . value ( " depends " , EmptyList ) . toList ( ) ;
for ( const auto & var : depends ) {
TxHash deptx = Util : : ParseHexFast ( var . toString ( ) . toUtf8 ( ) ) ;
if ( deptx . length ( ) ! = HashLen ) {
2019-12-28 16:34:50 +02:00
Error ( ) < < " Error parsing depend ` " < < var . toString ( ) < < " ` for mempool tx " < < hash . toHex ( ) ;
2019-12-28 15:10:07 +02:00
emit errored ( ) ;
return ;
}
auto res = tx - > depends . insert ( deptx ) ;
2019-12-29 11:38:24 +02:00
if ( res . second & & TRACE ) {
2019-12-28 15:10:07 +02:00
Debug ( ) < < " new dep: " < < deptx . toHex ( ) < < " for tx: " < < hash . toHex ( ) ;
}
}
txsNeedingDownload [ hash ] = tx ;
}
// at this point we have a valid tx ptr
// update descendantCount since it may change as new tx's appear in mempool
tx - > descendantCount = m . value ( " descendantcount " , 0 ) . toUInt ( ) ;
if ( ! tx - > descendantCount ) {
2019-12-28 16:34:50 +02:00
Error ( ) < < resp . method < < " : failed to parse descendant count for tx " < < hash . toHex ( ) ;
2019-12-28 15:10:07 +02:00
emit errored ( ) ;
return ;
}
const auto spentby = m . value ( " spentby " , EmptyList ) . toList ( ) ;
for ( const auto & var : spentby ) {
TxHash spendtx = Util : : ParseHexFast ( var . toString ( ) . toUtf8 ( ) ) ;
if ( spendtx . length ( ) ! = HashLen ) {
2019-12-28 16:34:50 +02:00
Error ( ) < < " Error parsing spentby ` " < < var . toString ( ) < < " ` for mempool tx " < < hash . toHex ( ) ;
2019-12-28 15:10:07 +02:00
emit errored ( ) ;
return ;
}
auto res = tx - > spentBy . insert ( spendtx ) ;
2019-12-29 11:38:24 +02:00
if ( res . second & & TRACE ) {
2019-12-28 15:10:07 +02:00
Debug ( ) < < " new spentby: " < < spendtx . toHex ( ) < < " for tx: " < < hash . toHex ( ) ;
}
}
// detect spentBy deletions... this normally won't happen unless a descendant tx has dropped out of the mempool
// if this happens mempool needs to be completely rebuilt -- in which case we reset this class's state as well
// as the known-mempool state, and try again.
// TODO here: keep track of notifications?
for ( const auto & hash : tx - > spentBy ) {
const auto hashHex = Util : : ToHexFast ( hash ) ;
if ( UNLIKELY ( ! spentby . contains ( hashHex ) ) ) {
Debug ( ) < < " spent-by descendant tx " < < hashHex < < " disappeared from mempool for parent tx " < < Util : : ToHexFast ( tx - > hash )
< < " , resetting mempool and trying again ... " ;
mempool . clear ( ) ;
clear ( ) ;
AGAIN ( ) ;
return ;
}
}
}
if ( UNLIKELY ( ! droppedTxs . empty ( ) ) ) {
2019-12-28 23:26:16 +02:00
const bool recommendFullRetry = oldCt > = 2 & & droppedTxs . size ( ) > = oldCt / 2 ; // more than 50% of the mempool tx's dropped out. something is funny. likely a new block arrived.
2019-12-28 15:10:07 +02:00
// TODO here: keep track of notifications?
Debug ( ) < < droppedTxs . size ( ) < < " txs dropped from mempool, resetting mempool and trying again ... " ;
mempool . clear ( ) ;
2019-12-28 23:26:16 +02:00
if ( recommendFullRetry ) {
emit retryRecommended ( ) ; // this is an exit point for this task
return ;
}
2019-12-28 15:10:07 +02:00
clear ( ) ;
AGAIN ( ) ;
return ;
}
2019-12-29 00:45:09 +02:00
if ( newCt )
2019-12-29 11:38:24 +02:00
Debug ( ) < < resp . method < < " : got reply with " < < vm . size ( ) < < " items, " < < newCt < < " new " ;
2019-12-28 15:10:07 +02:00
isdlingtxs = true ;
expectedNumTxsDownloaded = unsigned ( newCt ) ;
// TX data will be downloaded now, if needed
AGAIN ( ) ;
} ) ;
}
2019-11-18 08:29:33 +02:00
struct Controller : : StateMachine
{
2019-11-18 20:29:21 +02:00
enum State {
2019-12-15 20:52:21 +02:00
Begin = 0 , GetBlocks , DownloadingBlocks , FinishedDL , End , Failure , IBD , Retry ,
2019-12-28 15:10:07 +02:00
SynchMempool , SynchingMempool , SynchMempoolFinished
2019-11-18 20:29:21 +02:00
} ;
State state = Begin ;
2019-12-14 00:47:11 +02:00
int ht = - 1 ; ///< the latest height bitcoind told us this run
2019-12-11 00:57:52 +02:00
bool isMainNet = false ;
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-11-25 22:34:11 +02:00
const char * stateStr ( ) const {
2019-12-15 20:52:21 +02:00
static constexpr const char * stateStrings [ ] = { " Begin " , " GetBlocks " , " DownloadingBlocks " , " FinishedDL " , " End " ,
" Failure " , " IBD " , " Retry " ,
2019-12-28 15:10:07 +02:00
" SynchMempool " , " SynchingMempool " , " SynchMempoolFinished " ,
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-12-13 18:23:24 +02:00
static constexpr unsigned progressIntervalBlocks = 1000 ;
size_t nProgBlocks = 0 , nProgIOs = 0 , nProgTx = 0 ;
double lastProgTs = 0. ;
2019-12-23 18:36:36 +02:00
/// this pointer should *not* be dereferenced (which is why it's void *), but rather is just used to filter out
/// old/stale GetChainInfoTask responses in Controller::process()
void * mostRecentGetChainInfoTask = nullptr ;
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-11 00:21:08 +02:00
std : : shared_lock g ( smLock ) ; // this lock guarantees that 'sm' won't be deleted from underneath us
2019-12-08 22:07:01 +02:00
if ( sm ) {
2019-12-11 00:57:52 +02:00
int maxBackLog = 1000 ; // <--- TODO: have this be a more dynamic value based on current average blocksize.
if ( sm - > isMainNet ) {
// mainnet
if ( bnum > 150000 ) // beyond this height the blocks are starting to be big enough that we want to not eat memory.
maxBackLog = 250 ;
else if ( bnum > 550000 ) // beyond this height we may start to see 32MB blocks in the future
maxBackLog = 100 ;
} else {
// testnet
if ( bnum > 1300000 ) // beyond this height 32MB blocks may be common, esp. in the future
maxBackLog = 100 ;
}
2019-12-09 16:13:12 +02:00
const int diff = int ( bnum ) - int ( sm - > ppBlkHtNext . load ( ) ) ; // note: ppBlkHtNext is not guarded by the lock but it is an atomic value, so that's fine.
2019-12-09 15:39:39 +02:00
if ( diff > maxBackLog ) {
2019-12-11 00:57:52 +02:00
// Make the backoff time be from 10ms to 50ms, depending on how far in the future this block height is from
// what we are processing. The hope is that this enforces some order on future block arrivals and also
// prevents excessive polling for blocks that are too far ahead of us.
return std : : min ( 10u + 5 * unsigned ( diff - maxBackLog - 1 ) , 50u ) ; // TODO: also have this be tuneable.
2019-12-08 22:07:01 +02:00
}
2019-12-11 00:57:52 +02:00
2019-12-08 19:14:05 +02:00
}
2019-12-11 00:57:52 +02:00
return 0u ;
2019-12-08 19:14:05 +02:00
}
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-11-27 12:03:12 +02:00
void Controller : : genericTaskErrored ( )
{
if ( sm & & sm - > state ! = StateMachine : : State : : Failure ) {
2019-12-28 23:26:16 +02:00
if ( LIKELY ( sm ) )
sm - > state = StateMachine : : State : : Failure ;
2019-11-27 12:03:12 +02:00
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 ) ;
2019-12-28 23:26:16 +02:00
connect ( task , & CtlTask : : retryRecommended , this , [ this ] { // only the SynchMempoolTask ever emits this
if ( LIKELY ( sm ) )
sm - > state = StateMachine : : State : : Retry ;
AGAIN ( ) ;
} ) ;
2019-12-23 18:36:36 +02:00
Util : : AsyncOnObject ( this , [ task , this ] { // schedule start when we return to our event loop
2019-11-27 20:32:50 +02:00
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-12-28 23:26:16 +02:00
auto polltimeout = polltimeMS ;
2019-11-25 22:34:11 +02:00
stopTimer ( pollTimerName ) ;
2019-12-02 13:50:18 +02:00
//Debug() << "Process called...";
2019-12-11 00:21:08 +02:00
if ( ! sm ) {
std : : lock_guard g ( smLock ) ;
sm = std : : make_unique < StateMachine > ( ) ;
}
2019-11-25 22:34:11 +02:00
using State = StateMachine : : State ;
if ( sm - > state = = State : : Begin ) {
2019-11-27 20:32:50 +02:00
auto task = newTask < GetChainInfoTask > ( true , this ) ;
2019-12-29 00:45:09 +02:00
task - > threadObjectDebugLifecycle = Trace : : isEnabled ( ) ; // suppress debug prints here unless we are in trace mode
2019-12-23 18:36:36 +02:00
sm - > mostRecentGetChainInfoTask = task ; // reentrancy defense mechanism for ignoring all but the most recent getchaininfo reply from bitcoind
2019-11-25 22:34:11 +02:00
connect ( task , & CtlTask : : success , this , [ this , task , beSilentIfUpToDate ] {
2019-12-23 18:36:36 +02:00
if ( UNLIKELY ( ! sm | | task ! = sm - > mostRecentGetChainInfoTask | | isTaskDeleted ( task ) ) )
// task was stopped from underneath us and/or this response is stale.. so return and ignore
return ;
sm - > mostRecentGetChainInfoTask = nullptr ;
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-12-11 00:57:52 +02:00
sm - > isMainNet = task - > info . chain = = " main " ;
2019-12-18 17:53:16 +02:00
QByteArray tipHeader ;
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-18 17:53:16 +02:00
const auto [ tip , tipHash ] = storage - > latestTip ( & tipHeader ) ;
2019-11-27 12:12:16 +02:00
sm - > ht = task - > info . blocks ;
2019-12-18 17:53:16 +02:00
if ( tip = = sm - > ht ) {
if ( task - > info . bestBlockhash = = tipHash ) { // no reorg
2019-12-15 20:52:21 +02:00
if ( ! beSilentIfUpToDate ) {
2019-12-22 01:20:24 +02:00
storage - > updateMerkleCache ( unsigned ( tip ) ) ;
Log ( ) < < " Block height " < < tip < < " , up-to-date " ;
2019-12-15 20:52:21 +02:00
emit upToDate ( ) ;
2019-12-18 17:53:16 +02:00
emit newHeader ( unsigned ( tip ) , tipHeader ) ;
2019-12-15 20:52:21 +02:00
}
2019-12-28 23:26:16 +02:00
sm - > state = State : : SynchMempool ; // now, move on to synch mempool
2019-12-15 20:52:21 +02:00
} else {
// height ok, but best block hash mismatch.. reorg
2019-12-18 17:53:16 +02:00
Warning ( ) < < " We have bestBlock " < < tipHash . toHex ( ) < < " , but bitcoind reports bestBlock " < < task - > info . bestBlockhash . toHex ( ) < < " . "
2019-12-15 20:52:21 +02:00
< < " Possible reorg, will rewind back 1 block and try again ... " ;
process_DoUndoAndRetry ( ) ; // attempt to undo 1 block and try again.
return ;
2019-11-27 14:07:09 +02:00
}
2019-12-18 17:53:16 +02:00
} else if ( tip > sm - > ht ) {
Warning ( ) < < " We have height " < < tip < < " , but bitcoind reports height " < < sm - > ht < < " . "
2019-12-15 20:52:21 +02:00
< < " Possible reorg, will rewind back 1 block and try again ... " ;
process_DoUndoAndRetry ( ) ; // attempt to undo 1 block and try again.
2019-11-30 01:33:27 +02:00
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-13 18:23:24 +02:00
sm - > lastProgTs = Util : : getTimeSecs ( ) ;
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-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-11 00:21:08 +02:00
std : : lock_guard g ( smLock ) ;
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-15 20:52:21 +02:00
} else if ( sm - > state = = State : : Retry ) {
// normally the result of Rewinding due to reorg, retry right away.
Debug ( ) < < " Retrying download again ... " ;
{
std : : lock_guard g ( smLock ) ;
sm . reset ( ) ;
}
AGAIN ( ) ;
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-28 15:10:07 +02:00
Error ( ) < < " Failed to synch blocks and/or mempool " ;
2019-12-08 19:14:05 +02:00
{
2019-12-11 00:21:08 +02:00
std : : lock_guard g ( smLock ) ;
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-11 00:21:08 +02:00
std : : lock_guard g ( smLock ) ;
2019-12-08 19:14:05 +02:00
sm . reset ( ) ; // great success!
}
2019-11-25 22:34:11 +02:00
enablePollTimer = true ;
2019-11-27 12:03:12 +02:00
} else if ( sm - > state = = State : : IBD ) {
2019-12-08 19:14:05 +02:00
{
2019-12-11 00:21:08 +02:00
std : : lock_guard g ( smLock ) ;
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-12-28 15:10:07 +02:00
} else if ( sm - > state = = State : : SynchMempool ) {
// ...
auto task = newTask < SynchMempoolTask > ( true , this , storage ) ;
2019-12-29 00:45:09 +02:00
task - > threadObjectDebugLifecycle = Trace : : isEnabled ( ) ; // suppress verbose lifecycle prints unless trace mode
2019-12-28 15:10:07 +02:00
connect ( task , & CtlTask : : success , this , [ this , task ] {
if ( UNLIKELY ( ! sm | | isTaskDeleted ( task ) | | sm - > state ! = State : : SynchingMempool ) )
// task was stopped from underneath us and/or this response is stale.. so return and ignore
return ;
2019-12-31 13:14:27 +02:00
if ( masterNotifySubsFlag ) // this is false until we enable the servers that listen for connections
// notify status change for affected sh's
storage - > subs ( ) - > enqueueNotifications ( std : : move ( task - > scriptHashesAffected ) ) ;
2019-12-28 15:10:07 +02:00
sm - > state = State : : SynchMempoolFinished ;
AGAIN ( ) ;
} ) ;
sm - > state = State : : SynchingMempool ;
} else if ( sm - > state = = State : : SynchingMempool ) {
// ... nothing..
} else if ( sm - > state = = State : : SynchMempoolFinished ) {
// ...
sm - > state = State : : End ;
AGAIN ( ) ;
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 ) {
2019-12-15 20:52:21 +02:00
Debug ( ) < < " Ignoring putBlocks request for block " < < p - > height < < " -- state is not \" DownloadingBlocks \" but rather is: \" " < < sm - > stateStr ( ) < < " \" " ;
2019-12-02 13:50:18 +02:00
return ;
2019-12-02 11:42:10 +02:00
}
2019-12-11 00:21:08 +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-13 18:23:24 +02:00
void Controller : : process_PrintProgress ( unsigned height , size_t nTx , size_t nIO )
2019-12-11 00:21:08 +02:00
{
if ( UNLIKELY ( ! sm ) ) return ; // paranaoia
2019-12-13 18:23:24 +02:00
sm - > nProgBlocks + + ;
sm - > nProgTx + = nTx ;
sm - > nProgIOs + = nIO ;
if ( UNLIKELY ( height & & ! ( height % sm - > progressIntervalBlocks ) ) ) {
static const auto formatRate = [ ] ( double rate , const QString & thing , bool addComma = true ) {
QString unit = " sec " ;
if ( rate < 1.0 & & rate > 0.0 ) {
rate * = 60.0 ;
unit = " min " ;
}
if ( rate < 1.0 & & rate > 0.0 ) {
rate * = 60.0 ;
unit = " hour " ;
}
static const auto format = [ ] ( double rate ) { return QString : : number ( rate , ' f ' , rate < 10. ? ( rate < 1.0 ? 3 : 2 ) : 1 ) ; } ;
return rate > 0.0 ? QString ( " %1%2 %3/%4 " ) . arg ( addComma ? " , " : " " ) . arg ( format ( rate ) ) . arg ( thing ) . arg ( unit ) : QString ( ) ;
} ;
const double now = Util : : getTimeSecs ( ) ;
const double elapsed = std : : max ( now - sm - > lastProgTs , 0.00001 ) ; // ensure no division by zero
2019-12-11 00:21:08 +02:00
QString pctDisplay = QString : : number ( ( height * 1e2 ) / std : : max ( sm - > endHeight , 1U ) , ' f ' , 1 ) + " % " ;
2019-12-13 18:23:24 +02:00
const double rateBlocks = sm - > nProgBlocks / elapsed ;
const double rateIO = sm - > nProgIOs / elapsed ;
Log ( ) < < " Processed height: " < < height < < " , " < < pctDisplay < < formatRate ( rateBlocks , " blocks " ) < < formatRate ( rateIO , " ins & outs " ) ;
// update/reset ts and counters
sm - > lastProgTs = now ;
sm - > nProgBlocks = sm - > nProgTx = sm - > nProgIOs = 0 ;
2019-12-11 00:21:08 +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-09 16:13:12 +02:00
+ + 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
2019-12-13 18:23:24 +02:00
process_PrintProgress ( ppb - > height , ppb - > txInfos . size ( ) , ppb - > inputs . size ( ) + ppb - > outputs . size ( ) ) ;
2019-12-11 00:21:08 +02:00
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;
//}
}
2019-12-15 23:40:52 +02:00
namespace {
const QString inconsistentStateSorry ( " \n \n The database is now likely in an inconsistent state. "
" To recover, you will need to delete the datadir and do a full resynch. "
" Sorry! \n " ) ;
}
2019-12-02 13:50:18 +02:00
bool Controller : : process_VerifyAndAddBlock ( PreProcessedBlockPtr ppb )
{
assert ( sm ) ;
2019-12-15 12:33:02 +02:00
try {
const auto nLeft = qMax ( sm - > endHeight - ( sm - > ppBlkHtNext - 1 ) , 0U ) ;
2019-12-24 21:15:48 +02:00
const bool saveUndoInfo = int ( ppb - > height ) > ( sm - > ht - int ( storage - > configuredUndoDepth ( ) ) ) ;
2019-12-05 12:07:27 +02:00
2019-12-31 13:14:27 +02:00
storage - > addBlock ( ppb , saveUndoInfo , nLeft , masterNotifySubsFlag ) ;
2019-12-05 12:07:27 +02:00
2019-12-15 20:52:21 +02:00
} catch ( const HeaderVerificationFailure & e ) {
Debug ( ) < < " addBlock exception: " < < e . what ( ) ;
Log ( ) < < " Possible reorg detected at height " < < ppb - > height < < " , rewinding 1 block and trying again ... " ;
process_DoUndoAndRetry ( ) ;
return false ;
2019-12-15 12:33:02 +02:00
} catch ( const std : : exception & e ) {
2019-12-15 23:40:52 +02:00
// TODO: see about more graceful error and not a fatal exit. (although if we do get an error here it's pretty non-recoverable!)
Fatal ( ) < < e . what ( ) < < inconsistentStateSorry ;
2019-12-07 20:34:19 +02:00
sm - > state = StateMachine : : State : : Failure ;
2019-12-15 23:40:52 +02:00
// app will shut down after return to event loop.
2019-12-07 20:34:19 +02:00
return false ;
2019-12-04 23:53:45 +02:00
}
2019-12-02 13:50:18 +02:00
return true ;
}
2019-12-15 20:52:21 +02:00
void Controller : : process_DoUndoAndRetry ( )
{
assert ( sm ) ;
try {
2019-12-31 13:14:27 +02:00
storage - > undoLatestBlock ( masterNotifySubsFlag ) ;
2019-12-15 20:52:21 +02:00
// . <-- If we get here, rollback was successful.
// We flag the state to retry, which retries the full download right away
2019-12-15 23:40:52 +02:00
// (Note: this may eventually lead us to roll back again and again until we reorg to the sufficient depth).
2019-12-15 20:52:21 +02:00
sm - > state = StateMachine : : State : : Retry ;
AGAIN ( ) ; // schedule us again to do cleanup
} catch ( const std : : exception & e ) {
2019-12-15 23:40:52 +02:00
Fatal ( ) < < " Failed to rewind: " < < e . what ( ) < < inconsistentStateSorry ;
2019-12-15 20:52:21 +02:00
sm - > state = StateMachine : : State : : Failure ;
2019-12-15 23:40:52 +02:00
// upon return to event loop, will shut down
2019-12-15 20:52:21 +02:00
}
}
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 ( ) {
2019-12-30 01:58:54 +02:00
if ( isLifecyclePrint ( ) ) Debug ( ) < < __func__ < < " ( " < < objectName ( ) < < " ) " ;
2019-11-25 22:34:11 +02:00
stop ( ) ;
}
void CtlTask : : on_started ( )
{
ThreadObjectMixin : : on_started ( ) ;
conns + = connect ( this , & CtlTask : : success , ctl , [ this ] { stop ( ) ; } ) ;
conns + = connect ( this , & CtlTask : : errored , ctl , [ this ] { stop ( ) ; } ) ;
2019-12-28 23:26:16 +02:00
conns + = connect ( this , & CtlTask : : retryRecommended , ctl , [ this ] { stop ( ) ; } ) ;
2019-11-25 22:34:11 +02:00
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 ;
2019-12-09 15:45:52 +02:00
m [ " Chain " ] = storage - > getChain ( ) ;
2019-12-07 20:34:19 +02:00
m [ " Chain tip " ] = tipInfo . second . toHex ( ) ;
2019-12-09 12:10:59 +02:00
m [ " UTXO set " ] = qlonglong ( storage - > utxoSetSize ( ) ) ;
m [ " UTXO set bytes " ] = QString : : number ( storage - > utxoSetSizeMiB ( ) , ' f ' , 3 ) + " MiB " ;
2019-12-11 12:02:41 +02:00
const auto txnum = qlonglong ( storage - > getTxNum ( ) ) ;
m [ " TxNum " ] = txnum ;
m [ " TxNum -> TxHash (latest) " ] = txnum ? storage - > hashForTxNum ( TxNum ( txnum - 1 ) , false , nullptr , true ) . value_or ( " " ) . toHex ( ) : QVariant ( ) ;
2019-11-25 22:34:11 +02:00
if ( sm ) {
QVariantMap m2 ;
m2 [ " State " ] = sm - > stateStr ( ) ;
m2 [ " Height " ] = sm - > ht ;
2019-12-13 18:23:24 +02:00
if ( const auto nDL = nBlocksDownloadedSoFar ( ) ; nDL > 0 )
m2 [ " Blocks 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 ;
2019-12-12 00:23:19 +02:00
st [ " Storage " ] = storage - > statsSafe ( ) ;
2019-12-21 13:24:53 +02:00
QVariantMap misc ;
2019-12-24 13:18:44 +02:00
{
QVariantMap m ;
m [ " extant jobs " ] = Util : : ThreadPool : : ExtantJobs ( ) ;
m [ " extant jobs (max lifetime) " ] = Util : : ThreadPool : : ExtantJobsMaxSeen ( ) ;
m [ " extant limit " ] = Util : : ThreadPool : : ExtantJobLimit ( ) ;
m [ " job count (lifetime) " ] = qulonglong ( Util : : ThreadPool : : NumJobsSubmitted ( ) ) ;
m [ " job queue overflows (lifetime) " ] = qulonglong ( Util : : ThreadPool : : Overflows ( ) ) ;
misc [ " Job Queue (Thread Pool) " ] = m ;
}
2019-12-21 13:24:53 +02:00
st [ " Misc " ] = misc ;
2019-12-30 14:58:16 +02:00
st [ " SubsMgr " ] = storage - > subs ( ) - > statsSafe ( kDefaultTimeout / 2 ) ;
2019-11-25 22:34:11 +02:00
return st ;
}
2019-11-18 20:29:21 +02:00
2019-12-12 12:07:18 +02:00
auto Controller : : debug ( const StatsParams & p ) const - > Stats // from StatsMixin
{
QVariantMap ret ;
bool ok ;
const auto t0 = Util : : getTimeNS ( ) ;
if ( const auto txnum = p . value ( " txnum " ) . toULong ( & ok ) ; ok ) {
QVariantMap m ;
auto hash = storage - > hashForTxNum ( txnum ) . value_or ( QByteArray ( ) ) ;
auto opt = storage - > heightForTxNum ( txnum ) ;
2019-12-12 13:01:31 +02:00
m [ " tx_hash " ] = Util : : ToHexFast ( hash ) ;
2019-12-12 12:07:18 +02:00
m [ " height " ] = opt . has_value ( ) ? int ( opt . value ( ) ) : - 1 ;
ret [ " txnum_debug " ] = m ;
}
if ( const auto sh = QByteArray : : fromHex ( p . value ( " sh " ) . toLatin1 ( ) ) ; sh . length ( ) = = HashLen ) {
QVariantList l ;
2019-12-28 15:10:07 +02:00
auto items = storage - > getHistory ( sh , true , true ) ;
2019-12-12 12:07:18 +02:00
for ( const auto & item : items ) {
QVariantMap m ;
2019-12-12 13:01:31 +02:00
m [ " tx_hash " ] = Util : : ToHexFast ( item . hash ) ;
2019-12-12 12:07:18 +02:00
m [ " height " ] = item . height ;
2019-12-28 15:10:07 +02:00
if ( item . fee . has_value ( ) )
m [ " fee " ] = item . fee . value ( ) / item . fee . value ( ) . satoshi ( ) ;
2019-12-12 12:07:18 +02:00
l . push_back ( m ) ;
}
ret [ " sh_debug " ] = l ;
}
2019-12-13 00:14:09 +02:00
if ( const auto hashx = QByteArray : : fromHex ( p . value ( " unspent " ) . toLatin1 ( ) ) ; hashx . length ( ) = = HashLen ) {
QVariantList l ;
auto items = storage - > listUnspent ( hashx ) ;
for ( const auto & item : items ) {
QVariantMap m ;
m [ " tx_hash " ] = Util : : ToHexFast ( item . hash ) ;
m [ " height " ] = item . height ;
m [ " tx_pos " ] = item . tx_pos ;
m [ " value " ] = item . value / item . value . satoshi ( ) ;
l . push_back ( m ) ;
}
ret [ " unspent_debug " ] = l ;
}
2019-12-28 15:10:07 +02:00
if ( p . count ( " mempool " ) ) {
QVariantMap mp , txs ;
auto [ mempool , lock ] = storage - > mempool ( ) ;
for ( const auto & [ hash , tx ] : mempool . txs ) {
if ( ! tx ) continue ;
QVariantMap m ;
m [ " hash " ] = tx - > hash . toHex ( ) ;
2019-12-28 23:26:16 +02:00
m [ " ordinal " ] = tx - > ordinal ;
2019-12-28 15:10:07 +02:00
m [ " sizeBytes " ] = tx - > sizeBytes ;
m [ " fee " ] = tx - > fee . ToString ( ) . c_str ( ) ;
m [ " time " ] = qlonglong ( tx - > time ) ;
m [ " height " ] = unsigned ( tx - > height ) ;
m [ " ancestorCount " ] = tx - > ancestorCount ;
m [ " descendantCount " ] = tx - > descendantCount ;
QStringList l ;
for ( const auto & d : tx - > depends ) l . push_back ( d . toHex ( ) ) ;
m [ " depends " ] = l ;
l . clear ( ) ;
for ( const auto & s : tx - > spentBy ) l . push_back ( s . toHex ( ) ) ;
m [ " spentBy " ] = l ;
static const auto TXOInfo2Map = [ ] ( const TXOInfo & info ) - > QVariantMap {
return QVariantMap {
{ " amount " , QString : : fromStdString ( info . amount . ToString ( ) ) } ,
{ " scriptHash " , info . hashX . toHex ( ) } ,
} ;
} ;
QVariantMap txos ;
2019-12-31 16:22:52 +02:00
IONum num = 0 ;
for ( const auto & info : tx - > txos ) {
QVariantMap infoMap ;
if ( info . isValid ( ) )
infoMap = TXOInfo2Map ( info ) ;
else
infoMap = QVariantMap {
{ " amount " , QVariant ( ) } ,
{ " scriptHash " , QVariant ( ) } ,
{ " comment " , " OP_RETURN output not indexed " } ,
} ;
txos [ QString : : number ( num + + ) ] = infoMap ;
2019-12-28 15:10:07 +02:00
}
m [ " txos " ] = txos ;
QVariantMap hxs ;
static const auto IOInfo2Map = [ ] ( const Mempool : : Tx : : IOInfo & inf ) - > QVariantMap {
QVariantMap ret ;
2019-12-30 17:37:59 +02:00
const auto vl = QVariantList : : fromStdList ( Util : : toList < std : : list < QVariant > > ( inf . utxo ) ) ;
2019-12-28 15:10:07 +02:00
ret [ " utxos " ] = vl ;
QVariantMap cs ;
for ( const auto & [ txo , info ] : inf . confirmedSpends )
cs [ txo . toString ( ) ] = TXOInfo2Map ( info ) ;
ret [ " confirmedSpends " ] = cs ;
QVariantMap us ;
for ( const auto & [ txo , info ] : inf . unconfirmedSpends )
us [ txo . toString ( ) ] = TXOInfo2Map ( info ) ;
ret [ " unconfirmedSpends " ] = us ;
return ret ;
} ;
for ( const auto & [ sh , ioinfo ] : tx - > hashXs )
hxs [ sh . toHex ( ) ] = IOInfo2Map ( ioinfo ) ;
m [ " hashXs " ] = hxs ;
txs [ hash . toHex ( ) ] = m ;
}
mp [ " txs " ] = txs ;
QVariantMap hxs ;
for ( const auto & [ sh , txset ] : mempool . hashXTxs ) {
QVariantList l ;
for ( const auto & tx : txset )
if ( tx ) l . push_back ( tx - > hash . toHex ( ) ) ;
hxs [ sh . toHex ( ) ] = l ;
}
mp [ " hashXTxs " ] = hxs ;
ret [ " mempool_debug " ] = mp ;
}
2019-12-12 12:07:18 +02:00
const auto elapsed = Util : : getTimeNS ( ) - t0 ;
ret [ " elapsed " ] = QString : : number ( elapsed / 1e6 , ' f ' , 6 ) + " msec " ;
return ret ;
}
2019-12-13 18:23:24 +02:00
size_t Controller : : nBlocksDownloadedSoFar ( ) const
2019-11-25 22:34:11 +02:00
{
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
}