2019-11-18 20:29:21 +02:00
# include "BTC.h"
2019-11-18 00:21:34 +02:00
# include "Controller.h"
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 ( )
{
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-18 08:29:33 +02:00
srvmgr = std : : make_unique < SrvMgr > ( options - > interfaces , nullptr /* we are not parent so it won't follow us to our thread*/ ) ;
2019-11-18 00:21:34 +02:00
srvmgr - > startup ( ) ; // may throw Exception, waits for servers to bind
2019-11-18 08:29:33 +02:00
start ( ) ; // start our thread
2019-11-18 00:21:34 +02:00
}
void Controller : : cleanup ( )
{
2019-11-18 08:29:33 +02:00
stop ( ) ;
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-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 " ) { }
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 ( ) ) ;
if ( info . bestBlockhash . size ( ) ! = bitcoin : : uint256 : : width ( ) ) 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-25 22:34:11 +02:00
struct DownloadHeadersTask : public CtlTask
{
DownloadHeadersTask ( unsigned from , unsigned to , Controller * ctl ) ;
void process ( ) override ;
2019-11-26 00:18:28 +02:00
2019-11-25 22:34:11 +02:00
const unsigned from = 0 , to = 0 ;
unsigned next = 0 ;
unsigned goodCt = 0 ;
2019-11-26 00:18:28 +02:00
bool maybeDone = false ;
2019-11-25 22:34:11 +02:00
std : : vector < QByteArray > headers ;
2019-11-26 00:18:28 +02:00
int q_ct = 0 ;
static constexpr int max_q = 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
void do_get ( unsigned height ) ;
// thread safe, this is a rough estimate and not 100% accurate
size_t nSoFar ( ) const { return size_t ( qRound ( ( ( to - from ) + 1 ) * lastProgress ) ) ; }
} ;
/*static*/ const int DownloadHeadersTask : : HEADER_SIZE = int ( BTC : : GetBlockHeaderSize ( ) ) ;
DownloadHeadersTask : : DownloadHeadersTask ( unsigned from , unsigned to , Controller * ctl_ )
: CtlTask ( ctl_ , QString ( " Task.Headers %1 -> %2 " ) . arg ( from ) . arg ( to ) ) , from ( from ) , to ( to )
{
assert ( to > = from ) ; assert ( ctl_ ) ;
next = from ;
headers . reserve ( ( to - from ) + 1 ) ;
}
void DownloadHeadersTask : : process ( )
{
if ( next > to ) {
if ( maybeDone ) {
int bad = 0 ;
for ( size_t index = 0 ; index < headers . size ( ) ; + + index ) {
if ( headers [ index ] . length ( ) ! = HEADER_SIZE ) {
+ + bad ;
errorCode = int ( index + from ) ; // height
break ;
}
}
if ( ! bad ) {
emit success ( ) ;
} else {
errorMessage = QString ( " header length incorrect for height %1 " ) . arg ( errorCode ) ;
emit errored ( ) ;
}
}
return ;
}
do_get ( next + + ) ;
}
void DownloadHeadersTask : : do_get ( unsigned int bnum )
{
submitRequest ( " getblockhash " , { bnum } , [ this , bnum ] ( const RPC : : Message & resp ) { // testing
QVariant var = resp . result ( ) ;
2019-11-26 22:04:45 +02:00
const auto hash = Util : : ParseHexFast ( var . toByteArray ( ) ) ;
2019-11-25 22:34:11 +02:00
if ( hash . length ( ) = = bitcoin : : uint256 : : width ( ) ) {
submitRequest ( " getblockheader " , { var , false } , [ this , bnum , hash ] ( const RPC : : Message & resp ) { // testing
QVariant var = resp . result ( ) ;
2019-11-26 22:04:45 +02:00
const auto header = Util : : ParseHexFast ( var . toByteArray ( ) ) ;
2019-11-25 22:34:11 +02:00
QByteArray chkHash ;
if ( bool sizeOk = header . length ( ) = = HEADER_SIZE ; sizeOk & & ( chkHash = BTC : : HashRev ( header ) ) = = hash ) {
const auto expectedCt = ( to - from ) + 1 ;
const size_t index = bnum - from ;
+ + goodCt ;
2019-11-25 23:34:04 +02:00
q_ct = qMax ( q_ct - 1 , 0 ) ;
2019-11-27 02:38:39 +02:00
if ( ! ( index % 1000 ) & & index ) {
2019-11-25 22:34:11 +02:00
emit progress ( double ( bnum - from ) / double ( expectedCt ) ) ;
}
if ( Trace : : isEnabled ( ) ) Trace ( ) < < resp . method < < " : header for height: " < < bnum < < " len: " < < header . length ( ) ;
// save header and hash when both are correct
if ( headers . size ( ) < index + 1 )
headers . resize ( index + 1 ) ;
headers [ index ] = header ;
if ( goodCt > = expectedCt ) {
// flag state to maybeDone to do checks when process() called again
maybeDone = true ;
2019-11-25 23:34:04 +02:00
AGAIN ( ) ;
return ;
}
while ( goodCt + unsigned ( q_ct ) < expectedCt & & q_ct < max_q ) {
// queue multiple at once
AGAIN ( ) ;
+ + q_ct ;
2019-11-25 22:34:11 +02:00
}
} 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-11-27 12:03:12 +02:00
Begin = 0 , GetBlockHeaders , 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
std : : map < unsigned , std : : vector < QByteArray > > blockHeaders ; // mapping of from_height -> headers
std : : map < std : : pair < unsigned , unsigned > , unsigned > failures ; // mapping of from,to -> failCt
2019-11-26 00:18:28 +02:00
// todo: tune this
const size_t DL_CONCURRENCY = size_t ( qMin ( qMax ( int ( Util : : getNPhysicalProcessors ( ) ) - BitcoinDMgr : : N_CLIENTS , BitcoinDMgr : : N_CLIENTS ) , 32 ) ) ;
2019-11-25 22:34:11 +02:00
static constexpr unsigned maxErrCt = 3 ;
const char * stateStr ( ) const {
2019-11-27 12:03:12 +02:00
static constexpr const char * stateStrings [ ] = { " Begin " , " GetBlockHeaders " , " 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-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 )
{
DownloadHeadersTask * t = new DownloadHeadersTask ( unsigned ( from ) , unsigned ( to ) , this ) ;
connect ( t , & CtlTask : : success , this , [ t , this , nTasks ] {
if ( UNLIKELY ( ! sm | | isTaskDeleted ( t ) ) ) return ; // task was stopped from underneath us, this is stale.. abort.
Debug ( ) < < " Got all headers from: " < < t - > objectName ( ) < < " headerCt: " < < t - > headers . size ( ) ;
sm - > blockHeaders [ t - > from ] . swap ( t - > headers ) ; // constant time copy
if ( sm - > blockHeaders . size ( ) = = nTasks ) {
sm - > state = StateMachine : : State : : FinishedDL ;
AGAIN ( ) ;
}
} ) ;
connect ( t , & CtlTask : : errored , this , [ t , this , nTasks ] {
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-11-25 22:34:11 +02:00
// Handle failures with retries for the specific task -- failures are unlikely so it's ok to do it on this level
if ( auto ct = + + sm - > failures [ std : : make_pair ( t - > from , t - > to ) ] ; ct < sm - > maxErrCt ) {
Warning ( ) < < " Task errored: " < < t - > objectName ( ) < < " , error: " < < t - > errorMessage < < " , retrying # " < < ct < < " ... " ;
add_DLHeaderTask ( t - > from , t - > to , nTasks ) ;
} else {
2019-11-27 12:03:12 +02:00
Error ( ) < < " Task errored: " < < t - > objectName ( ) < < " , error: " < < t - > errorMessage < < " , failed after " < < ct < < " tries, giving up " ;
genericTaskErrored ( ) ;
2019-11-25 22:34:11 +02:00
}
} ) ;
connect ( t , & CtlTask : : progress , t , [ t , this ] ( double prog ) {
if ( UNLIKELY ( ! sm | | isTaskDeleted ( t ) ) ) return ; // task was stopped from underneath us, this is stale.. abort.
2019-11-27 02:38:39 +02:00
Log ( ) < < " Downloaded height: " < < t - > from + unsigned ( qRound ( prog * ( ( t - > to - t - > from ) + 1 ) ) ) < < " , " < < QString : : number ( prog * 1e2 , ' f ' , 1 ) < < " % " ;
2019-11-25 22:34:11 +02:00
} , Qt : : DirectConnection ) ;
2019-11-27 02:38:39 +02:00
tasks . emplace ( t , t ) ;
2019-11-25 22:34:11 +02:00
t - > start ( ) ;
}
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-25 22:34:11 +02:00
void Controller : : process ( bool beSilentIfUpToDate )
{
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 ) ;
Debug ( ) < < " Process called... " ;
if ( ! sm ) sm = std : : make_unique < StateMachine > ( ) ;
using State = StateMachine : : State ;
if ( sm - > state = = State : : Begin ) {
2019-11-27 12:03:12 +02:00
auto task = new GetChainInfoTask ( 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 ;
}
// TODO: detect reorgs here -- to be implemented later after we figure out data model more, etc.
2019-11-25 22:34:11 +02:00
const auto old = int ( storage . headers . size ( ) ) - 1 ;
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 ) {
if ( ! beSilentIfUpToDate ) Log ( ) < < " Block height " < < sm - > ht < < " , up-to-date " ;
sm - > state = State : : End ;
} else {
Log ( ) < < " Block height " < < sm - > ht < < " , downloading new headers ... " ;
sm - > state = State : : GetBlockHeaders ;
}
AGAIN ( ) ;
} ) ;
2019-11-27 12:03:12 +02:00
connect ( task , & CtlTask : : errored , this , & Controller : : genericTaskErrored ) ;
2019-11-27 02:38:39 +02:00
tasks . emplace ( task , task ) ;
2019-11-25 22:34:11 +02:00
task - > start ( ) ;
} else if ( sm - > state = = State : : GetBlockHeaders ) {
const size_t base = storage . headers . size ( ) ;
const size_t num = size_t ( sm - > ht + 1 ) - base ;
const size_t nTasks = qMax ( size_t ( 1 ) , num < 1000 ? size_t ( 1 ) : sm - > DL_CONCURRENCY ) ;
const size_t headersPerTask = num / nTasks , rem = num % nTasks ;
for ( size_t i = 0 ; i < nTasks ; + + i ) {
auto N = i + 1 < nTasks ? headersPerTask : headersPerTask + rem ;
auto from = unsigned ( base + i * headersPerTask ) , to = unsigned ( ( from + N ) - 1 ) ;
add_DLHeaderTask ( from , to , nTasks ) ;
}
} else if ( sm - > state = = State : : FinishedDL ) {
size_t ctr = 0 ;
for ( const auto & pair : sm - > blockHeaders ) {
ctr + = pair . second . size ( ) ;
}
2019-11-25 22:56:46 +02:00
Log ( ) < < " Downloaded " < < ctr < < " new " < < Util : : Pluralize ( " header " , ctr ) < < " , verifying ... " ;
2019-11-25 22:34:11 +02:00
ctr = 0 ;
for ( auto & [ num , hdrs ] : sm - > blockHeaders ) {
Log ( ) < < " Verifying from " < < num < < " ... " ;
for ( const auto & hdr : hdrs ) {
if ( hdr . size ( ) ! = DownloadHeadersTask : : HEADER_SIZE ) {
Error ( ) < < " Header " < < ctr < < " has wrong length! " ;
sm - > state = State : : Failure ;
AGAIN ( ) ;
2019-11-18 20:48:22 +02:00
return ;
2019-11-18 20:29:21 +02:00
}
2019-11-25 22:34:11 +02:00
+ + ctr ;
2019-11-18 20:29:21 +02:00
}
}
2019-11-26 12:29:27 +02:00
if ( const auto size = storage . headers . size ( ) ; size + ctr < storage . headers . capacity ( ) )
storage . headers . reserve ( size + ctr ) ; // reserve space for new headers in 1 go to save on copying
2019-11-25 22:34:11 +02:00
ctr = 0 ;
for ( auto & [ num , hdrs ] : sm - > blockHeaders ) {
Debug ( ) < < " Copying from " < < num < < " ... " ;
storage . headers . insert ( storage . headers . end ( ) , hdrs . begin ( ) , hdrs . end ( ) ) ;
ctr + = hdrs . size ( ) ;
hdrs . clear ( ) ;
}
2019-11-26 12:29:27 +02:00
storage . headers . shrink_to_fit ( ) ; // make sure no memory is wasted since we don't push_back but rather reserve ahead of time each time.
2019-11-25 22:56:46 +02:00
Log ( ) < < " Verified & copied " < < ctr < < " new " < < Util : : Pluralize ( " header " , ctr ) < < " ok " ;
2019-11-25 22:34:11 +02:00
sm . reset ( ) ; // go back to "Begin" state to check if any new headers arrived in the meantime
AGAIN ( ) ;
} else if ( sm - > state = = State : : Failure ) {
2019-11-25 22:56:46 +02:00
// We will try again later via the pollTimer
2019-11-25 22:34:11 +02:00
Error ( ) < < " Failed to download headers " ;
sm . reset ( ) ;
enablePollTimer = true ;
} else if ( sm - > state = = State : : End ) {
sm . reset ( ) ; // great success!
enablePollTimer = true ;
2019-11-27 12:03:12 +02:00
} else if ( sm - > state = = State : : IBD ) {
sm . reset ( ) ;
enablePollTimer = true ;
Warning ( ) < < " bitcoind is in initial block download, will try again in 1 minute " ;
polltimeout = 60 * 1000 ; // try again every minute
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
}
// -- CtlTask
CtlTask : : CtlTask ( Controller * ctl , const QString & name )
: QObject ( nullptr ) , ctl ( ctl )
{
setObjectName ( name ) ;
_thread . setObjectName ( name ) ;
connect ( this , & CtlTask : : progress , this , [ this ] ( double prog ) { lastProgress = prog ; } ) ;
}
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"
auto st = srvmgr - > statsSafe ( ) ;
// "BitcoinD's"
Util : : updateMap ( st , bitcoindmgr - > statsSafe ( ) ) ;
// "Controller" (self)
QVariantMap m ;
m [ " Headers " ] = int ( storage . headers . size ( ) ) ;
if ( sm ) {
QVariantMap m2 ;
m2 [ " State " ] = sm - > stateStr ( ) ;
m2 [ " Height " ] = sm - > ht ;
if ( const auto nDL = nHeadersDownloadedSoFar ( ) ; nDL > 0 )
m2 [ " Headers_Downloaded_This_Run " ] = int ( nDL ) ;
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-25 22:34:11 +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-24 22:28:26 +02:00
}
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-25 22:34:11 +02:00
auto t = dynamic_cast < DownloadHeadersTask * > ( task ) ;
if ( t )
ret + = t - > nSoFar ( ) ;
}
return ret ;
2019-11-18 00:21:34 +02:00
}