2019-11-18 14:32:48 +02:00
# include <QPointer>
2019-11-12 22:14:04 +02:00
# include "BitcoinD.h"
BitcoinDMgr : : BitcoinDMgr ( const QHostAddress & host , quint16 port ,
const QString & user , const QString & pass )
: Mgr ( nullptr ) , IdMixin ( newId ( ) ) , host ( host ) , port ( port ) , user ( user ) , pass ( pass )
{
setObjectName ( " BitcoinDMgr " ) ;
2019-11-13 00:57:20 +02:00
_thread . setObjectName ( objectName ( ) ) ;
2019-11-12 22:14:04 +02:00
}
BitcoinDMgr : : ~ BitcoinDMgr ( ) { cleanup ( ) ; }
void BitcoinDMgr : : startup ( ) {
Log ( ) < < objectName ( ) < < " : starting " < < N_CLIENTS < < " bitcoin rpc clients ... " ;
2019-11-13 00:57:20 +02:00
for ( auto & client : clients ) {
client = std : : make_unique < BitcoinD > ( host , port , user , pass ) ;
2019-11-13 01:55:55 +02:00
// connect client to us -- TODO: figure out workflow: how requests for work and results will get dispatched
connect ( client . get ( ) , & BitcoinD : : gotMessage , this , & BitcoinDMgr : : on_Message ) ;
connect ( client . get ( ) , & BitcoinD : : gotErrorMessage , this , & BitcoinDMgr : : on_ErrorMessage ) ;
2019-11-18 09:47:18 +02:00
connect ( client . get ( ) , & BitcoinD : : authenticated , this , [ this ] ( BitcoinD * b ) {
// guard against stale/old signal
if ( ! Util : : CallOnObjectWithTimeoutNoThrow < bool > ( miniTimeout , b , & BitcoinD : : isGood ) . value_or ( false ) ) {
Debug ( ) < < " got authenticated for id: " < < b - > id < < " but isGood() is false! " ;
return ; // false/stale signal
}
2019-11-18 14:32:48 +02:00
const bool wasEmpty = goodSet . empty ( ) ;
goodSet . insert ( b - > id ) ;
2019-11-18 09:47:18 +02:00
if ( wasEmpty )
emit gotFirstGoodConnection ( b - > id ) ;
} ) ;
connect ( client . get ( ) , & BitcoinD : : lostConnection , this , [ this ] ( AbstractConnection * c ) {
// guard against stale/old signal
if ( Util : : CallOnObjectWithTimeoutNoThrow < bool > ( miniTimeout , c , & AbstractConnection : : isGood ) . value_or ( false ) ) {
Debug ( ) < < " got lostConnection for id: " < < c - > id < < " but isGood() is true! " ;
return ; // false/stale signal
}
2019-11-18 14:32:48 +02:00
goodSet . erase ( c - > id ) ;
2019-11-18 09:47:18 +02:00
auto constexpr chkTimer = " checkNoMoreBitcoinDs " ;
// we throttle the spamming of the allConnectionsLost signal via this mechanism
callOnTimerSoonNoRepeat ( miniTimeout , chkTimer , [ this ] {
2019-11-18 14:32:48 +02:00
if ( goodSet . empty ( ) )
2019-11-18 09:47:18 +02:00
emit allConnectionsLost ( ) ;
} , true ) ;
} ) ;
2019-11-13 01:55:55 +02:00
2019-11-13 00:57:20 +02:00
client - > start ( ) ;
}
2019-11-12 22:14:04 +02:00
start ( ) ;
Log ( ) < < objectName ( ) < < " : started ok " ;
}
void BitcoinDMgr : : cleanup ( ) {
stop ( ) ;
for ( auto & client : clients ) {
client . reset ( ) ; /// implicitly calls client->stop()
}
2019-11-18 14:32:48 +02:00
goodSet . clear ( ) ;
2019-11-18 09:47:18 +02:00
2019-11-13 00:57:20 +02:00
Debug ( ) < < " BitcoinDMgr cleaned up " ;
2019-11-12 22:14:04 +02:00
}
2019-11-13 01:52:15 +02:00
void BitcoinDMgr : : on_Message ( quint64 bid , const RPC : : Message & msg )
{
Debug ( ) < < " Msg from: " < < bid < < " method= " < < msg . method ;
}
void BitcoinDMgr : : on_ErrorMessage ( quint64 bid , const RPC : : Message & msg )
{
Debug ( ) < < " ErrMsg from: " < < bid < < " error= " < < msg . errorMessage ( ) ;
}
2019-11-12 22:14:04 +02:00
auto BitcoinDMgr : : stats ( ) const - > Stats
{
Stats ret ;
2019-11-13 00:57:20 +02:00
QVariantList l ;
2019-11-18 09:47:18 +02:00
constexpr int timeout = kDefaultTimeout / qMax ( N_CLIENTS , 1 ) ;
2019-11-13 00:57:20 +02:00
for ( const auto & client : clients ) {
if ( ! client ) continue ;
2019-11-18 00:21:34 +02:00
auto map = client - > statsSafe ( timeout ) ;
2019-11-16 22:27:21 +02:00
auto name = map . take ( " name " ) . toString ( ) ;
l + = QVariantMap ( { { name , map } } ) ;
2019-11-13 00:57:20 +02:00
}
ret [ " Bitcoin Daemon " ] = l ;
2019-11-12 22:14:04 +02:00
return ret ;
}
2019-11-18 14:32:48 +02:00
BitcoinD * BitcoinDMgr : : getBitcoinD ( )
{
BitcoinD * ret = nullptr ;
if ( goodSet . size ( ) < = 1 )
lastBitcoinDUsed = NO_ID ;
if ( ! goodSet . empty ( ) ) {
// linear search for a bitcoind that is not lastBitcoinDUsed
for ( auto & client : clients ) {
if ( goodSet . count ( client - > id ) & & lastBitcoinDUsed ! = client - > id
// fixme here: will tinyTimeout expire easily under load? tune this.
& & Util : : CallOnObjectWithTimeoutNoThrow < bool > ( tinyTimeout , client . get ( ) , & BitcoinD : : isGood ) . value_or ( false ) ) {
ret = client . get ( ) ;
break ;
}
}
}
if ( ret ) lastBitcoinDUsed = ret - > id ;
return ret ;
}
/// this is safe to call from any thread. internally it dispatches messages to this obejct's thread.
/// may throw (TODO list exceptions it may throw). Results/Error/Fail functions are called in the context of the sender's thread.
/// Returns the BitcoinD->id that was given the message.
void BitcoinDMgr : : submitRequest ( QObject * sender , const RPC : : Message : : Id & rid , const QString & method , const QVariantList & params ,
const ResultsF & resf , const ErrorF & errf , const FailF & failf )
{
QPointer < QObject > context ( new QObject ( sender ) ) ; // this is a weak ref that gets killed when sender is killed. This way stuff just "goes away" if sender dies.
context - > setObjectName ( QString ( " context for '%1' request id: %2 " ) . arg ( sender ? sender - > objectName ( ) : " " ) . arg ( rid . toString ( ) ) ) ;
auto killContext = [ context , this ] {
if ( LIKELY ( context ) ) { // need to check context because race conditions
Util : : VoidFuncOnObjectNoThrow ( this , [ context , this ] { if ( LIKELY ( context ) ) disconnect ( this , & QObject : : destroyed , context , nullptr ) ; } , 0 ) ;
Util : : VoidFuncOnObjectNoThrow ( context , [ context ] { if ( LIKELY ( context ) ) context - > deleteLater ( ) ; } , 0 ) ;
}
} ;
connect ( this , & QObject : : destroyed , context , killContext ) ; // make sure that if we die, to kill the context too to clean up resources.
// schedule this ASAP
QTimer : : singleShot ( 0 , this , [ this , context , resf , errf , failf , rid , method , params , killContext ] {
auto replied = std : : make_shared < std : : atomic_bool > ( false ) ; // guards against spurious lostConnection arriving after reply msg
auto do_fail = [ failf , context , killContext , rid , replied ] ( const QString & reason ) {
if ( LIKELY ( failf & & context & & ! replied - > exchange ( true ) ) ) {
Util : : VoidFuncOnObjectNoThrow ( context , [ context , failf , rid , reason ] {
if ( LIKELY ( context ) ) // need to check context again because race conditions
failf ( rid , reason ) ;
} ) ; // fixme: this has an infinite timeout
}
killContext ( ) ;
} ;
auto do_res = [ resf , context , killContext , replied ] ( const RPC : : Message & resp ) {
if ( LIKELY ( resf & & context & & ! replied - > exchange ( true ) ) ) {
Util : : VoidFuncOnObjectNoThrow ( context , [ context , resf , resp ] {
if ( LIKELY ( context ) ) // need to check context again because race conditions
resf ( resp ) ;
} ) ; // fixme: this has an infinite timeout
}
killContext ( ) ;
} ;
auto do_err = [ errf , context , killContext , replied ] ( const RPC : : Message & resp ) {
if ( LIKELY ( errf & & context & & ! replied - > exchange ( true ) ) ) {
Util : : VoidFuncOnObjectNoThrow ( context . data ( ) , [ context , errf , resp ] {
if ( LIKELY ( context ) ) // need to check context again because race conditions
errf ( resp ) ;
} ) ; // fixmne: this has an infinite timeout
}
killContext ( ) ;
} ;
if ( UNLIKELY ( ! context ) )
// sender parent must have been deleted before we got a chance to run
return ;
auto bd = getBitcoinD ( ) ;
if ( UNLIKELY ( ! bd ) ) {
do_fail ( " Unable to find a good BitcoinD connection " ) ;
return ;
}
connect ( bd , & BitcoinD : : gotMessage , context , [ do_res , rid ] ( quint64 , const RPC : : Message & reply ) {
if ( reply . id = = rid ) // filter out messages not for us
do_res ( reply ) ;
} ) ;
connect ( bd , & BitcoinD : : gotErrorMessage , context , [ do_err , rid ] ( quint64 , const RPC : : Message & errMsg ) {
if ( errMsg . id = = rid ) // filter out error messages not for us
do_err ( errMsg ) ;
} ) ;
connect ( bd , & BitcoinD : : lostConnection , context , [ do_fail , rid ] ( AbstractConnection * ) {
do_fail ( " connection lost " ) ;
} ) ;
bd - > sendRequest ( rid , method , params ) ;
} ) ;
// .. aand.. return right away
}
/* --- BitcoinD --- */
2019-11-17 00:18:44 +02:00
auto BitcoinD : : stats ( ) const - > Stats
2019-11-13 00:57:20 +02:00
{
2019-11-17 00:18:44 +02:00
Stats m = RPC : : HttpConnection : : stats ( ) ;
2019-11-13 00:57:20 +02:00
m [ " lastPeerError " ] = badAuth ? " Auth Failure " : lastPeerError ;
2019-11-17 12:38:36 +02:00
m . remove ( " nErrorsSent " ) ; // should always be 0
m . remove ( " nNotificationsSent " ) ; // again, 0
m . remove ( " nResultsSent " ) ; // again, 0
2019-11-16 22:27:21 +02:00
return m ;
2019-11-13 00:57:20 +02:00
}
2019-11-12 22:14:04 +02:00
BitcoinD : : BitcoinD ( const QHostAddress & host , quint16 port , const QString & user , const QString & pass )
: RPC : : HttpConnection ( RPC : : MethodMap { } , newId ( ) , nullptr ) , host ( host ) , port ( port )
{
static int N = 1 ;
2019-11-13 00:57:20 +02:00
setObjectName ( QString ( " BitcoinD.%1 " ) . arg ( N + + ) ) ;
2019-11-12 22:14:04 +02:00
_thread . setObjectName ( objectName ( ) ) ;
setAuth ( user , pass ) ;
setV1 ( true ) ; // bitcoind uses jsonrpc v1
pingtime_ms = 10000 ;
stale_threshold = pingtime_ms * 2 ;
2019-11-16 20:17:18 +02:00
connectMiscSignals ( ) ;
2019-11-12 22:14:04 +02:00
}
BitcoinD : : ~ BitcoinD ( )
{
stop ( ) ;
}
2019-11-16 20:17:18 +02:00
void BitcoinD : : connectMiscSignals ( )
{
connect ( this , & BitcoinD : : gotMessage , this , [ this ] {
// this hook emits "authenticated" as soon as we get a good result message via 'do_ping' initiated from 'on_connected' below
if ( needAuth | | badAuth ) {
needAuth = badAuth = false ;
emit authenticated ( this ) ;
}
} ) ;
}
bool BitcoinD : : isGood ( ) const
{
return ! badAuth & & ! needAuth & & RPC : : HttpConnection : : isGood ( ) ;
}
2019-11-12 22:14:04 +02:00
void BitcoinD : : on_started ( )
{
ThreadObjectMixin : : on_started ( ) ;
2019-11-12 22:42:16 +02:00
2019-11-12 23:19:46 +02:00
{ // setup the "reconnect timer"
2019-11-18 09:47:18 +02:00
constexpr auto reconnectTimer = " reconnectTimer " ;
2019-11-12 22:42:16 +02:00
const auto SetTimer = [ this ] {
2019-11-16 20:17:18 +02:00
callOnTimerSoon ( 5000 , reconnectTimer , [ this ] {
2019-11-12 22:42:16 +02:00
if ( ! isGood ( ) ) {
Debug ( ) < < prettyName ( ) < < " reconnecting... " ;
reconnect ( ) ;
2019-11-16 20:17:18 +02:00
return true ; // keep the timer alive
2019-11-12 22:42:16 +02:00
}
2019-11-16 20:17:18 +02:00
return false ; // kill timer
2019-11-12 22:42:16 +02:00
} ) ;
} ;
2019-11-12 23:19:46 +02:00
conns + = connect ( this , & BitcoinD : : lostConnection , this , [ SetTimer ] {
2019-11-12 22:42:16 +02:00
Log ( ) < < " Lost connection to bitcoind, will retry every 5 seconds ... " ;
SetTimer ( ) ;
} ) ;
2019-11-13 00:57:20 +02:00
conns + = connect ( this , & BitcoinD : : authFailure , this , [ SetTimer , this ] {
2019-11-12 22:42:16 +02:00
Error ( ) < < " Authentication to bitcoind rpc failed. Please check the rpcuser and rpcpass are correct and restart! " ;
2019-11-13 00:57:20 +02:00
badAuth = true ;
2019-11-12 22:42:16 +02:00
SetTimer ( ) ;
} ) ;
2019-11-16 20:17:18 +02:00
conns + = connect ( this , & BitcoinD : : authenticated , this , [ this ] { stopTimer ( reconnectTimer ) ; } ) ;
2019-11-12 22:42:16 +02:00
SetTimer ( ) ;
}
2019-11-12 22:14:04 +02:00
reconnect ( ) ;
}
void BitcoinD : : reconnect ( )
{
if ( socket ) delete socket ;
socket = new QTcpSocket ( this ) ;
socketConnectSignals ( ) ;
socket - > connectToHost ( host , port ) ;
}
void BitcoinD : : on_connected ( )
{
RPC : : HttpConnection : : on_connected ( ) ;
lastGood = Util : : getTime ( ) ;
2019-11-13 00:57:20 +02:00
nSent = nReceived = 0 ;
lastPeerError . clear ( ) ;
lastSocketError . clear ( ) ;
badAuth = false ;
2019-11-16 20:17:18 +02:00
needAuth = true ;
2019-11-12 22:14:04 +02:00
emit connected ( this ) ;
2019-11-16 20:17:18 +02:00
// note that the 'authenticated' signal is only emitted after good auth is confirmed via the reply from the do_ping below
2019-11-12 22:14:04 +02:00
do_ping ( ) ;
}
void BitcoinD : : do_ping ( )
{
if ( isStale ( ) ) {
Debug ( ) < < " Stale connection, reconnecting. " ;
reconnect ( ) ;
} else
2019-11-18 14:32:48 +02:00
emit sendRequest ( newId ( ) , " ping " ) ;
2019-11-12 22:14:04 +02:00
}