2019-11-18 20:29:21 +02:00
# include "BTC.h"
2019-11-18 00:21:34 +02:00
# include "Controller.h"
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-18 00:34:24 +02:00
Controller : : ~ Controller ( ) { Debug ( __FUNCTION__ ) ; cleanup ( ) ; }
2019-11-18 00:21:34 +02:00
auto Controller : : stats ( ) const - > Stats
{
auto st = srvmgr - > statsSafe ( ) ;
Util : : updateMap ( st , bitcoindmgr - > statsSafe ( ) ) ;
return st ;
}
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 ;
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 ( ) ;
}
struct Controller : : StateMachine
{
2019-11-18 20:29:21 +02:00
enum State {
2019-11-18 20:48:22 +02:00
Begin , GetBlockHashes , End , Finished
2019-11-18 20:29:21 +02:00
} ;
State state = Begin ;
int bl = 0 ;
int ht = - 1 ;
int cur = 0 ;
2019-11-19 01:58:30 +02:00
static constexpr int maxcur = 16 ; //BitcoinDMgr::N_CLIENTS*4; // fixme: tune this
2019-11-18 20:29:21 +02:00
Util : : VoidFunc AGAIN ;
std : : vector < QByteArray > blockHashes ;
2019-11-18 08:29:33 +02:00
} ;
void Controller : : process ( )
{
Debug ( ) < < " Process called... " ;
2019-11-18 20:29:21 +02:00
// TESTING...
2019-11-18 21:09:21 +02:00
// TODO: This doesn't deal at all well with failures. After tuning this, figure out a
// better way to recover from failure and keep retrying, etc. This is just a first
// stab to test communication, really.
2019-11-18 20:29:21 +02:00
using S = StateMachine : : State ;
if ( ! sm ) {
sm = std : : make_unique < StateMachine > ( ) ; // create statemachine if doest not exist
sm - > AGAIN = [ this ] { QTimer : : singleShot ( 0 , this , [ this ] { process ( ) ; } ) ; } ;
}
static const auto BOILERPLATE_ERR = [ ] ( const RPC : : Message & resp ) {
Warning ( ) < < resp . method < < " : error response: " < < resp . toJsonString ( ) ;
} ;
static const auto BOILERPLATE_FAIL = [ ] ( auto id , auto msg ) {
Warning ( ) < < id . toString ( ) < < " : FAIL: " < < msg ;
} ;
if ( sm - > state = = S : : Begin ) {
bitcoindmgr - > submitRequest ( this , IdMixin : : newId ( ) , " getblockcount " , { } , [ this ] ( const RPC : : Message & resp ) { // testing
QVariant var = resp . result ( ) ;
Trace ( ) < < resp . method < < " : result reply: " < < var . toInt ( ) ;
if ( var . canConvert < int > ( ) ) {
2019-11-18 20:48:22 +02:00
int oldVal = sm - > ht ;
const int newVal = var . toInt ( ) ;
if ( newVal ! = oldVal ) {
sm - > ht = newVal ;
sm - > state = S : : GetBlockHashes ;
2019-11-18 21:22:30 +02:00
while ( sm - > cur < sm - > maxcur & & oldVal + + < newVal ) { // fixme: should be ngoodclients
2019-11-18 20:48:22 +02:00
sm - > AGAIN ( ) ;
+ + sm - > cur ;
}
} else {
Log ( ) < < " Headers up to date at height: " < < sm - > ht ;
sm - > state = S : : Finished ;
return ;
2019-11-18 20:29:21 +02:00
}
} else {
Warning ( ) < < resp . method < < " : response not int! " ;
}
} , BOILERPLATE_ERR , BOILERPLATE_FAIL ) ;
} else if ( sm - > state = = S : : GetBlockHashes ) {
2019-11-19 10:44:51 +02:00
if ( sm - > bl > sm - > ht ) {
2019-11-19 10:59:53 +02:00
// it's nonsensical to request block past known height -- todo: find out if this conditional is even needed. ideally it would never happen
2019-11-19 10:44:51 +02:00
return ;
2019-11-18 20:29:21 +02:00
}
unsigned bnum = unsigned ( sm - > bl ) ;
bitcoindmgr - > submitRequest ( this , IdMixin : : newId ( ) , " getblockhash " , { sm - > bl + + } , [ this , bnum ] ( const RPC : : Message & resp ) { // testing
sm - > cur = qMax ( 0 , sm - > cur - 1 ) ; // decrement current q ct
QVariant var = resp . result ( ) ;
auto res = QByteArray : : fromHex ( var . toByteArray ( ) ) ;
2019-11-19 10:44:51 +02:00
if ( res . length ( ) = = bitcoin : : uint256 : : width ( ) ) {
2019-11-18 20:29:21 +02:00
if ( bnum & & ! ( bnum % 1000 ) ) {
Log ( " Processed block %u " , bnum ) ;
}
2019-11-19 12:33:50 +02:00
if ( Trace : : isEnabled ( ) ) Trace ( ) < < resp . method < < " : result for height: " < < bnum < < " len: " < < res . length ( ) ;
2019-11-18 20:29:21 +02:00
if ( sm - > blockHashes . size ( ) < bnum + 1 )
sm - > blockHashes . resize ( bnum + 1 ) ;
sm - > blockHashes [ bnum ] = res ;
2019-11-19 10:59:53 +02:00
while ( sm - > cur < sm - > maxcur & & sm - > bl + sm - > cur < = sm - > ht ) { // fixme: should be ngoodclients? maybe?
// queue up more requests
2019-11-18 20:29:21 +02:00
sm - > AGAIN ( ) ;
+ + sm - > cur ;
}
2019-11-19 10:59:53 +02:00
if ( sm - > bl > sm - > ht & & sm - > cur = = 0 ) {
// we just got the last reply -- flag state to "end" to check headers received
sm - > state = S : : End ;
sm - > AGAIN ( ) ;
}
2019-11-18 20:29:21 +02:00
} else {
Warning ( ) < < resp . method < < " : at height " < < bnum < < " response not valid (decoded size: " < < res . length ( ) < < " ) " ;
}
} , BOILERPLATE_ERR , BOILERPLATE_FAIL ) ;
} else if ( sm - > state = = S : : End ) {
2019-11-19 10:44:51 +02:00
Log ( ) < < " Downloaded " < < sm - > blockHashes . size ( ) < < " headers (for height: " < < sm - > ht < < " ), verifying... " ;
2019-11-18 20:48:22 +02:00
QTimer : : singleShot ( 0 , this , [ this ] {
int bad = 0 ;
for ( const auto & ba : sm - > blockHashes ) {
if ( ba . length ( ) ! = bitcoin : : uint256 : : width ( ) ) {
+ + bad ;
}
}
// TESTING
if ( ! bad )
Log ( ) < < " All headers ok " ;
else
Log ( ) < < bad < < " headers have the wrong length " ;
2019-11-19 10:59:53 +02:00
// trigger one last check for last few headers that may have come in -- this is still for testing.
// ideally these two processes (height check & header dl) will be separated and signal each other.
// on their progress and as information comes in, etc.
2019-11-18 20:48:22 +02:00
sm - > state = S : : Begin ;
sm - > AGAIN ( ) ;
} ) ;
2019-11-18 20:29:21 +02:00
}
/*
2019-11-18 14:32:48 +02:00
bitcoindmgr - > submitRequest ( this , IdMixin : : newId ( ) , " getmempoolinfo " , { } , [ ] ( auto resp ) { // testing
Trace ( ) < < resp . id . toString ( ) < < " : result reply: " < < resp . toJsonString ( ) ;
} , [ ] ( auto resp ) {
Trace ( ) < < resp . id . toString ( ) < < " : error response: " < < resp . toJsonString ( ) ;
} , [ ] ( auto id , auto msg ) {
Warning ( ) < < id . toString ( ) < < " : FAIL: " < < msg ;
} ) ;
2019-11-18 20:29:21 +02:00
*/
2019-11-18 00:21:34 +02:00
}