From 96034e4da653136264d787552ff9a7c79acc58e1 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sun, 11 Oct 2026 16:41:30 +0000 Subject: [PATCH 1/2] parent_fetch: Make RPC timeout configurable --- src/datum_conf.c | 6 ++ src/datum_conf.h | 1 + src/datum_jsonrpc.c | 18 +++--- src/datum_jsonrpc.h | 1 + src/datum_parent_fetch.c | 2 +- src/datum_parent_fetch_tests.c | 108 ++++++++++++++++++++------------- 6 files changed, 86 insertions(+), 50 deletions(-) diff --git a/src/datum_conf.c b/src/datum_conf.c index 3a890ffc..175daeef 100644 --- a/src/datum_conf.c +++ b/src/datum_conf.c @@ -76,6 +76,8 @@ const T_DATUM_CONFIG_ITEM datum_config_options[] = { { .var_type = DATUM_CONF_STRING, .category = "bitcoind", .name = "rpcurl", .description = "RPC URL for communication with local bitcoind. (GBT Template Source)", .example = "\"http://localhost:8332\"", .required = true, .ptr = datum_config.bitcoind_rpcurl, .max_string_len = sizeof(datum_config.bitcoind_rpcurl) }, + { .var_type = DATUM_CONF_INT, .category = "bitcoind", .name = "parent_fetch_timeout_seconds", .description = "Maximum seconds to wait for a raw parent block RPC response. (1-300)", + .required = false, .ptr = &datum_config.bitcoind_parent_fetch_timeout_seconds, .default_int = 5 }, { .var_type = DATUM_CONF_INT, .category = "bitcoind", .name = "work_update_seconds", .description = "How many seconds between normal work updates? (5-120, 40 suggested)", .required = false, .ptr = &datum_config.bitcoind_work_update_seconds, .default_int = 40 }, { .var_type = DATUM_CONF_BOOL, .category = "bitcoind", .name = "notify_fallback", .description = "Fall back to less efficient methods for new block notifications. Can disable if you use blocknotify.", @@ -641,6 +643,10 @@ int datum_read_config(const char *conffile) { if (datum_config.bitcoind_work_update_seconds > 120) { datum_config.bitcoind_work_update_seconds = 120; } + if (datum_config.bitcoind_parent_fetch_timeout_seconds < 1 || datum_config.bitcoind_parent_fetch_timeout_seconds > 300) { + DLOG_FATAL("Configuration option bitcoind.parent_fetch_timeout_seconds must be between 1 and 300"); + return -1; + } if (datum_config.datum_pool_migration_max_seconds < 0) { DLOG_FATAL("Configuration option datum.migration_max_seconds cannot be negative"); return -1; diff --git a/src/datum_conf.h b/src/datum_conf.h index f230419c..b79712c1 100644 --- a/src/datum_conf.h +++ b/src/datum_conf.h @@ -109,6 +109,7 @@ typedef struct { char bitcoind_rpcuser[128]; char bitcoind_rpcpassword[128]; char bitcoind_rpcurl[256]; + int bitcoind_parent_fetch_timeout_seconds; int bitcoind_work_update_seconds; bool bitcoind_notify_fallback; diff --git a/src/datum_jsonrpc.c b/src/datum_jsonrpc.c index 87ce13cc..5f545300 100644 --- a/src/datum_jsonrpc.c +++ b/src/datum_jsonrpc.c @@ -139,7 +139,7 @@ char *basic_http_call(CURL *curl, const char *url) { return NULL; } -json_t *json_rpc_call_full(CURL *curl, const char *url, const char *userpass, const char *rpc_req, const char *extra_header, long * const http_resp_code_out) { +static json_t *json_rpc_call_full(CURL *curl, const char *url, const char *userpass, const char *rpc_req, const char *extra_header, long * const http_resp_code_out, const long timeout_seconds) { json_t *val, *err_val, *res_val; CURLcode rc; struct data_buffer all_data = { }; @@ -158,8 +158,8 @@ json_t *json_rpc_call_full(CURL *curl, const char *url, const char *userpass, co curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_data_cb); curl_easy_setopt(curl, CURLOPT_READDATA, &upload_data); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err_str); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L); // quick timeout! - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L); // quick timeout! + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout_seconds); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_seconds); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); if (userpass) { @@ -240,7 +240,7 @@ json_t *json_rpc_call_full(CURL *curl, const char *url, const char *userpass, co } json_t *json_rpc_call(CURL *curl, const char *url, const char *userpass, const char *rpc_req) { - return json_rpc_call_full(curl, url, userpass, rpc_req, NULL, NULL); + return json_rpc_call_full(curl, url, userpass, rpc_req, NULL, NULL, 5L); } bool update_rpc_cookie(global_config_t * const cfg) { @@ -265,14 +265,18 @@ void update_rpc_auth(global_config_t * const cfg) { } } -json_t *bitcoind_json_rpc_call(CURL * const curl, global_config_t * const cfg, const char * const rpc_req) { +json_t *bitcoind_json_rpc_call_with_timeout(CURL * const curl, global_config_t * const cfg, const char * const rpc_req, const long timeout_seconds) { long http_resp_code = -1; - json_t *j = json_rpc_call_full(curl, cfg->bitcoind_rpcurl, cfg->bitcoind_rpcuserpass, rpc_req, NULL, &http_resp_code); + json_t *j = json_rpc_call_full(curl, cfg->bitcoind_rpcurl, cfg->bitcoind_rpcuserpass, rpc_req, NULL, &http_resp_code, timeout_seconds); if (j) return j; if (cfg->bitcoind_rpcuser[0]) return NULL; if (http_resp_code != 401) return NULL; // Authentication failure using cookie; reload cookie file and try again if (!update_rpc_cookie(cfg)) return NULL; - return json_rpc_call(curl, cfg->bitcoind_rpcurl, cfg->bitcoind_rpcuserpass, rpc_req); + return json_rpc_call_full(curl, cfg->bitcoind_rpcurl, cfg->bitcoind_rpcuserpass, rpc_req, NULL, NULL, timeout_seconds); +} + +json_t *bitcoind_json_rpc_call(CURL * const curl, global_config_t * const cfg, const char * const rpc_req) { + return bitcoind_json_rpc_call_with_timeout(curl, cfg, rpc_req, 5L); } diff --git a/src/datum_jsonrpc.h b/src/datum_jsonrpc.h index b06fd098..b263e676 100644 --- a/src/datum_jsonrpc.h +++ b/src/datum_jsonrpc.h @@ -68,5 +68,6 @@ char *basic_http_call(CURL *curl, const char *url); bool update_rpc_cookie(global_config_t *cfg); void update_rpc_auth(global_config_t *cfg); json_t *bitcoind_json_rpc_call(CURL *curl, global_config_t *cfg, const char *rpc_req); +json_t *bitcoind_json_rpc_call_with_timeout(CURL *curl, global_config_t *cfg, const char *rpc_req, long timeout_seconds); #endif diff --git a/src/datum_parent_fetch.c b/src/datum_parent_fetch.c index ca924a35..e2c6877e 100644 --- a/src/datum_parent_fetch.c +++ b/src/datum_parent_fetch.c @@ -103,7 +103,7 @@ static uint8_t parent_fetch_get( uint8_t ** const block_out, size_t * const block_size_out) { char *rpc = parent_fetch_rpc_request(request->parent_hash); if (!rpc) return DATUM_PARENT_FETCH_STATUS_RPC_FAILED; - json_t *response = bitcoind_json_rpc_call(curl, &datum_config, rpc); + json_t *response = bitcoind_json_rpc_call_with_timeout(curl, &datum_config, rpc, datum_config.bitcoind_parent_fetch_timeout_seconds); free(rpc); if (!response) return DATUM_PARENT_FETCH_STATUS_UNAVAILABLE; json_t *result = json_object_get(response, "result"); diff --git a/src/datum_parent_fetch_tests.c b/src/datum_parent_fetch_tests.c index a348d3e9..a2e902b6 100644 --- a/src/datum_parent_fetch_tests.c +++ b/src/datum_parent_fetch_tests.c @@ -56,7 +56,7 @@ typedef struct { int listen_fd; - bool saw_getblock; + unsigned getblock_requests; char expected_hash[65]; } T_DATUM_PARENT_FETCH_TEST_SERVER; @@ -80,40 +80,59 @@ static bool parent_test_send_all(int fd, const char *data, size_t len) { static void *parent_test_server(void *arg) { T_DATUM_PARENT_FETCH_TEST_SERVER *server = arg; - const int client = accept(server->listen_fd, NULL, NULL); - if (client < 0) return NULL; - char request[4096] = {0}; - size_t used = 0; - while (used + 1 < sizeof(request)) { - const ssize_t n = recv(client, request + used, - sizeof(request) - used - 1, 0); - if (n <= 0) break; - used += (size_t)n; - request[used] = 0; - char *body = strstr(request, "\r\n\r\n"); - char *length = strstr(request, "Content-Length:"); - if (!body || !length) continue; - unsigned long body_size = 0; - if (sscanf(length, "Content-Length: %lu", &body_size) == 1 && - used >= (size_t)(body + 4 - request) + body_size) break; + for (unsigned request_index = 0; request_index < 2; ++request_index) { + const int client = accept(server->listen_fd, NULL, NULL); + if (client < 0) return NULL; + char request[4096] = {0}; + size_t used = 0; + while (used + 1 < sizeof(request)) { + const ssize_t n = recv(client, request + used, + sizeof(request) - used - 1, 0); + if (n <= 0) break; + used += (size_t)n; + request[used] = 0; + char *body = strstr(request, "\r\n\r\n"); + char *length = strstr(request, "Content-Length:"); + if (!body || !length) continue; + unsigned long body_size = 0; + if (sscanf(length, "Content-Length: %lu", &body_size) == 1 && + used >= (size_t)(body + 4 - request) + body_size) break; + } + if (strstr(request, "\"method\":\"getblock\"") && + strstr(request, server->expected_hash) && strstr(request, ",0]")) { + ++server->getblock_requests; + } + if (request_index == 1) sleep(2); + const char body[] = + "{\"result\":\"aabbcc\",\"error\":null,\"id\":\"datum-parent-fetch\"}"; + char response[512]; + const int response_size = snprintf( + response, sizeof(response), + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n" + "Content-Length: %zu\r\nConnection: close\r\n\r\n%s", + strlen(body), body); + if (response_size > 0 && (size_t)response_size < sizeof(response)) { + parent_test_send_all(client, response, (size_t)response_size); + } + close(client); } - server->saw_getblock = strstr(request, "\"method\":\"getblock\"") && - strstr(request, server->expected_hash) && strstr(request, ",0]"); - const char body[] = - "{\"result\":\"aabbcc\",\"error\":null,\"id\":\"datum-parent-fetch\"}"; - char response[512]; - const int response_size = snprintf( - response, sizeof(response), - "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n" - "Content-Length: %zu\r\nConnection: close\r\n\r\n%s", - strlen(body), body); - if (response_size > 0 && (size_t)response_size < sizeof(response)) { - parent_test_send_all(client, response, (size_t)response_size); - } - close(client); return NULL; } +static bool parent_test_wait_for_reply(unsigned timeout_seconds) { + pthread_mutex_lock(&parent_test_lock); + struct timespec deadline; + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_sec += timeout_seconds; + while (!parent_test_replied) { + if (pthread_cond_timedwait( + &parent_test_cond, &parent_test_lock, &deadline) == ETIMEDOUT) break; + } + const bool replied = parent_test_replied; + pthread_mutex_unlock(&parent_test_lock); + return replied; +} + static void parent_test_reply( uint8_t job_id, uint64_t session_generation, uint8_t status, const uint8_t parent_hash[32], @@ -166,25 +185,30 @@ void datum_parent_fetch_tests(void) { "http://127.0.0.1:%u", ntohs(bound.sin_port)); strcpy(datum_config.bitcoind_rpcuser, "test"); strcpy(datum_config.bitcoind_rpcuserpass, "test:test"); + datum_config.bitcoind_parent_fetch_timeout_seconds = 15; datum_test(!datum_parent_fetch_init(parent_test_reply)); datum_test(datum_parent_fetch_enqueue(2, UINT64_C(0x1122334455667788), parent_hash) == DATUM_PARENT_FETCH_STATUS_QUEUED); - pthread_mutex_lock(&parent_test_lock); - struct timespec deadline; - clock_gettime(CLOCK_REALTIME, &deadline); - deadline.tv_sec += 5; - while (!parent_test_replied) { - if (pthread_cond_timedwait( - &parent_test_cond, &parent_test_lock, &deadline) == ETIMEDOUT) break; - } - pthread_mutex_unlock(&parent_test_lock); - datum_test(parent_test_replied); + datum_test(parent_test_wait_for_reply(5)); datum_test(parent_test_status == DATUM_PARENT_FETCH_STATUS_SUCCESS); datum_test(parent_test_session_generation == UINT64_C(0x1122334455667788)); datum_test(parent_test_block_size == 3); datum_test(!memcmp(parent_test_block, "\xaa\xbb\xcc", 3)); + + pthread_mutex_lock(&parent_test_lock); + parent_test_replied = false; + parent_test_status = 0; + parent_test_session_generation = 0; + parent_test_block_size = 0; + pthread_mutex_unlock(&parent_test_lock); + datum_config.bitcoind_parent_fetch_timeout_seconds = 1; + datum_test(datum_parent_fetch_enqueue(3, UINT64_C(0x8877665544332211), parent_hash) == DATUM_PARENT_FETCH_STATUS_QUEUED); + datum_test(parent_test_wait_for_reply(3)); + datum_test(parent_test_status == DATUM_PARENT_FETCH_STATUS_UNAVAILABLE); + datum_test(parent_test_session_generation == UINT64_C(0x8877665544332211)); + datum_test(parent_test_block_size == 0); pthread_join(server_thread, NULL); - datum_test(server.saw_getblock); + datum_test(server.getblock_requests == 2); close(server.listen_fd); datum_config = saved_config; } From ca4e0316ebd82c607eecb8467cbcfdbdb0215ccb Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sun, 11 Oct 2026 16:41:58 +0000 Subject: [PATCH 2/2] parent_fetch: Increase RPC timeout to 15 seconds by default --- src/datum_conf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datum_conf.c b/src/datum_conf.c index 175daeef..42b43235 100644 --- a/src/datum_conf.c +++ b/src/datum_conf.c @@ -77,7 +77,7 @@ const T_DATUM_CONFIG_ITEM datum_config_options[] = { .example = "\"http://localhost:8332\"", .required = true, .ptr = datum_config.bitcoind_rpcurl, .max_string_len = sizeof(datum_config.bitcoind_rpcurl) }, { .var_type = DATUM_CONF_INT, .category = "bitcoind", .name = "parent_fetch_timeout_seconds", .description = "Maximum seconds to wait for a raw parent block RPC response. (1-300)", - .required = false, .ptr = &datum_config.bitcoind_parent_fetch_timeout_seconds, .default_int = 5 }, + .required = false, .ptr = &datum_config.bitcoind_parent_fetch_timeout_seconds, .default_int = 15 }, { .var_type = DATUM_CONF_INT, .category = "bitcoind", .name = "work_update_seconds", .description = "How many seconds between normal work updates? (5-120, 40 suggested)", .required = false, .ptr = &datum_config.bitcoind_work_update_seconds, .default_int = 40 }, { .var_type = DATUM_CONF_BOOL, .category = "bitcoind", .name = "notify_fallback", .description = "Fall back to less efficient methods for new block notifications. Can disable if you use blocknotify.",