Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-mainchainrpcuser=<user>", "The rpc username that the daemon will use to connect to the trusted mainchain daemon to validate peg-ins, if enabled. (default: cookie auth)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::ELEMENTS);
argsman.AddArg("-mainchainrpcpassword=<pwd>", "The rpc password which the daemon will use to connect to the trusted mainchain daemon to validate peg-ins, if enabled. (default: cookie auth)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::ELEMENTS);
argsman.AddArg("-mainchainrpccookiefile=<file>", "The bitcoind cookie auth path which the daemon will use to connect to the trusted mainchain daemon to validate peg-ins. (default: `<datadir>/regtest/.cookie`)", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS);
argsman.AddArg("-mainchainrpctimeout=<n>", strprintf("Timeout in seconds during mainchain RPC requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS);
argsman.AddArg("-mainchainrpctimeout=<n>", strprintf("Timeout in seconds for mainchain RPC requests. Note that a value of 0 does not disable the timeout: libevent then applies its own internal defaults (45s connect, 50s read/write). Mainchain RPC calls made while validating peg-ins are additionally capped at %d seconds. (default: %d)", MAX_VALIDATION_RPC_TIMEOUT, DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS);
argsman.AddArg("-peginconfirmationdepth=<n>", strprintf("Peg-in claims must be this deep to be considered valid. (default: %d)", DEFAULT_PEGIN_CONFIRMATION_DEPTH), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS);
argsman.AddArg("-parentpubkeyprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 pubkey address. (default: %d)", 111), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-parentscriptprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 script address. (default: %d)", 196), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
Expand Down
20 changes: 17 additions & 3 deletions src/mainchainrpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <event2/buffer.h>
#include <event2/keyvalq_struct.h>

#include <algorithm>

/** Reply structure for request_done to fill in */
struct HTTPReply
{
Expand Down Expand Up @@ -77,7 +79,7 @@ static void http_error_cb(enum evhttp_request_error err, void *ctx)
}
#endif

UniValue CallMainChainRPC(const std::string& strMethod, const UniValue& params)
UniValue CallMainChainRPC(const std::string& strMethod, const UniValue& params, int timeout)
{
std::string host = gArgs.GetArg("-mainchainrpchost", DEFAULT_RPCCONNECT);
int port = gArgs.GetIntArg("-mainchainrpcport", BaseParams().MainchainRPCPort());
Expand All @@ -87,7 +89,10 @@ UniValue CallMainChainRPC(const std::string& strMethod, const UniValue& params)

// Synchronously look up hostname
raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port);
evhttp_connection_set_timeout(evcon.get(), gArgs.GetIntArg("-mainchainrpctimeout", DEFAULT_HTTP_CLIENT_TIMEOUT));
if (timeout < 0) {
timeout = gArgs.GetIntArg("-mainchainrpctimeout", DEFAULT_HTTP_CLIENT_TIMEOUT);
}
evhttp_connection_set_timeout(evcon.get(), timeout);

HTTPReply response;
raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
Expand Down Expand Up @@ -150,13 +155,22 @@ UniValue CallMainChainRPC(const std::string& strMethod, const UniValue& params)
return reply;
}

int GetValidationRPCTimeout(const ArgsManager& argsman)
{
const int64_t timeout = argsman.GetIntArg("-mainchainrpctimeout", DEFAULT_HTTP_CLIENT_TIMEOUT);
return static_cast<int>(std::clamp<int64_t>(timeout, 1, MAX_VALIDATION_RPC_TIMEOUT));
}

bool IsConfirmedBitcoinBlock(const uint256& hash, const int nMinConfirmationDepth, const int nbTxs)
{
LogPrintf("Checking for confirmed bitcoin block with hash %s, mindepth %d, nbtxs %d\n", hash.ToString().c_str(), nMinConfirmationDepth, nbTxs);
try {
UniValue params(UniValue::VARR);
params.push_back(hash.GetHex());
UniValue reply = CallMainChainRPC("getblockheader", params);
// This call is made while holding cs_main during block connection
// and mempool acceptance, and failure is simply retried later, so a
// bounded timeout is used instead of the full -mainchainrpctimeout.
UniValue reply = CallMainChainRPC("getblockheader", params, GetValidationRPCTimeout(gArgs));
const UniValue& errval = reply.find_value("error");
if (!errval.isNull()) {
LogPrintf("WARNING: Got error reply from bitcoind getblockheader: %s\n", errval.write());
Expand Down
18 changes: 17 additions & 1 deletion src/mainchainrpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,16 @@

#include <univalue.h>

class ArgsManager;

static const bool DEFAULT_NAMED=false;
static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
// Hard cap (in seconds) on the timeout of mainchain RPC calls made while
// validating peg-ins. These calls are issued synchronously while cs_main is
// held, so the cap bounds how long an unresponsive mainchain daemon can
// stall block connection and mempool acceptance.
static const int MAX_VALIDATION_RPC_TIMEOUT=30;

//
// Exception thrown on connection error. This error is used to determine
Expand All @@ -33,7 +40,16 @@ class CConnectionFailed : public std::runtime_error

};

UniValue CallMainChainRPC(const std::string& strMethod, const UniValue& params);
// If timeout is negative (the default), the value of -mainchainrpctimeout
// (or DEFAULT_HTTP_CLIENT_TIMEOUT if unset) is used.
UniValue CallMainChainRPC(const std::string& strMethod, const UniValue& params, int timeout = -1);

// Returns the timeout to use for mainchain RPC calls made while validating
// peg-ins: the value of -mainchainrpctimeout clamped into the range
// [1, MAX_VALIDATION_RPC_TIMEOUT]. Note that a -mainchainrpctimeout of 0
// does not disable the timeout (libevent substitutes its own internal
// defaults), so it is clamped like any other out-of-range value.
int GetValidationRPCTimeout(const ArgsManager& argsman);

// Verify if the block with given hash has at least the specified minimum number
// of confirmations.
Expand Down
1 change: 1 addition & 0 deletions src/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ add_executable(test_elements
key_io_tests.cpp
key_tests.cpp
logging_tests.cpp
mainchainrpc_tests.cpp
mempool_tests.cpp
merkle_tests.cpp
merkleblock_tests.cpp
Expand Down
40 changes: 40 additions & 0 deletions src/test/mainchainrpc_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) 2026 The Elements Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <mainchainrpc.h>

#include <common/args.h>
#include <test/util/setup_common.h>

#include <boost/test/unit_test.hpp>
#include <tinyformat.h>

BOOST_FIXTURE_TEST_SUITE(mainchainrpc_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(validation_rpc_timeout)
{
ArgsManager argsman;

// Unset -mainchainrpctimeout: the 900s default is capped
BOOST_CHECK_EQUAL(GetValidationRPCTimeout(argsman), MAX_VALIDATION_RPC_TIMEOUT);

// Values below the cap are honored
argsman.ForceSetArg("-mainchainrpctimeout", "10");
BOOST_CHECK_EQUAL(GetValidationRPCTimeout(argsman), 10);

// Values above the cap are clamped to it
argsman.ForceSetArg("-mainchainrpctimeout", "3600");
BOOST_CHECK_EQUAL(GetValidationRPCTimeout(argsman), MAX_VALIDATION_RPC_TIMEOUT);

// The cap boundary itself is honored
argsman.ForceSetArg("-mainchainrpctimeout", strprintf("%d", MAX_VALIDATION_RPC_TIMEOUT));
BOOST_CHECK_EQUAL(GetValidationRPCTimeout(argsman), MAX_VALIDATION_RPC_TIMEOUT);

// 0 does not mean "no timeout" (libevent substitutes its own internal
// defaults for 0), so it is clamped to the lower bound instead
argsman.ForceSetArg("-mainchainrpctimeout", "0");
BOOST_CHECK_EQUAL(GetValidationRPCTimeout(argsman), 1);
}

BOOST_AUTO_TEST_SUITE_END()
5 changes: 4 additions & 1 deletion src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,10 @@ class MemPoolAccept
params.push_back(txid.GetHex());
params.push_back(2);
params.push_back(blockhash.GetHex());
UniValue result = CallMainChainRPC("getrawtransaction", params);
// This call is made while holding cs_main and mempool.cs
// during mempool acceptance, so use the bounded validation
// timeout rather than the full -mainchainrpctimeout.
UniValue result = CallMainChainRPC("getrawtransaction", params, GetValidationRPCTimeout(gArgs));
if (result["error"].isStr()) {
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "pegin-subsidy-mainchain-error", result["error"]["message"].get_str());
} else {
Expand Down