diff --git a/lightningd/bitcoind.c b/lightningd/bitcoind.c index a7e9b876d77b..fdeb56c3dfc7 100644 --- a/lightningd/bitcoind.c +++ b/lightningd/bitcoind.c @@ -111,6 +111,25 @@ static void bitcoin_plugin_error(struct bitcoind *bitcoind, const char *buf, toks->end - toks->start, buf + toks->start); } +/* Check if response is an error and fail with clear message if so */ +static void check_bitcoin_error(struct bitcoind *bitcoind, const char *buf, + const jsmntok_t *toks, const char *method) +{ + const jsmntok_t *err_tok, *msg_tok; + + err_tok = json_get_member(buf, toks, "error"); + if (!err_tok) + return; + + msg_tok = json_get_member(buf, err_tok, "message"); + if (msg_tok) + fatal("%s: %.*s", method, + json_tok_full_len(msg_tok), json_tok_full(buf, msg_tok)); + else + fatal("%s: %.*s", method, + json_tok_full_len(err_tok), json_tok_full(buf, err_tok)); +} + /* Send a request to the Bitcoin plugin which registered that method, * if it's still alive. */ static void bitcoin_plugin_send(struct bitcoind *bitcoind, @@ -287,6 +306,8 @@ static void estimatefees_callback(const char *buf, const jsmntok_t *toks, struct feerate_est *feerates; u32 floor; + check_bitcoin_error(call->bitcoind, buf, toks, "estimatefees"); + resulttok = json_get_member(buf, toks, "result"); if (!resulttok) bitcoin_plugin_error(call->bitcoind, buf, toks, @@ -390,6 +411,8 @@ static void sendrawtx_callback(const char *buf, const jsmntok_t *toks, const char *errmsg = NULL; bool success = false; + check_bitcoin_error(call->bitcoind, buf, toks, "sendrawtransaction"); + err = json_scan(tmpctx, buf, toks, "{result:{success:%}}", JSON_SCAN(json_to_bool, &success)); if (err) { @@ -472,6 +495,8 @@ getrawblockbyheight_callback(const char *buf, const jsmntok_t *toks, trace_span_resume(call); trace_span_end(call); + check_bitcoin_error(call->bitcoind, buf, toks, "getrawblockbyheight"); + /* Callback may free parent of call, so steal onto context to * free if it doesn't */ ctx = tal(NULL, char); @@ -569,6 +594,8 @@ static void getchaininfo_callback(const char *buf, const jsmntok_t *toks, u32 headers, blocks; bool ibd; + check_bitcoin_error(call->bitcoind, buf, toks, "getchaininfo"); + err = json_scan(tmpctx, buf, toks, "{result:{chain:%,headercount:%,blockcount:%,ibd:%}}", JSON_SCAN_TAL(tmpctx, json_strdup, &chain), @@ -642,6 +669,8 @@ static void getutxout_callback(const char *buf, const jsmntok_t *toks, /* Whatever happens, we want to free this. */ tal_steal(tmpctx, call); + check_bitcoin_error(call->bitcoind, buf, toks, "getutxout"); + err = json_scan(tmpctx, buf, toks, "{result:{script:null}}"); if (!err) { call->cb(call->bitcoind, NULL, call->cb_arg); diff --git a/plugins/bcli.c b/plugins/bcli.c index cb99763e724a..b914714d867c 100644 --- a/plugins/bcli.c +++ b/plugins/bcli.c @@ -1,7 +1,6 @@ #include "config.h" #include #include -#include #include #include #include @@ -12,21 +11,10 @@ #include #include #include +#include -/* Bitcoind's web server has a default of 4 threads, with queue depth 16. - * It will *fail* rather than queue beyond that, so we must not stress it! - * - * This is how many request for each priority level we have. - */ -#define BITCOIND_MAX_PARALLEL 4 #define RPC_TRANSACTION_ALREADY_IN_CHAIN -27 -enum bitcoind_prio { - BITCOIND_LOW_PRIO, - BITCOIND_HIGH_PRIO -}; -#define BITCOIND_NUM_PRIO (BITCOIND_HIGH_PRIO+1) - struct bitcoind { /* eg. "bitcoin-cli" */ char *cli; @@ -37,22 +25,6 @@ struct bitcoind { /* bitcoind's version, used for compatibility checks. */ u32 version; - /* Is bitcoind synced? If not, we retry. */ - bool synced; - - /* How many high/low prio requests are we running (it's ratelimited) */ - size_t num_requests[BITCOIND_NUM_PRIO]; - - /* Pending requests (high and low prio). */ - struct list_head pending[BITCOIND_NUM_PRIO]; - - /* In flight requests (in a list for memleak detection) */ - struct list_head current; - - /* If non-zero, time we first hit a bitcoind error. */ - unsigned int error_count; - struct timemono first_error_time; - /* How long to keep trying to contact bitcoind * before fatally exiting. */ u64 retry_timeout; @@ -73,22 +45,13 @@ struct bitcoind { static struct bitcoind *bitcoind; -struct bitcoin_cli { - struct list_node list; - int fd; - int *exitstatus; - pid_t pid; - const char **args; - const char **stdinargs; - struct timemono start; - enum bitcoind_prio prio; +/* Result of a synchronous bitcoin-cli call */ +struct bcli_result { char *output; - size_t output_bytes; - size_t new_output; - struct command_result *(*process)(struct bitcoin_cli *); - struct command *cmd; - /* Used to stash content between multiple calls */ - void *stash; + size_t output_len; + int exitstatus; + /* Command args string for error messages */ + const char *args; }; /* Add the n'th arg to *args, incrementing n and keeping args of size n+1 */ @@ -162,25 +125,6 @@ gather_args(const tal_t *ctx, const char ***stdinargs, const char *cmd, ...) return ret; } -static struct io_plan *read_more(struct io_conn *conn, struct bitcoin_cli *bcli) -{ - bcli->output_bytes += bcli->new_output; - if (bcli->output_bytes == tal_count(bcli->output)) - tal_resize(&bcli->output, bcli->output_bytes * 2); - return io_read_partial(conn, bcli->output + bcli->output_bytes, - tal_count(bcli->output) - bcli->output_bytes, - &bcli->new_output, read_more, bcli); -} - -static struct io_plan *output_init(struct io_conn *conn, struct bitcoin_cli *bcli) -{ - bcli->output_bytes = bcli->new_output = 0; - bcli->output = tal_arr(bcli, char, 100); - return read_more(conn, bcli); -} - -static void next_bcli(enum bitcoind_prio prio); - /* For printing: simple string of args (no secrets!) */ static char *args_string(const tal_t *ctx, const char **args, const char **stdinargs) { @@ -204,200 +148,76 @@ static char *args_string(const tal_t *ctx, const char **args, const char **stdin return ret; } -static char *bcli_args(const tal_t *ctx, struct bitcoin_cli *bcli) -{ - return args_string(ctx, bcli->args, bcli->stdinargs); -} - -/* Only set as destructor once bcli is in current. */ -static void destroy_bcli(struct bitcoin_cli *bcli) -{ - list_del_from(&bitcoind->current, &bcli->list); -} - -static struct command_result *retry_bcli(struct command *cmd, - struct bitcoin_cli *bcli) -{ - list_del_from(&bitcoind->current, &bcli->list); - tal_del_destructor(bcli, destroy_bcli); - - list_add_tail(&bitcoind->pending[bcli->prio], &bcli->list); - tal_free(bcli->output); - next_bcli(bcli->prio); - return timer_complete(cmd); -} - -/* We allow 60 seconds of spurious errors, eg. reorg. */ -static void bcli_failure(struct bitcoin_cli *bcli, - int exitstatus) -{ - struct timerel t; - - if (!bitcoind->error_count) - bitcoind->first_error_time = time_mono(); - - t = timemono_between(time_mono(), bitcoind->first_error_time); - if (time_greater(t, time_from_sec(bitcoind->retry_timeout))) - plugin_err(bcli->cmd->plugin, - "%s exited %u (after %u other errors) '%.*s'; " - "we have been retrying command for " - "--bitcoin-retry-timeout=%"PRIu64" seconds; " - "bitcoind setup or our --bitcoin-* configs broken?", - bcli_args(tmpctx, bcli), - exitstatus, - bitcoind->error_count, - (int)bcli->output_bytes, - bcli->output, - bitcoind->retry_timeout); - - plugin_log(bcli->cmd->plugin, LOG_UNUSUAL, "%s exited with status %u", - bcli_args(tmpctx, bcli), exitstatus); - bitcoind->error_count++; - - /* Retry in 1 second */ - command_timer(bcli->cmd, time_from_sec(1), retry_bcli, bcli); -} - -static void bcli_finished(struct io_conn *conn UNUSED, struct bitcoin_cli *bcli) -{ - int ret, status; - struct command_result *res; - enum bitcoind_prio prio = bcli->prio; - u64 msec = time_to_msec(timemono_between(time_mono(), bcli->start)); - - /* If it took over 10 seconds, that's rather strange. */ - if (msec > 10000) - plugin_log(bcli->cmd->plugin, LOG_UNUSUAL, - "bitcoin-cli: finished %s (%"PRIu64" ms)", - bcli_args(tmpctx, bcli), msec); - - assert(bitcoind->num_requests[prio] > 0); - - /* FIXME: If we waited for SIGCHILD, this could never hang! */ - while ((ret = waitpid(bcli->pid, &status, 0)) < 0 && errno == EINTR); - if (ret != bcli->pid) - plugin_err(bcli->cmd->plugin, "%s %s", bcli_args(tmpctx, bcli), - ret == 0 ? "not exited?" : strerror(errno)); - - if (!WIFEXITED(status)) - plugin_err(bcli->cmd->plugin, "%s died with signal %i", - bcli_args(tmpctx, bcli), - WTERMSIG(status)); - - /* Implicit nonzero_exit_ok == false */ - if (!bcli->exitstatus) { - if (WEXITSTATUS(status) != 0) { - bcli_failure(bcli, WEXITSTATUS(status)); - bitcoind->num_requests[prio]--; - goto done; - } - } else - *bcli->exitstatus = WEXITSTATUS(status); - - if (WEXITSTATUS(status) == 0) - bitcoind->error_count = 0; - - bitcoind->num_requests[bcli->prio]--; - - res = bcli->process(bcli); - if (!res) - bcli_failure(bcli, WEXITSTATUS(status)); - else - tal_free(bcli); - -done: - next_bcli(prio); -} - -static void next_bcli(enum bitcoind_prio prio) +/* Synchronous execution of bitcoin-cli. + * Returns result with output and exit status. */ +static struct bcli_result * +run_bitcoin_cliv(const tal_t *ctx, + struct plugin *plugin, + const char *method, + va_list ap) { - struct bitcoin_cli *bcli; - struct io_conn *conn; - int in; - - if (bitcoind->num_requests[prio] >= BITCOIND_MAX_PARALLEL) - return; - - bcli = list_pop(&bitcoind->pending[prio], struct bitcoin_cli, list); - if (!bcli) - return; + int in, from, status; + pid_t child; + const char **stdinargs; + const char **cmd; + struct bcli_result *res; - bcli->pid = pipecmdarr(&in, &bcli->fd, &bcli->fd, - cast_const2(char **, bcli->args)); - if (bcli->pid < 0) - plugin_err(bcli->cmd->plugin, "%s exec failed: %s", - bcli->args[0], strerror(errno)); + stdinargs = tal_arr(ctx, const char *, 0); + cmd = gather_argsv(ctx, &stdinargs, method, ap); + child = pipecmdarr(&in, &from, &from, cast_const2(char **, cmd)); + if (child < 0) + plugin_err(plugin, "%s exec failed: %s", cmd[0], strerror(errno)); + /* Send rpcpass via stdin if configured */ if (bitcoind->rpcpass) { write_all(in, bitcoind->rpcpass, strlen(bitcoind->rpcpass)); - write_all(in, "\n", strlen("\n")); + write_all(in, "\n", 1); } - for (size_t i = 0; i < tal_count(bcli->stdinargs); i++) { - write_all(in, bcli->stdinargs[i], strlen(bcli->stdinargs[i])); - write_all(in, "\n", strlen("\n")); + /* Send any additional stdin args */ + for (size_t i = 0; i < tal_count(stdinargs); i++) { + write_all(in, stdinargs[i], strlen(stdinargs[i])); + write_all(in, "\n", 1); } close(in); - bcli->start = time_mono(); - - bitcoind->num_requests[prio]++; - - /* We don't keep a pointer to this, but it's not a leak */ - conn = notleak(io_new_conn(bcli, bcli->fd, output_init, bcli)); - io_set_finish(conn, bcli_finished, bcli); + /* Read all output until EOF */ + res = tal(ctx, struct bcli_result); + res->output = grab_fd_str(res, from); + res->output_len = strlen(res->output); + res->args = args_string(res, cmd, stdinargs); + close(from); - list_add_tail(&bitcoind->current, &bcli->list); - tal_add_destructor(bcli, destroy_bcli); -} - -static void -start_bitcoin_cliv(const tal_t *ctx, - struct command *cmd, - struct command_result *(*process)(struct bitcoin_cli *), - bool nonzero_exit_ok, - enum bitcoind_prio prio, - void *stash, - const char *method, - va_list ap) -{ - struct bitcoin_cli *bcli = tal(bitcoind, struct bitcoin_cli); - - bcli->process = process; - bcli->cmd = cmd; - bcli->prio = prio; + /* Wait for child to exit */ + while (waitpid(child, &status, 0) < 0) { + if (errno == EINTR) + continue; + plugin_err(plugin, "waitpid(%s) failed: %s", + res->args, strerror(errno)); + } - if (nonzero_exit_ok) - bcli->exitstatus = tal(bcli, int); - else - bcli->exitstatus = NULL; + if (!WIFEXITED(status)) + plugin_err(plugin, "%s died with signal %i", + res->args, WTERMSIG(status)); - bcli->stdinargs = tal_arr(bcli, const char *, 0); - bcli->args = gather_argsv(bcli, &bcli->stdinargs, method, ap); - bcli->stash = stash; + res->exitstatus = WEXITSTATUS(status); - list_add_tail(&bitcoind->pending[bcli->prio], &bcli->list); - next_bcli(bcli->prio); + return res; } -/* If ctx is non-NULL, and is freed before we return, we don't call process(). - * process returns false() if it's a spurious error, and we should retry. */ -static void LAST_ARG_NULL -start_bitcoin_cli(const tal_t *ctx, - struct command *cmd, - struct command_result *(*process)(struct bitcoin_cli *), - bool nonzero_exit_ok, - enum bitcoind_prio prio, - void *stash, - const char *method, - ...) +static LAST_ARG_NULL struct bcli_result * +run_bitcoin_cli(const tal_t *ctx, + struct plugin *plugin, + const char *method, ...) { va_list ap; + struct bcli_result *res; va_start(ap, method); - start_bitcoin_cliv(ctx, cmd, process, nonzero_exit_ok, prio, stash, method, - ap); + res = run_bitcoin_cliv(ctx, plugin, method, ap); va_end(ap); + + return res; } static void strip_trailing_whitespace(char *str, size_t len) @@ -409,13 +229,13 @@ static void strip_trailing_whitespace(char *str, size_t len) str[stripped_len] = 0x00; } -static struct command_result *command_err_bcli_badjson(struct bitcoin_cli *bcli, - const char *errmsg) +static struct command_result *command_err(struct command *cmd, + struct bcli_result *res, + const char *errmsg) { - char *err = tal_fmt(bcli, "%s: bad JSON: %s (%.*s)", - bcli_args(tmpctx, bcli), errmsg, - (int)bcli->output_bytes, bcli->output); - return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); + char *err = tal_fmt(cmd, "%s: %s (%.*s)", + res->args, errmsg, (int)res->output_len, res->output); + return command_done_err(cmd, BCLI_ERROR, err, NULL); } /* Don't use this in general: it's better to omit fields. */ @@ -424,80 +244,6 @@ static void json_add_null(struct json_stream *stream, const char *fieldname) json_add_primitive(stream, fieldname, "null"); } -static struct command_result *process_getutxout(struct bitcoin_cli *bcli) -{ - const jsmntok_t *tokens; - struct json_stream *response; - struct bitcoin_tx_output output; - const char *err; - - /* As of at least v0.15.1.0, bitcoind returns "success" but an empty - string on a spent txout. */ - if (*bcli->exitstatus != 0 || bcli->output_bytes == 0) { - response = jsonrpc_stream_success(bcli->cmd); - json_add_null(response, "amount"); - json_add_null(response, "script"); - - return command_finished(bcli->cmd, response); - } - - tokens = json_parse_simple(bcli->output, bcli->output, - bcli->output_bytes); - if (!tokens) { - return command_err_bcli_badjson(bcli, "cannot parse"); - } - - err = json_scan(tmpctx, bcli->output, tokens, - "{value:%,scriptPubKey:{hex:%}}", - JSON_SCAN(json_to_bitcoin_amount, - &output.amount.satoshis), /* Raw: bitcoind */ - JSON_SCAN_TAL(bcli, json_tok_bin_from_hex, - &output.script)); - if (err) - return command_err_bcli_badjson(bcli, err); - - response = jsonrpc_stream_success(bcli->cmd); - json_add_sats(response, "amount", output.amount); - json_add_string(response, "script", tal_hex(response, output.script)); - - return command_finished(bcli->cmd, response); -} - -static struct command_result *process_getblockchaininfo(struct bitcoin_cli *bcli) -{ - const jsmntok_t *tokens; - struct json_stream *response; - bool ibd; - u32 headers, blocks; - const char *chain, *err; - - tokens = json_parse_simple(bcli->output, - bcli->output, bcli->output_bytes); - if (!tokens) { - return command_err_bcli_badjson(bcli, "cannot parse"); - } - - err = json_scan(tmpctx, bcli->output, tokens, - "{chain:%,headers:%,blocks:%,initialblockdownload:%}", - JSON_SCAN_TAL(tmpctx, json_strdup, &chain), - JSON_SCAN(json_to_number, &headers), - JSON_SCAN(json_to_number, &blocks), - JSON_SCAN(json_to_bool, &ibd)); - if (err) - return command_err_bcli_badjson(bcli, err); - - if (bitcoind->dev_ignore_ibd) - ibd = false; - - response = jsonrpc_stream_success(bcli->cmd); - json_add_string(response, "chain", chain); - json_add_u32(response, "headercount", headers); - json_add_u32(response, "blockcount", blocks); - json_add_bool(response, "ibd", ibd); - - return command_finished(bcli->cmd, response); -} - struct estimatefee_params { u32 blocks; const char *style; @@ -510,281 +256,67 @@ static const struct estimatefee_params estimatefee_params[] = { { 100, "ECONOMICAL" }, }; -struct estimatefees_stash { - /* This is max(mempoolminfee,minrelaytxfee) */ - u64 perkb_floor; - u32 cursor; - /* FIXME: We use u64 but lightningd will store them as u32. */ - u64 perkb[ARRAY_SIZE(estimatefee_params)]; -}; - static struct command_result * -estimatefees_null_response(struct bitcoin_cli *bcli) +estimatefees_null_response(struct command *cmd) { - struct json_stream *response = jsonrpc_stream_success(bcli->cmd); + struct json_stream *response = jsonrpc_stream_success(cmd); /* We give a floor, which is the standard minimum */ json_array_start(response, "feerates"); json_array_end(response); json_add_u32(response, "feerate_floor", 1000); - return command_finished(bcli->cmd, response); + return command_finished(cmd, response); } static struct command_result * -estimatefees_parse_feerate(struct bitcoin_cli *bcli, u64 *feerate) -{ - const jsmntok_t *tokens; - - tokens = json_parse_simple(bcli->output, - bcli->output, bcli->output_bytes); - if (!tokens) { - return command_err_bcli_badjson(bcli, "cannot parse"); - } - - if (json_scan(tmpctx, bcli->output, tokens, "{feerate:%}", - JSON_SCAN(json_to_bitcoin_amount, feerate)) != NULL) { - /* Paranoia: if it had a feerate, but was malformed: */ - if (json_get_member(bcli->output, tokens, "feerate")) - return command_err_bcli_badjson(bcli, "cannot scan"); - /* Regtest fee estimation is generally awful: Fake it at min. */ - if (bitcoind->fake_fees) { - *feerate = 1000; - return NULL; - } - /* We return null if estimation failed, and bitcoin-cli will - * exit with 0 but no feerate field on failure. */ - return estimatefees_null_response(bcli); - } - - return NULL; -} - -static struct command_result *process_sendrawtransaction(struct bitcoin_cli *bcli) +getrawblockbyheight_notfound(struct command *cmd) { struct json_stream *response; - /* This is useful for functional tests. */ - if (bcli->exitstatus) - plugin_log(bcli->cmd->plugin, LOG_DBG, - "sendrawtx exit %i (%s) %.*s", - *bcli->exitstatus, bcli_args(tmpctx, bcli), - *bcli->exitstatus ? - (u32)bcli->output_bytes-1 : 0, - bcli->output); - - response = jsonrpc_stream_success(bcli->cmd); - json_add_bool(response, "success", - *bcli->exitstatus == 0 || - *bcli->exitstatus == - RPC_TRANSACTION_ALREADY_IN_CHAIN); - json_add_string(response, "errmsg", - *bcli->exitstatus ? - tal_strndup(bcli->cmd, - bcli->output, bcli->output_bytes-1) - : ""); - - return command_finished(bcli->cmd, response); -} - -struct getrawblock_stash { - const char *block_hash; - u32 block_height; - const char *block_hex; - int *peers; -}; - -/* Mutual recursion. */ -static struct command_result *getrawblock(struct bitcoin_cli *bcli); - -static struct command_result *process_rawblock(struct bitcoin_cli *bcli) -{ - struct json_stream *response; - struct getrawblock_stash *stash = bcli->stash; - - strip_trailing_whitespace(bcli->output, bcli->output_bytes); - stash->block_hex = tal_steal(stash, bcli->output); - - response = jsonrpc_stream_success(bcli->cmd); - json_add_string(response, "blockhash", stash->block_hash); - json_add_string(response, "block", stash->block_hex); - - return command_finished(bcli->cmd, response); -} - -static struct command_result *process_getblockfrompeer(struct bitcoin_cli *bcli) -{ - /* Remove the peer that we tried to get the block from and move along, - * we may also check on errors here */ - struct getrawblock_stash *stash = bcli->stash; - - if (bcli->exitstatus && *bcli->exitstatus != 0) { - /* We still continue with the execution if we can not fetch the - * block from peer */ - plugin_log(bcli->cmd->plugin, LOG_DBG, - "failed to fetch block %s from peer %i, skip.", - stash->block_hash, stash->peers[tal_count(stash->peers) - 1]); - } else { - plugin_log(bcli->cmd->plugin, LOG_DBG, - "try to fetch block %s from peer %i.", - stash->block_hash, stash->peers[tal_count(stash->peers) - 1]); - } - tal_resize(&stash->peers, tal_count(stash->peers) - 1); - - /* `getblockfrompeer` is an async call. sleep for a second to allow the - * block to be delivered by the peer. fixme: We could also sleep for - * double the last ping here (with sanity limit)*/ - sleep(1); + response = jsonrpc_stream_success(cmd); + json_add_null(response, "blockhash"); + json_add_null(response, "block"); - return getrawblock(bcli); + return command_finished(cmd, response); } -static struct command_result *process_getpeerinfo(struct bitcoin_cli *bcli) +/* Get peers that support NODE_NETWORK (full nodes). + * Returns array of peer ids, or empty array if none found. */ +static int *get_fullnode_peers(const tal_t *ctx, struct command *cmd) { + struct bcli_result *res; const jsmntok_t *t, *toks; - struct getrawblock_stash *stash = bcli->stash; + int *peers = tal_arr(ctx, int, 0); size_t i; - toks = - json_parse_simple(bcli->output, bcli->output, bcli->output_bytes); + res = run_bitcoin_cli(cmd, cmd->plugin, "getpeerinfo", NULL); + if (res->exitstatus != 0) + return peers; - if (!toks) { - return command_err_bcli_badjson(bcli, "cannot parse"); - } + toks = json_parse_simple(res->output, res->output, res->output_len); + if (!toks) + return peers; - stash->peers = tal_arr(bcli->stash, int, 0); - - json_for_each_arr(i, t, toks) - { + json_for_each_arr(i, t, toks) { int id; u8 *services; - if (json_scan(tmpctx, bcli->output, t, "{id:%,services:%}", + if (json_scan(tmpctx, res->output, t, "{id:%,services:%}", JSON_SCAN(json_to_int, &id), JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &services)) == NULL) { /* From bitcoin source: - * // NODE_NETWORK means that the node is capable of serving the complete block chain. It is currently - * // set by all Bitcoin Core non pruned nodes, and is unset by SPV clients or other light clients. + * NODE_NETWORK means that the node is capable of serving the complete block chain. + * It is currently set by all Bitcoin Core non pruned nodes, and is unset by SPV + * clients or other light clients. * NODE_NETWORK = (1 << 0) */ - if (tal_count(services) > 0 && (services[tal_count(services)-1] & (1<<0))) { - // fixme: future optimization: sort by last ping - tal_arr_expand(&stash->peers, id); - } + if (tal_count(services) > 0 && (services[tal_count(services)-1] & (1 << 0))) + tal_arr_expand(&peers, id); } } - if (tal_count(stash->peers) <= 0) { - /* We don't have peers yet, retry from `getrawblock` */ - plugin_log(bcli->cmd->plugin, LOG_DBG, - "got an empty peer list."); - return getrawblock(bcli); - } - - start_bitcoin_cli(NULL, bcli->cmd, process_getblockfrompeer, true, - BITCOIND_HIGH_PRIO, stash, "getblockfrompeer", - stash->block_hash, - take(tal_fmt(NULL, "%i", stash->peers[tal_count(stash->peers) - 1])), NULL); - - return command_still_pending(bcli->cmd); -} - -static struct command_result *process_getrawblock(struct bitcoin_cli *bcli) -{ - /* We failed to get the raw block. */ - if (bcli->exitstatus && *bcli->exitstatus != 0) { - struct getrawblock_stash *stash = bcli->stash; - - plugin_log(bcli->cmd->plugin, LOG_DBG, - "failed to fetch block %s from the bitcoin backend (maybe pruned).", - stash->block_hash); - - if (bitcoind->version >= 230000) { - /* `getblockformpeer` was introduced in v23.0.0 */ - - if (!stash->peers) { - /* We don't have peers to fetch blocks from, get - * some! */ - start_bitcoin_cli(NULL, bcli->cmd, - process_getpeerinfo, true, - BITCOIND_HIGH_PRIO, stash, - "getpeerinfo", NULL); - - return command_still_pending(bcli->cmd); - } - - if (tal_count(stash->peers) > 0) { - /* We have peers left that we can ask for the - * block */ - start_bitcoin_cli( - NULL, bcli->cmd, process_getblockfrompeer, - true, BITCOIND_HIGH_PRIO, stash, - "getblockfrompeer", stash->block_hash, - take(tal_fmt(NULL, "%i", stash->peers[tal_count(stash->peers) - 1])), - NULL); - - return command_still_pending(bcli->cmd); - } - - /* We failed to fetch the block from from any peer we - * got. */ - plugin_log( - bcli->cmd->plugin, LOG_DBG, - "asked all known peers about block %s, retry", - stash->block_hash); - stash->peers = tal_free(stash->peers); - } - - return NULL; - } - - return process_rawblock(bcli); -} - -static struct command_result * -getrawblockbyheight_notfound(struct bitcoin_cli *bcli) -{ - struct json_stream *response; - - response = jsonrpc_stream_success(bcli->cmd); - json_add_null(response, "blockhash"); - json_add_null(response, "block"); - - return command_finished(bcli->cmd, response); -} - -static struct command_result *getrawblock(struct bitcoin_cli *bcli) -{ - struct getrawblock_stash *stash = bcli->stash; - - start_bitcoin_cli(NULL, bcli->cmd, process_getrawblock, true, - BITCOIND_HIGH_PRIO, stash, "getblock", - stash->block_hash, - /* Non-verbose: raw block. */ - "0", NULL); - - return command_still_pending(bcli->cmd); -} - -static struct command_result *process_getblockhash(struct bitcoin_cli *bcli) -{ - struct getrawblock_stash *stash = bcli->stash; - - /* If it failed with error 8, give an empty response. */ - if (bcli->exitstatus && *bcli->exitstatus != 0) { - /* Other error means we have to retry. */ - if (*bcli->exitstatus != 8) - return NULL; - return getrawblockbyheight_notfound(bcli); - } - - strip_trailing_whitespace(bcli->output, bcli->output_bytes); - stash->block_hash = tal_strdup(stash, bcli->output); - if (!stash->block_hash || strlen(stash->block_hash) != 64) { - return command_err_bcli_badjson(bcli, "bad blockhash"); - } - - return getrawblock(bcli); + return peers; } /* Get a raw block given its height. @@ -795,27 +327,98 @@ static struct command_result *getrawblockbyheight(struct command *cmd, const char *buf, const jsmntok_t *toks) { - struct getrawblock_stash *stash; + struct bcli_result *res; + struct json_stream *response; + const char *block_hash; u32 *height; + struct timemono first_error_time; + bool first_error = true; + int *peers = NULL; - /* bitcoin-cli wants a string. */ if (!param(cmd, buf, toks, p_req("height", param_number, &height), NULL)) return command_param_failed(); - stash = tal(cmd, struct getrawblock_stash); - stash->block_height = *height; - stash->peers = NULL; - tal_free(height); + res = run_bitcoin_cli(cmd, cmd->plugin, "getblockhash", + tal_fmt(tmpctx, "%u", *height), NULL); + + if (res->exitstatus != 0) { + return getrawblockbyheight_notfound(cmd); + } + + strip_trailing_whitespace(res->output, res->output_len); + if (strlen(res->output) != 64) + return command_err(cmd, res, "bad JSON: bad blockhash"); + + block_hash = tal_strdup(cmd, res->output); + + for (;;) { + res = run_bitcoin_cli(cmd, cmd->plugin, "getblock", + block_hash, "0", NULL); + + if (res->exitstatus == 0) { + strip_trailing_whitespace(res->output, res->output_len); + response = jsonrpc_stream_success(cmd); + json_add_string(response, "blockhash", block_hash); + json_add_string(response, "block", res->output); + return command_finished(cmd, response); + } + + plugin_log(cmd->plugin, LOG_DBG, + "failed to fetch block %s from the bitcoin backend (maybe pruned).", + block_hash); - start_bitcoin_cli(NULL, cmd, process_getblockhash, true, - BITCOIND_LOW_PRIO, stash, - "getblockhash", - take(tal_fmt(NULL, "%u", stash->block_height)), - NULL); + if (first_error) { + first_error_time = time_mono(); + first_error = false; + } + + struct timerel elapsed = timemono_between(time_mono(), first_error_time); + if (time_greater(elapsed, time_from_sec(bitcoind->retry_timeout))) { + return command_done_err(cmd, BCLI_ERROR, + tal_fmt(cmd, "getblock %s timed out after %"PRIu64" seconds", + block_hash, bitcoind->retry_timeout), NULL); + } - return command_still_pending(cmd); + /* Try fetching from peers if bitcoind >= 23.0.0 */ + if (bitcoind->version >= 230000) { + if (!peers) + peers = get_fullnode_peers(cmd, cmd); + + if (tal_count(peers) > 0) { + int peer = peers[tal_count(peers) - 1]; + tal_resize(&peers, tal_count(peers) - 1); + + res = run_bitcoin_cli(cmd, cmd->plugin, + "getblockfrompeer", + block_hash, + tal_fmt(tmpctx, "%i", peer), + NULL); + + if (res->exitstatus != 0) { + /* We still continue with the execution if we cannot fetch the + * block from peer */ + plugin_log(cmd->plugin, LOG_DBG, + "failed to fetch block %s from peer %i, skip.", + block_hash, peer); + } else { + plugin_log(cmd->plugin, LOG_DBG, + "try to fetch block %s from peer %i.", + block_hash, peer); + } + } + + if (tal_count(peers) == 0) { + plugin_log(cmd->plugin, LOG_DBG, + "asked all known peers about block %s, retry", + block_hash); + peers = tal_free(peers); + } + } + + sleep(1); + } } /* Get infos about the block chain. @@ -832,108 +435,137 @@ static struct command_result *getchaininfo(struct command *cmd, * a lower height than the one we already know, by waiting for a short period. * However, I currently don't have a better idea on how to handle this situation. */ u32 *height UNUSED; + struct bcli_result *res; + const jsmntok_t *tokens; + struct json_stream *response; + bool ibd; + u32 headers, blocks; + const char *chain, *err; + if (!param(cmd, buf, toks, p_opt("last_height", param_number, &height), NULL)) return command_param_failed(); - start_bitcoin_cli(NULL, cmd, process_getblockchaininfo, false, - BITCOIND_HIGH_PRIO, NULL, - "getblockchaininfo", NULL); + res = run_bitcoin_cli(cmd, cmd->plugin, "getblockchaininfo", NULL); + if (res->exitstatus != 0) + return command_err(cmd, res, "command failed"); - return command_still_pending(cmd); -} + tokens = json_parse_simple(res->output, res->output, res->output_len); + if (!tokens) + return command_err(cmd, res, "bad JSON: cannot parse"); -/* Mutual recursion. */ -static struct command_result *estimatefees_done(struct bitcoin_cli *bcli); + err = json_scan(tmpctx, res->output, tokens, + "{chain:%,headers:%,blocks:%,initialblockdownload:%}", + JSON_SCAN_TAL(tmpctx, json_strdup, &chain), + JSON_SCAN(json_to_number, &headers), + JSON_SCAN(json_to_number, &blocks), + JSON_SCAN(json_to_bool, &ibd)); + if (err) + return command_err(cmd, res, tal_fmt(tmpctx, "bad JSON: %s", err)); + + if (bitcoind->dev_ignore_ibd) + ibd = false; + + response = jsonrpc_stream_success(cmd); + json_add_string(response, "chain", chain); + json_add_u32(response, "headercount", headers); + json_add_u32(response, "blockcount", blocks); + json_add_bool(response, "ibd", ibd); + + return command_finished(cmd, response); +} /* Add a feerate, but don't publish one that bitcoind won't accept. */ static void json_add_feerate(struct json_stream *result, const char *fieldname, struct command *cmd, - const struct estimatefees_stash *stash, - uint64_t value) + u64 perkb_floor, + u64 value) { /* Anthony Towns reported signet had a 900kbtc fee block, and then * CLN got upset scanning feerate. It expects a u32. */ if (value > 0xFFFFFFFF) { plugin_log(cmd->plugin, LOG_UNUSUAL, - "Feerate %"PRIu64" is ridiculous: trimming to 32 bites", + "Feerate %"PRIu64" is ridiculous: trimming to 32 bits", value); value = 0xFFFFFFFF; } /* 0 is special, it means "unknown" */ - if (value && value < stash->perkb_floor) { + if (value && value < perkb_floor) { plugin_log(cmd->plugin, LOG_DBG, "Feerate %s raised from %"PRIu64 " perkb to floor of %"PRIu64, - fieldname, value, stash->perkb_floor); - json_add_u64(result, fieldname, stash->perkb_floor); + fieldname, value, perkb_floor); + json_add_u64(result, fieldname, perkb_floor); } else { json_add_u64(result, fieldname, value); } } -static struct command_result *estimatefees_next(struct command *cmd, - struct estimatefees_stash *stash) -{ - struct json_stream *response; - - if (stash->cursor < ARRAY_SIZE(stash->perkb)) { - start_bitcoin_cli(NULL, cmd, estimatefees_done, true, - BITCOIND_LOW_PRIO, stash, - "estimatesmartfee", - take(tal_fmt(NULL, "%u", - estimatefee_params[stash->cursor].blocks)), - estimatefee_params[stash->cursor].style, - NULL); - - return command_still_pending(cmd); - } - - response = jsonrpc_stream_success(cmd); - /* Present an ordered array of block deadlines, and a floor. */ - json_array_start(response, "feerates"); - for (size_t i = 0; i < ARRAY_SIZE(stash->perkb); i++) { - if (!stash->perkb[i]) - continue; - json_object_start(response, NULL); - json_add_u32(response, "blocks", estimatefee_params[i].blocks); - json_add_feerate(response, "feerate", cmd, stash, stash->perkb[i]); - json_object_end(response); - } - json_array_end(response); - json_add_u64(response, "feerate_floor", stash->perkb_floor); - return command_finished(cmd, response); -} - -static struct command_result *getminfees_done(struct bitcoin_cli *bcli) +/* Get the feerate floor from getmempoolinfo. + * Returns NULL on success (floor stored in *perkb_floor), or error response. */ +static struct command_result *get_feerate_floor(struct command *cmd, + u64 *perkb_floor) { + struct bcli_result *res; const jsmntok_t *tokens; const char *err; u64 mempoolfee, relayfee; - struct estimatefees_stash *stash = bcli->stash; - if (*bcli->exitstatus != 0) - return estimatefees_null_response(bcli); + res = run_bitcoin_cli(cmd, cmd->plugin, "getmempoolinfo", NULL); + if (res->exitstatus != 0) + return estimatefees_null_response(cmd); - tokens = json_parse_simple(bcli->output, - bcli->output, bcli->output_bytes); + tokens = json_parse_simple(res->output, res->output, res->output_len); if (!tokens) - return command_err_bcli_badjson(bcli, - "cannot parse getmempoolinfo"); + return command_err(cmd, res, "bad JSON: cannot parse"); - /* Look at minrelaytxfee they configured, and current min fee to get - * into mempool. */ - err = json_scan(tmpctx, bcli->output, tokens, + err = json_scan(tmpctx, res->output, tokens, "{mempoolminfee:%,minrelaytxfee:%}", JSON_SCAN(json_to_bitcoin_amount, &mempoolfee), JSON_SCAN(json_to_bitcoin_amount, &relayfee)); if (err) - return command_err_bcli_badjson(bcli, err); + return command_err(cmd, res, tal_fmt(tmpctx, "bad JSON: %s", err)); + + *perkb_floor = max_u64(mempoolfee, relayfee); + return NULL; +} + +/* Get a single feerate from estimatesmartfee. + * Returns NULL on success (feerate stored in *perkb), or error response. */ +static struct command_result *get_feerate(struct command *cmd, + u32 blocks, + const char *style, + u64 *perkb) +{ + struct bcli_result *res; + const jsmntok_t *tokens; + + res = run_bitcoin_cli(cmd, cmd->plugin, "estimatesmartfee", + tal_fmt(tmpctx, "%u", blocks), style, NULL); - stash->perkb_floor = max_u64(mempoolfee, relayfee); - stash->cursor = 0; - return estimatefees_next(bcli->cmd, stash); + if (res->exitstatus != 0) + return estimatefees_null_response(cmd); + + tokens = json_parse_simple(res->output, res->output, res->output_len); + if (!tokens) + return command_err(cmd, res, "bad JSON: cannot parse"); + + if (json_scan(tmpctx, res->output, tokens, "{feerate:%}", + JSON_SCAN(json_to_bitcoin_amount, perkb)) != NULL) { + /* Paranoia: if it had a feerate, but was malformed: */ + if (json_get_member(res->output, tokens, "feerate")) + return command_err(cmd, res, "bad JSON: cannot scan"); + /* Regtest fee estimation is generally awful: Fake it at min. */ + if (bitcoind->fake_fees) + *perkb = 1000; + else + /* We return null if estimation failed, and bitcoin-cli will + * exit with 0 but no feerate field on failure. */ + return estimatefees_null_response(cmd); + } + + return NULL; } /* Get the current feerates. We use an urgent feerate for unilateral_close and max, @@ -944,33 +576,38 @@ static struct command_result *estimatefees(struct command *cmd, const char *buf UNUSED, const jsmntok_t *toks UNUSED) { - struct estimatefees_stash *stash = tal(cmd, struct estimatefees_stash); + struct command_result *err; + u64 perkb_floor = 0; + u64 perkb[ARRAY_SIZE(estimatefee_params)]; + struct json_stream *response; if (!param(cmd, buf, toks, NULL)) return command_param_failed(); - start_bitcoin_cli(NULL, cmd, getminfees_done, true, - BITCOIND_LOW_PRIO, stash, - "getmempoolinfo", - NULL); - return command_still_pending(cmd); -} - -static struct command_result *estimatefees_done(struct bitcoin_cli *bcli) -{ - struct command_result *err; - struct estimatefees_stash *stash = bcli->stash; - - /* If we cannot estimate fees, no need to continue bothering bitcoind. */ - if (*bcli->exitstatus != 0) - return estimatefees_null_response(bcli); - - err = estimatefees_parse_feerate(bcli, &stash->perkb[stash->cursor]); + err = get_feerate_floor(cmd, &perkb_floor); if (err) return err; - stash->cursor++; - return estimatefees_next(bcli->cmd, stash); + for (size_t i = 0; i < ARRAY_SIZE(estimatefee_params); i++) { + err = get_feerate(cmd, estimatefee_params[i].blocks, + estimatefee_params[i].style, &perkb[i]); + if (err) + return err; + } + + response = jsonrpc_stream_success(cmd); + json_array_start(response, "feerates"); + for (size_t i = 0; i < ARRAY_SIZE(perkb); i++) { + if (!perkb[i]) + continue; + json_object_start(response, NULL); + json_add_u32(response, "blocks", estimatefee_params[i].blocks); + json_add_feerate(response, "feerate", cmd, perkb_floor, perkb[i]); + json_object_end(response); + } + json_array_end(response); + json_add_u64(response, "feerate_floor", perkb_floor); + return command_finished(cmd, response); } /* Send a transaction to the Bitcoin network. @@ -982,6 +619,8 @@ static struct command_result *sendrawtransaction(struct command *cmd, { const char *tx, *highfeesarg; bool *allowhighfees; + struct bcli_result *res; + struct json_stream *response; /* bitcoin-cli wants strings. */ if (!param(cmd, buf, toks, @@ -995,12 +634,26 @@ static struct command_result *sendrawtransaction(struct command *cmd, } else highfeesarg = NULL; - start_bitcoin_cli(NULL, cmd, process_sendrawtransaction, true, - BITCOIND_HIGH_PRIO, NULL, - "sendrawtransaction", - tx, highfeesarg, NULL); + res = run_bitcoin_cli(cmd, cmd->plugin, + "sendrawtransaction", tx, highfeesarg, NULL); + + /* This is useful for functional tests. */ + plugin_log(cmd->plugin, LOG_DBG, + "sendrawtx exit %i (%s) %.*s", + res->exitstatus, res->args, + res->exitstatus ? (int)res->output_len : 0, + res->output); - return command_still_pending(cmd); + response = jsonrpc_stream_success(cmd); + json_add_bool(response, "success", + res->exitstatus == 0 || + res->exitstatus == RPC_TRANSACTION_ALREADY_IN_CHAIN); + json_add_string(response, "errmsg", + res->exitstatus ? + tal_strndup(cmd, res->output, res->output_len) + : ""); + + return command_finished(cmd, response); } static struct command_result *getutxout(struct command *cmd, @@ -1008,6 +661,11 @@ static struct command_result *getutxout(struct command *cmd, const jsmntok_t *toks) { const char *txid, *vout; + struct bcli_result *res; + const jsmntok_t *tokens; + struct json_stream *response; + struct bitcoin_tx_output output; + const char *err; /* bitcoin-cli wants strings. */ if (!param(cmd, buf, toks, @@ -1016,11 +674,35 @@ static struct command_result *getutxout(struct command *cmd, NULL)) return command_param_failed(); - start_bitcoin_cli(NULL, cmd, process_getutxout, true, - BITCOIND_HIGH_PRIO, NULL, - "gettxout", txid, vout, NULL); + res = run_bitcoin_cli(cmd, cmd->plugin, "gettxout", txid, vout, NULL); - return command_still_pending(cmd); + /* As of at least v0.15.1.0, bitcoind returns "success" but an empty + string on a spent txout. */ + if (res->exitstatus != 0 || res->output_len == 0) { + response = jsonrpc_stream_success(cmd); + json_add_null(response, "amount"); + json_add_null(response, "script"); + return command_finished(cmd, response); + } + + tokens = json_parse_simple(res->output, res->output, res->output_len); + if (!tokens) + return command_err(cmd, res, "bad JSON: cannot parse"); + + err = json_scan(tmpctx, res->output, tokens, + "{value:%,scriptPubKey:{hex:%}}", + JSON_SCAN(json_to_bitcoin_amount, + &output.amount.satoshis), /* Raw: bitcoind */ + JSON_SCAN_TAL(cmd, json_tok_bin_from_hex, + &output.script)); + if (err) + return command_err(cmd, res, tal_fmt(tmpctx, "bad JSON: %s", err)); + + response = jsonrpc_stream_success(cmd); + json_add_sats(response, "amount", output.amount); + json_add_string(response, "script", tal_hex(response, output.script)); + + return command_finished(cmd, response); } static void bitcoind_failure(struct plugin *p, const char *error_message) @@ -1098,6 +780,7 @@ static void wait_and_check_bitcoind(struct plugin *p) } output = grab_fd_str(cmd, from); + close(from); waitpid(child, &status, 0); @@ -1173,12 +856,6 @@ static struct bitcoind *new_bitcoind(const tal_t *ctx) bitcoind->cli = NULL; bitcoind->datadir = NULL; - for (size_t i = 0; i < BITCOIND_NUM_PRIO; i++) { - bitcoind->num_requests[i] = 0; - list_head_init(&bitcoind->pending[i]); - } - list_head_init(&bitcoind->current); - bitcoind->error_count = 0; bitcoind->retry_timeout = 60; bitcoind->rpcuser = NULL; bitcoind->rpcpass = NULL; diff --git a/tests/test_closing.py b/tests/test_closing.py index 47cc28d8077c..3879934a07a7 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -4277,7 +4277,7 @@ def test_onchain_reestablish_reply(node_factory, bitcoind, executor): # We block l3 from seeing close, so it will try to reestablish. def no_new_blocks(req): - return {"error": "go away"} + return {"error": {"code": -8, "message": "Block height out of range"}} l3.daemon.rpcproxy.mock_rpc('getblockhash', no_new_blocks) l2.rpc.disconnect(l3.info['id'], force=True) @@ -4360,7 +4360,7 @@ def test_reestablish_closed_channels(node_factory, bitcoind): # We block l2 from seeing close, so it will try to reestablish. def no_new_blocks(req): - return {"error": "go away"} + return {"error": {"code": -8, "message": "Block height out of range"}} l2.daemon.rpcproxy.mock_rpc('getblockhash', no_new_blocks) # Make a payment, make sure it's entirely finished before we close. diff --git a/tests/test_misc.py b/tests/test_misc.py index 18c7384b0a8f..e22ca92babea 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -77,35 +77,28 @@ def test_db_upgrade(node_factory): def test_bitcoin_failure(node_factory, bitcoind): - l1 = node_factory.get_node() + # The node will crash when bitcoind fails, so we need `may_fail` and `broken_log`. + l1 = node_factory.get_node(may_fail=True, broken_log=r'getrawblockbyheight:|FATAL SIGNAL|backtrace') # Make sure we're not failing it between getblockhash and getblock. sync_blockheight(bitcoind, [l1]) def crash_bitcoincli(r): - return {'error': 'go away'} + return {'id': r['id'], 'result': 'not_a_valid_blockhash', 'error': None} - # This is not a JSON-RPC response by purpose - l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', crash_bitcoincli) + # This is not a JSON-RPC response by purpose. l1.daemon.rpcproxy.mock_rpc('getblockhash', crash_bitcoincli) - # This should cause both estimatefee and getblockhash fail - l1.daemon.wait_for_logs(['Unable to estimate any fees', - 'getblockhash .* exited with status 1']) - - # And they should retry! - l1.daemon.wait_for_logs(['Unable to estimate any fees', - 'getblockhash .* exited with status 1']) - - # Restore, then it should recover and get blockheight. - l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', None) - l1.daemon.rpcproxy.mock_rpc('getblockhash', None) + # Generate a block to trigger the topology update which calls getblockhash. + bitcoind.generate_block(1) - bitcoind.generate_block(5) - sync_blockheight(bitcoind, [l1]) + # lightningd should crash with the error + # `fatal()` calls `abort()` when crashlog is set (during operation), so exit code is -6 (SIGABRT). + l1.daemon.wait_for_log(r'getrawblockbyheight:') + assert l1.daemon.wait() != 0 # We refuse to start if bitcoind is in `blocksonly` - l1.stop() + # l1 already crashed, so we just need to restart bitcoind. bitcoind.stop() bitcoind.cmd_line += ["-blocksonly"] bitcoind.start() @@ -2216,31 +2209,17 @@ def test_bitcoind_fail_first(node_factory, bitcoind): """ # Do not start the lightning node since we need to instrument bitcoind # first. - timeout = 5 if 5 < TIMEOUT // 3 else TIMEOUT // 3 - l1 = node_factory.get_node(start=False, - broken_log=r'plugin-bcli: .*(-stdinrpcpass -stdin getblockhash 100 exited 1 \(after [0-9]* other errors\)|we have been retrying command for)', - may_fail=True, - options={'bitcoin-retry-timeout': timeout}) + l1 = node_factory.get_node(start=False, may_fail=True) # Instrument bitcoind to fail some queries first. - def mock_fail(*args): - raise ValueError() - - # If any of these succeed, they reset fail timeout. - l1.daemon.rpcproxy.mock_rpc('getblockhash', mock_fail) - l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', mock_fail) - l1.daemon.rpcproxy.mock_rpc('getmempoolinfo', mock_fail) + def crash_bitcoincli(r): + return {'id': r['id'], 'result': 'not_a_valid_blockhash', 'error': None} + l1.daemon.rpcproxy.mock_rpc('getblockhash', crash_bitcoincli) l1.daemon.start(wait_for_initialized=False, stderr_redir=True) - l1.daemon.wait_for_logs([r'getblockhash [a-z0-9]* exited with status 1', - r'Unable to estimate any fees', - r'BROKEN.*we have been retrying command for --bitcoin-retry-timeout={} seconds'.format(timeout)]) - # Will exit with failure code. - assert l1.daemon.wait() == 1 - # Now unset the mock, so calls go through again - l1.daemon.rpcproxy.mock_rpc('getblockhash', None) - l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', None) + assert l1.daemon.wait() == 1 + assert l1.daemon.is_in_stderr('getrawblockbyheight:') @unittest.skipIf(TEST_NETWORK == 'liquid-regtest', "Fees on elements are different")