From 3eebd0cc2029598150bcaaee8665aa8dcf651624 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 10 Aug 2019 20:17:32 +0930 Subject: [PATCH] lightningd: add flag for whether we're synced, and callback infrastructure. We consider ourselves synced when bitcoind is synced and we're synced with that. Signed-off-by: Rusty Russell --- lightningd/chaintopology.c | 44 +++++++++++++++++++++++++++++++++++++ lightningd/chaintopology.h | 33 ++++++++++++++++++++++++++++ lightningd/peer_control.c | 3 +++ tests/test_misc.py | 45 +++++++++++++++++++++++++++++++++++++- 4 files changed, 124 insertions(+), 1 deletion(-) diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c index 1ca6299d2..ee3e3d52b 100644 --- a/lightningd/chaintopology.c +++ b/lightningd/chaintopology.c @@ -559,6 +559,31 @@ static void next_updatefee_timer(struct chain_topology *topo) start_fee_estimate, topo)); } +struct sync_waiter { + /* Linked from chain_topology->sync_waiters */ + struct list_node list; + void (*cb)(struct chain_topology *topo, void *arg); + void *arg; +}; + +static void destroy_sync_waiter(struct sync_waiter *waiter) +{ + list_del(&waiter->list); +} + +void topology_add_sync_waiter_(const tal_t *ctx, + struct chain_topology *topo, + void (*cb)(struct chain_topology *topo, + void *arg), + void *arg) +{ + struct sync_waiter *w = tal(ctx, struct sync_waiter); + w->cb = cb; + w->arg = arg; + list_add_tail(topo->sync_waiters, &w->list); + tal_add_destructor(w, destroy_sync_waiter); +} + /* Once we're run out of new blocks to add, call this. */ static void updates_complete(struct chain_topology *topo) { @@ -579,6 +604,23 @@ static void updates_complete(struct chain_topology *topo) topo->prev_tip = topo->tip; } + /* If bitcoind is synced, we're now synced. */ + if (topo->bitcoind->synced && !topology_synced(topo)) { + struct sync_waiter *w; + struct list_head *list = topo->sync_waiters; + + /* Mark topology_synced() before callbacks. */ + topo->sync_waiters = NULL; + + while ((w = list_pop(list, struct sync_waiter, list))) { + /* In case it doesn't free itself. */ + tal_del_destructor(w, destroy_sync_waiter); + tal_steal(list, w); + w->cb(topo, w->arg); + } + tal_free(list); + } + /* Try again soon. */ next_topology_timer(topo); } @@ -911,6 +953,8 @@ void setup_topology(struct chain_topology *topo, { memset(&topo->feerate, 0, sizeof(topo->feerate)); topo->timers = timers; + topo->sync_waiters = tal(topo, struct list_head); + list_head_init(topo->sync_waiters); topo->min_blockheight = min_blockheight; topo->max_blockheight = max_blockheight; diff --git a/lightningd/chaintopology.h b/lightningd/chaintopology.h index 13fcbd43e..c36286719 100644 --- a/lightningd/chaintopology.h +++ b/lightningd/chaintopology.h @@ -100,6 +100,11 @@ struct chain_topology { /* How often to poll. */ u32 poll_seconds; + /* struct sync_waiters waiting for us to catch up with bitcoind (and + * once that has caught up with the network). NULL if we're already + * caught up. */ + struct list_head *sync_waiters; + /* The bitcoind. */ struct bitcoind *bitcoind; @@ -171,6 +176,34 @@ void begin_topology(struct chain_topology *topo); struct txlocator *locate_tx(const void *ctx, const struct chain_topology *topo, const struct bitcoin_txid *txid); +static inline bool topology_synced(const struct chain_topology *topo) +{ + return topo->sync_waiters == NULL; +} + +/** + * topology_add_sync_waiter: wait for lightningd to sync with bitcoin network + * @ctx: context to allocate the waiter from. + * @topo: chain topology + * @cb: callback to call when we're synced. + * @arg: arg for @cb + * + * topology_synced() must be false when this is called. It will be true + * when @cb is called. @cb will not be called if @ctx is freed first. + */ +void topology_add_sync_waiter_(const tal_t *ctx, + struct chain_topology *topo, + void (*cb)(struct chain_topology *topo, + void *arg), + void *arg); +#define topology_add_sync_waiter(ctx, topo, cb, arg) \ + topology_add_sync_waiter_((ctx), (topo), \ + typesafe_cb_preargs(void, void *, \ + (cb), (arg), \ + struct chain_topology *), \ + (arg)) + + /* In channel_control.c */ void notify_feerate_change(struct lightningd *ld); #endif /* LIGHTNING_LIGHTNINGD_CHAINTOPOLOGY_H */ diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index ef4abda03..0419b922a 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -1557,6 +1557,9 @@ static struct command_result *json_getinfo(struct command *cmd, if (!cmd->ld->topology->bitcoind->synced) json_add_string(response, "warning_bitcoind_sync", "Bitcoind is not up-to-date with network."); + else if (!topology_synced(cmd->ld->topology)) + json_add_string(response, "warning_lightningd_sync", + "Still loading latest blocks from bitcoind."); return command_success(cmd, response); } diff --git a/tests/test_misc.py b/tests/test_misc.py index 1867e1b5c..cc4fb62de 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,7 +1,9 @@ +from bitcoin.rpc import RawProxy from fixtures import * # noqa: F401,F403 from flaky import flaky # noqa: F401 from lightning import RpcError -from utils import DEVELOPER, VALGRIND, sync_blockheight, only_one, wait_for, TailableProc +from threading import Event +from utils import DEVELOPER, TIMEOUT, VALGRIND, sync_blockheight, only_one, wait_for, TailableProc from ephemeral_port_reserve import reserve import json @@ -133,6 +135,47 @@ def test_bitcoin_ibd(node_factory, bitcoind): assert 'warning_bitcoind_sync' not in l1.rpc.getinfo() +def test_lightningd_still_loading(node_factory, bitcoind, executor): + """Test that we recognize we haven't got all blocks from bitcoind""" + + mock_release = Event() + + # This is slow enough that we're going to notice. + def mock_getblock(r): + conf_file = os.path.join(bitcoind.bitcoin_dir, 'bitcoin.conf') + brpc = RawProxy(btc_conf_file=conf_file) + if r['params'][0] == slow_blockid: + mock_release.wait(TIMEOUT) + return { + "result": brpc._call(r['method'], *r['params']), + "error": None, + "id": r['id'] + } + + # Start it once, make sure it gets a second block (thus writes into db) + l1 = node_factory.get_node() + bitcoind.generate_block(1) + sync_blockheight(bitcoind, [l1]) + l1.stop() + + # Now make sure it's behind. + bitcoind.generate_block(2) + + # Make it slow grabbing the final block. + slow_blockid = bitcoind.rpc.getblockhash(bitcoind.rpc.getblockcount()) + l1.daemon.rpcproxy.mock_rpc('getblock', mock_getblock) + + l1.start() + + # It will warn about being out-of-sync. + assert 'warning_bitcoind_sync' not in l1.rpc.getinfo() + assert 'warning_lightningd_sync' in l1.rpc.getinfo() + + # Release the mock, and it will recover. + mock_release.set() + wait_for(lambda: 'warning_lightningd_sync' not in l1.rpc.getinfo()) + + def test_ping(node_factory): l1, l2 = node_factory.line_graph(2, fundchannel=False)