From faded9a9cf7e4fc01bcb9b03b0381e0ca738bfe1 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 9 Aug 2019 12:08:59 +0930 Subject: [PATCH] bitcoind: detect when it's still syncing, add field to getinfo. Signed-off-by: Rusty Russell --- lightningd/bitcoind.c | 98 +++++++++++++++++++++++++++++++++++++++ lightningd/bitcoind.h | 3 ++ lightningd/peer_control.c | 5 ++ tests/test_misc.py | 21 +++++++++ 4 files changed, 127 insertions(+) diff --git a/lightningd/bitcoind.c b/lightningd/bitcoind.c index be2f671d4..b70a8193d 100644 --- a/lightningd/bitcoind.c +++ b/lightningd/bitcoind.c @@ -997,6 +997,103 @@ void bitcoind_getclientversion(struct bitcoind *bitcoind) "getnetworkinfo", NULL); } +/* Mutual recursion */ +static bool process_getblockchaininfo(struct bitcoin_cli *bcli); + +static void retry_getblockchaininfo(struct bitcoind *bitcoind) +{ + assert(!bitcoind->synced); + start_bitcoin_cli(bitcoind, NULL, + process_getblockchaininfo, + false, BITCOIND_LOW_PRIO, NULL, NULL, + "getblockchaininfo", NULL); +} + +/* Given JSON object from getblockchaininfo, are we synced? Poll if not. */ +static void is_bitcoind_synced_yet(struct bitcoind *bitcoind, + const char *output, size_t output_len, + const jsmntok_t *obj, + bool initial) +{ + const jsmntok_t *t; + unsigned int headers, blocks; + bool ibd; + + t = json_get_member(output, obj, "headers"); + if (!t || !json_to_number(output, t, &headers)) + fatal("Invalid 'headers' field in getblockchaininfo '%.*s'", + (int)output_len, output); + + t = json_get_member(output, obj, "blocks"); + if (!t || !json_to_number(output, t, &blocks)) + fatal("Invalid 'blocks' field in getblockchaininfo '%.*s'", + (int)output_len, output); + + t = json_get_member(output, obj, "initialblockdownload"); + if (!t || !json_to_bool(output, t, &ibd)) + fatal("Invalid 'initialblockdownload' field in getblockchaininfo '%.*s'", + (int)output_len, output); + + if (ibd) { + if (initial) + log_unusual(bitcoind->log, + "Waiting for initial block download" + " (this can take a while!)"); + else + log_debug(bitcoind->log, + "Still waiting for initial block download"); + } else if (headers != blocks) { + if (initial) + log_unusual(bitcoind->log, + "Waiting for bitcoind to catch up" + " (%u blocks of %u)", + blocks, headers); + else + log_debug(bitcoind->log, + "Waiting for bitcoind to catch up" + " (%u blocks of %u)", + blocks, headers); + } else { + if (!initial) + log_info(bitcoind->log, "Bitcoind now synced."); + bitcoind->synced = true; + return; + } + + bitcoind->synced = false; + notleak(new_reltimer(bitcoind->ld->timers, bitcoind, + /* Be 4x more aggressive in this case. */ + time_divide(time_from_sec(bitcoind->ld->topology + ->poll_seconds), 4), + retry_getblockchaininfo, bitcoind)); +} + +static bool process_getblockchaininfo(struct bitcoin_cli *bcli) +{ + const jsmntok_t *tokens; + bool valid; + + tokens = json_parse_input(bcli, bcli->output, bcli->output_bytes, + &valid); + if (!tokens) + fatal("%s: %s response (%.*s)", + bcli_args(tmpctx, bcli), + valid ? "partial" : "invalid", + (int)bcli->output_bytes, bcli->output); + + if (tokens[0].type != JSMN_OBJECT) { + log_unusual(bcli->bitcoind->log, + "%s: gave non-object (%.*s)?", + bcli_args(tmpctx, bcli), + (int)bcli->output_bytes, bcli->output); + return false; + } + + is_bitcoind_synced_yet(bcli->bitcoind, bcli->output, bcli->output_bytes, + tokens, false); + return true; +} + static void destroy_bitcoind(struct bitcoind *bitcoind) { /* Suppresses the callbacks from bcli_finished as we free conns. */ @@ -1073,6 +1170,7 @@ static char* check_blockchain_from_bitcoincli(const tal_t *ctx, " Should be: %s", bitcoind->chainparams->bip70_name); + is_bitcoind_synced_yet(bitcoind, output, output_bytes, tokens, true); return NULL; } diff --git a/lightningd/bitcoind.h b/lightningd/bitcoind.h index cdc43f72b..4d1e1ceb6 100644 --- a/lightningd/bitcoind.h +++ b/lightningd/bitcoind.h @@ -43,6 +43,9 @@ struct bitcoind { /* Main lightningd structure */ struct lightningd *ld; + /* 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]; diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 822ac3316..ef4abda03 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -1553,6 +1553,11 @@ static struct command_result *json_getinfo(struct command *cmd, wallet_total_forward_fees(cmd->ld->wallet), "msatoshi_fees_collected", "fees_collected_msat"); + + if (!cmd->ld->topology->bitcoind->synced) + json_add_string(response, "warning_bitcoind_sync", + "Bitcoind is not up-to-date with network."); + return command_success(cmd, response); } diff --git a/tests/test_misc.py b/tests/test_misc.py index 63b99cf1a..1867e1b5c 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -112,6 +112,27 @@ def test_bitcoin_failure(node_factory, bitcoind): sync_blockheight(bitcoind, [l1]) +def test_bitcoin_ibd(node_factory, bitcoind): + """Test that we recognize bitcoin in initial download mode""" + info = bitcoind.rpc.getblockchaininfo() + info['initialblockdownload'] = True + + l1 = node_factory.get_node(start=False) + l1.daemon.rpcproxy.mock_rpc('getblockchaininfo', info) + + l1.start() + + # This happens before the Starting message start() waits for. + assert l1.daemon.is_in_log('Waiting for initial block download') + assert 'warning_bitcoind_sync' in l1.rpc.getinfo() + + # "Finish" IDB. + l1.daemon.rpcproxy.mock_rpc('getblockchaininfo', None) + + l1.daemon.wait_for_log('Bitcoind now synced') + assert 'warning_bitcoind_sync' not in l1.rpc.getinfo() + + def test_ping(node_factory): l1, l2 = node_factory.line_graph(2, fundchannel=False)