2018-12-11 13:18:46 +01:00
# include <mainchainrpc.h>
# include <chainparamsbase.h>
2019-05-01 20:51:19 +01:00
# include <util/system.h>
# include <util/strencodings.h>
2020-11-09 21:20:08 +00:00
# include <util/translation.h>
2020-11-09 03:58:20 +00:00
# include <rpc/request.h>
2018-12-11 13:18:46 +01:00
# include <support/events.h>
# include <rpc/client.h>
# include <event2/buffer.h>
# include <event2/keyvalq_struct.h>
/** Reply structure for request_done to fill in */
struct HTTPReply
{
2024-12-04 10:52:04 +02:00
int status { 0 } ;
int error { - 1 } ;
2018-12-11 13:18:46 +01:00
std : : string body ;
} ;
const char * http_errorstring ( int code )
{
switch ( code ) {
# if LIBEVENT_VERSION_NUMBER >= 0x02010300
case EVREQ_HTTP_TIMEOUT :
return " timeout reached " ;
case EVREQ_HTTP_EOF :
return " EOF reached " ;
case EVREQ_HTTP_INVALID_HEADER :
return " error while reading header, or invalid header " ;
case EVREQ_HTTP_BUFFER_ERROR :
return " error encountered while reading or writing " ;
case EVREQ_HTTP_REQUEST_CANCEL :
return " request was canceled " ;
case EVREQ_HTTP_DATA_TOO_LONG :
return " response body is larger than allowed " ;
# endif
default :
return " unknown " ;
}
}
static void http_request_done ( struct evhttp_request * req , void * ctx )
{
HTTPReply * reply = static_cast < HTTPReply * > ( ctx ) ;
2024-12-04 10:52:04 +02:00
if ( req = = nullptr ) {
/* If req is nullptr, it means an error occurred while connecting: the
2018-12-11 13:18:46 +01:00
* error code will have been passed to http_error_cb .
*/
reply - > status = 0 ;
return ;
}
reply - > status = evhttp_request_get_response_code ( req ) ;
struct evbuffer * buf = evhttp_request_get_input_buffer ( req ) ;
if ( buf )
{
size_t size = evbuffer_get_length ( buf ) ;
const char * data = ( const char * ) evbuffer_pullup ( buf , size ) ;
if ( data )
reply - > body = std : : string ( data , size ) ;
evbuffer_drain ( buf , size ) ;
}
}
# if LIBEVENT_VERSION_NUMBER >= 0x02010300
static void http_error_cb ( enum evhttp_request_error err , void * ctx )
{
HTTPReply * reply = static_cast < HTTPReply * > ( ctx ) ;
reply - > error = err ;
}
# endif
UniValue CallMainChainRPC ( const std : : string & strMethod , const UniValue & params )
{
std : : string host = gArgs . GetArg ( " -mainchainrpchost " , DEFAULT_RPCCONNECT ) ;
2023-04-24 12:05:24 +00:00
int port = gArgs . GetIntArg ( " -mainchainrpcport " , BaseParams ( ) . MainchainRPCPort ( ) ) ;
2018-12-11 13:18:46 +01:00
// Obtain event base
raii_event_base base = obtain_event_base ( ) ;
// Synchronously look up hostname
raii_evhttp_connection evcon = obtain_evhttp_connection_base ( base . get ( ) , host , port ) ;
2023-04-24 12:05:24 +00:00
evhttp_connection_set_timeout ( evcon . get ( ) , gArgs . GetIntArg ( " -mainchainrpctimeout " , DEFAULT_HTTP_CLIENT_TIMEOUT ) ) ;
2018-12-11 13:18:46 +01:00
HTTPReply response ;
raii_evhttp_request req = obtain_evhttp_request ( http_request_done , ( void * ) & response ) ;
2024-12-04 10:52:04 +02:00
if ( req = = nullptr )
2018-12-11 13:18:46 +01:00
throw std : : runtime_error ( " create http request failed " ) ;
# if LIBEVENT_VERSION_NUMBER >= 0x02010300
evhttp_request_set_error_cb ( req . get ( ) , http_error_cb ) ;
# endif
// Get credentials
std : : string strRPCUserColonPass ;
if ( gArgs . GetArg ( " -mainchainrpcpassword " , " " ) = = " " ) {
// Try fall back to cookie-based authentication if no password is provided
if ( ! GetMainchainAuthCookie ( & strRPCUserColonPass ) ) {
throw std : : runtime_error ( strprintf (
2020-11-09 21:20:08 +00:00
_ ( " Could not locate mainchain RPC credentials. No authentication cookie could be found, and no mainchainrpcpassword is set in the configuration file (%s) " ) . translated ,
2023-05-11 11:35:30 +00:00
gArgs . GetArg ( " -conf " , BITCOIN_CONF_FILENAME ) . c_str ( ) ) ) ;
2018-12-11 13:18:46 +01:00
}
} else {
strRPCUserColonPass = gArgs . GetArg ( " -mainchainrpcuser " , " " ) + " : " + gArgs . GetArg ( " -mainchainrpcpassword " , " " ) ;
}
struct evkeyvalq * output_headers = evhttp_request_get_output_headers ( req . get ( ) ) ;
assert ( output_headers ) ;
evhttp_add_header ( output_headers , " Host " , host . c_str ( ) ) ;
evhttp_add_header ( output_headers , " Connection " , " close " ) ;
evhttp_add_header ( output_headers , " Authorization " , ( std : : string ( " Basic " ) + EncodeBase64 ( strRPCUserColonPass ) ) . c_str ( ) ) ;
// Attach request data
std : : string strRequest = JSONRPCRequestObj ( strMethod , params , 1 ) . write ( ) + " \n " ;
struct evbuffer * output_buffer = evhttp_request_get_output_buffer ( req . get ( ) ) ;
assert ( output_buffer ) ;
evbuffer_add ( output_buffer , strRequest . data ( ) , strRequest . size ( ) ) ;
int r = evhttp_make_request ( evcon . get ( ) , req . get ( ) , EVHTTP_REQ_POST , " / " ) ;
req . release ( ) ; // ownership moved to evcon in above call
if ( r ! = 0 ) {
throw CConnectionFailed ( " send http request failed " ) ;
}
event_base_dispatch ( base . get ( ) ) ;
if ( response . status = = 0 )
throw CConnectionFailed ( strprintf ( " couldn't connect to server: %s (code %d) \ n ( make sure server is running and you are connecting to the correct RPC port ) " , http_errorstring(response.error), response.error)) ;
else if ( response . status = = HTTP_UNAUTHORIZED )
throw std : : runtime_error ( " incorrect mainchainrpcuser or mainchainrpcpassword (authorization failed) " ) ;
else if ( response . status > = 400 & & response . status ! = HTTP_BAD_REQUEST & & response . status ! = HTTP_NOT_FOUND & & response . status ! = HTTP_INTERNAL_SERVER_ERROR )
throw std : : runtime_error ( strprintf ( " server returned HTTP error %d " , response . status ) ) ;
else if ( response . body . empty ( ) )
throw std : : runtime_error ( " no response from server " ) ;
// Parse reply
UniValue valReply ( UniValue : : VSTR ) ;
if ( ! valReply . read ( response . body ) )
throw std : : runtime_error ( " couldn't parse reply from server " ) ;
const UniValue & reply = valReply . get_obj ( ) ;
if ( reply . empty ( ) )
throw std : : runtime_error ( " expected reply to have result, error and id properties " ) ;
return reply ;
}
bool IsConfirmedBitcoinBlock ( const uint256 & hash , const int nMinConfirmationDepth , const int nbTxs )
{
When validation is waiting for parent chain daemon, "stall".
Currently, if -validatepegin is given, and block validation can't proceed
because the parent chain is not synced, we mark the block invalid and put
it in a queue to be "revalidated" later. Unfortunately, marking a block
invalid has downstream consequences, in particular causing descendant blocks
to be marked invalid, which are not currently fixed by the queue.
Instead, we'll use a different strategy: if the mainchain daemon isn't
sufficiently synced to validate a block, we will "stall" connecting that
block to the chain, and have ActivateBestChain simply keep the tip at the
previous block until we're ready.
We can still download and validate (partly) blocks past this point while
we're waiting. They will be connected once the parent chain daemon catches
up.
2021-09-07 17:01:33 +00:00
LogPrintf ( " Checking for confirmed bitcoin block with hash %s, mindepth %d, nbtxs %d \n " , hash . ToString ( ) . c_str ( ) , nMinConfirmationDepth , nbTxs ) ;
2018-12-11 13:18:46 +01:00
try {
UniValue params ( UniValue : : VARR ) ;
params . push_back ( hash . GetHex ( ) ) ;
UniValue reply = CallMainChainRPC ( " getblockheader " , params ) ;
2021-10-12 12:23:11 -07:00
UniValue errval = find_value ( reply , " error " ) ;
if ( ! errval . isNull ( ) ) {
LogPrintf ( " WARNING: Got error reply from bitcoind getblockheader: %s \n " , errval . write ( ) ) ;
2018-12-11 13:18:46 +01:00
return false ;
When validation is waiting for parent chain daemon, "stall".
Currently, if -validatepegin is given, and block validation can't proceed
because the parent chain is not synced, we mark the block invalid and put
it in a queue to be "revalidated" later. Unfortunately, marking a block
invalid has downstream consequences, in particular causing descendant blocks
to be marked invalid, which are not currently fixed by the queue.
Instead, we'll use a different strategy: if the mainchain daemon isn't
sufficiently synced to validate a block, we will "stall" connecting that
block to the chain, and have ActivateBestChain simply keep the tip at the
previous block until we're ready.
We can still download and validate (partly) blocks past this point while
we're waiting. They will be connected once the parent chain daemon catches
up.
2021-09-07 17:01:33 +00:00
}
2018-12-11 13:18:46 +01:00
UniValue result = find_value ( reply , " result " ) ;
When validation is waiting for parent chain daemon, "stall".
Currently, if -validatepegin is given, and block validation can't proceed
because the parent chain is not synced, we mark the block invalid and put
it in a queue to be "revalidated" later. Unfortunately, marking a block
invalid has downstream consequences, in particular causing descendant blocks
to be marked invalid, which are not currently fixed by the queue.
Instead, we'll use a different strategy: if the mainchain daemon isn't
sufficiently synced to validate a block, we will "stall" connecting that
block to the chain, and have ActivateBestChain simply keep the tip at the
previous block until we're ready.
We can still download and validate (partly) blocks past this point while
we're waiting. They will be connected once the parent chain daemon catches
up.
2021-09-07 17:01:33 +00:00
if ( ! result . isObject ( ) ) {
2021-10-12 12:23:11 -07:00
LogPrintf ( " ERROR: bitcoind getblockheader result was malformed (not object): %s \n " , result . write ( ) ) ;
2018-12-11 13:18:46 +01:00
return false ;
When validation is waiting for parent chain daemon, "stall".
Currently, if -validatepegin is given, and block validation can't proceed
because the parent chain is not synced, we mark the block invalid and put
it in a queue to be "revalidated" later. Unfortunately, marking a block
invalid has downstream consequences, in particular causing descendant blocks
to be marked invalid, which are not currently fixed by the queue.
Instead, we'll use a different strategy: if the mainchain daemon isn't
sufficiently synced to validate a block, we will "stall" connecting that
block to the chain, and have ActivateBestChain simply keep the tip at the
previous block until we're ready.
We can still download and validate (partly) blocks past this point while
we're waiting. They will be connected once the parent chain daemon catches
up.
2021-09-07 17:01:33 +00:00
}
2018-12-11 13:18:46 +01:00
UniValue confirmations = find_value ( result . get_obj ( ) , " confirmations " ) ;
2024-09-06 18:49:50 +00:00
if ( ! confirmations . isNum ( ) | | confirmations . getInt < int64_t > ( ) < nMinConfirmationDepth ) {
2021-10-12 12:23:11 -07:00
LogPrintf ( " Insufficient confirmations (got %s, need at least %d). \n " , confirmations . write ( ) , nMinConfirmationDepth ) ;
2018-12-11 13:18:46 +01:00
return false ;
}
// Only perform extra test if nbTxs has been provided (non-zero).
if ( nbTxs ! = 0 ) {
UniValue nTx = find_value ( result . get_obj ( ) , " nTx " ) ;
2024-09-06 18:49:50 +00:00
if ( ! nTx . isNum ( ) | | nTx . getInt < int64_t > ( ) ! = nbTxs ) {
2021-10-12 12:23:11 -07:00
LogPrintf ( " ERROR: Invalid number of transactions in merkle block for %s (got %s, need exactly %d) \n " ,
hash . GetHex ( ) , nTx . write ( ) , nbTxs ) ;
2018-12-11 13:18:46 +01:00
return false ;
}
}
2023-09-27 12:01:23 +00:00
} catch ( CConnectionFailed & ) {
2021-10-12 12:23:11 -07:00
LogPrintf ( " WARNING: Lost connection to mainchain daemon RPC; will retry. \n " ) ;
2018-12-11 13:18:46 +01:00
return false ;
} catch ( . . . ) {
2021-10-12 12:23:11 -07:00
LogPrintf ( " WARNING: Failure connecting to mainchain daemon RPC; will retry. \n " ) ;
2018-12-11 13:18:46 +01:00
return false ;
}
return true ;
}