From 521d3d53eda4bfd835a984b4c6cb180b68e02273 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sun, 24 Apr 2016 19:37:13 +0930 Subject: [PATCH] chaintopology: keep track of the bitcoin block topology. This allows us to track precise transaction depth ourselves, particularly in the case of branching. Signed-off-by: Rusty Russell --- daemon/Makefile | 2 + daemon/chaintopology.c | 429 +++++++++++++++++++++++++++++++++++++++++ daemon/chaintopology.h | 12 ++ daemon/lightningd.c | 4 + daemon/lightningd.h | 3 + daemon/timeout.h | 1 + 6 files changed, 451 insertions(+) create mode 100644 daemon/chaintopology.c create mode 100644 daemon/chaintopology.h diff --git a/daemon/Makefile b/daemon/Makefile index 1e0554823..dbc62ca90 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -15,6 +15,7 @@ DAEMON_LIB_OBJS := $(DAEMON_LIB_SRC:.c=.o) DAEMON_SRC := \ daemon/bitcoind.c \ + daemon/chaintopology.c \ daemon/controlled_time.c \ daemon/cryptopkt.c \ daemon/dns.c \ @@ -40,6 +41,7 @@ DAEMON_JSMN_HEADERS := daemon/jsmn/jsmn.h DAEMON_HEADERS := \ daemon/bitcoind.h \ + daemon/chaintopology.h \ daemon/configdir.h \ daemon/controlled_time.h \ daemon/cryptopkt.h \ diff --git a/daemon/chaintopology.c b/daemon/chaintopology.c new file mode 100644 index 000000000..9e662e824 --- /dev/null +++ b/daemon/chaintopology.c @@ -0,0 +1,429 @@ +#include "bitcoind.h" +#include "chaintopology.h" +#include "lightningd.h" +#include "log.h" +#include "timeout.h" +#include "watch.h" +#include +#include + +static struct timeout topology_timeout; + +struct tx_in_block { + struct list_node list; + struct txwatch *w; + struct block *block; +}; + +struct block { + int height; + + /* We can have a single parent */ + struct sha256_double prevblkid; + /* ... but multiple children. */ + struct block **nexts; + + /* Key for hash table */ + struct sha256_double blkid; + u32 mediantime; + + /* Transactions in this block we care about */ + struct list_head txs; +}; + +/* Hash blocks by sha */ +static const struct sha256_double *keyof_block_map(const struct block *b) +{ + return &b->blkid; +} + +static size_t hash_sha(const struct sha256_double *key) +{ + size_t ret; + + memcpy(&ret, key, sizeof(ret)); + return ret; +} + +static bool block_eq(const struct block *b, const struct sha256_double *key) +{ + return structeq(&b->blkid, key); +} +HTABLE_DEFINE_TYPE(struct block, keyof_block_map, hash_sha, block_eq, block_map); + +struct topology { + struct block **tips; + struct sha256_double *newtips; + struct block_map block_map; +}; + +static struct block *connect_blocks(struct topology *topo, + const struct sha256_double *blkid) +{ + struct block *b = block_map_get(&topo->block_map, blkid); + struct block *prev; + size_t n; + + /* Hooked in already? */ + if (b->height != -1) + return b; + + prev = connect_blocks(topo, &b->prevblkid); + b->height = prev->height + 1; + n = tal_count(prev->nexts); + tal_resize(&prev->nexts, n+1); + prev->nexts[n] = b; + + return b; +} + +/* This is expensive, but reorgs are usually short and txs are few. + * + * B TX + * o--------o-------o + * \ + * \ TX + * ------o-------o-------o + * + * + * This counts as depth 1, not 2, since the top fork may be extended. + * + * B + * o--------o-------o + * \ + * \ TX + * ------o-------o-------o + * + * This TX counts as depth 0 by our algorithm, which treats "not in chain" + * as "next in chain". + * + * B + * o--------o-------o-------o + * \ + * \ TX + * ------o-------o-------o + * + * This counts as -1. + * + * We calculate the "height" of a tx (subtraction from best tips gives us the + * the depth). + * + * 1) The height of a tx in a particular branch is the height of the block it + * appears in, or the max height + 1 (assuming it's pending). + * 2) The overall height of a tx is the maximum height on any branch. + */ + +static bool tx_in_block(const struct block *b, const struct txwatch *w) +{ + struct tx_in_block *tx; + + list_for_each(&b->txs, tx, list) { + if (tx->w == w) + return true; + } + return false; +} + +/* FIXME: Cache this on the tips. */ +static size_t get_tx_branch_height(const struct topology *topo, + const struct block *tip, + const struct txwatch *w, + size_t max) +{ + const struct block *b; + + for (b = tip; b; b = block_map_get(&topo->block_map, &b->prevblkid)) { + if (tx_in_block(b, w)) + return b->height; + /* Don't bother returning less than max */ + if (b->height < max) + return max; + } + + return tip->height + 1; +} + +size_t get_tx_depth(struct lightningd_state *dstate, const struct txwatch *w) +{ + const struct topology *topo = dstate->topology; + size_t i, max = 0, longest = 0; + + /* Calculate tx height. */ + for (i = 0; i < tal_count(topo->tips); i++) { + size_t h = get_tx_branch_height(topo, topo->tips[i], w, max); + if (h > max) + max = h; + + /* Grab longest tip while we're here. */ + if (topo->tips[i]->height > longest) + longest = topo->tips[i]->height; + } + + return longest + 1 - max; +} + +#if 0 +static void reevaluate_txs_from(struct lightningd_state *dstate, + struct block *common, + struct block *b) +{ + struct topology *topo = dstate->topology; + size_t i; + struct tx_in_block *tx; + + /* Careful! Callbacks could cause arbitrary txs to be deleted. */ +again: + b->tx_deleted = false; + + list_for_each(&b->txs, tx, list) { + size_t dist = get_tx_distance(common, tx->w); + int depth; + + /* Worst case, distance is one past tips. */ + depth = topo->tips[0]->height - (b->height + dist); + assert(depth >= -1); + +#if 0 /* When we replace notifications */ + if (tx->w->depth != depth) { + tx->w->depth = depth; + tx->w->cb(tx->w->peer, depth, tx->w->cbdata); + if (b->tx_deleted) + goto again; + } +#endif + } + + for (i = 0; i < tal_count(b->nexts); i++) + reevaluate_txs_from(dstate, common, b->nexts[i]); +} + +static struct block *find_common(struct topology *topo, + struct block *a, struct block *b) +{ + /* Special case for first time, when we have no previous tips */ + if (!a) + return b; + + /* Get to same height to start. */ + while (a->height > b->height) + a = block_map_get(&topo->block_map, &a->prevblkid); + while (b->height > a->height) + b = block_map_get(&topo->block_map, &b->prevblkid); + + while (a != b) { + a = block_map_get(&topo->block_map, &a->prevblkid); + b = block_map_get(&topo->block_map, &b->prevblkid); + } + return a; +} +#endif + +static void topology_changed(struct lightningd_state *dstate) +{ + struct topology *topo = dstate->topology; + size_t i; + +#if 0 + struct block *common = NULL; + + /* topo->tips is NULL for very first time. */ + if (topo->tips) { + for (i = 0; i < tal_count(topo->tips); i++) + common = find_common(topo, common, topo->tips[i]); + } +#endif + + tal_free(topo->tips); + topo->tips = tal_arr(topo, struct block *, tal_count(topo->newtips)); + for (i = 0; i < tal_count(topo->newtips); i++) + topo->tips[i] = connect_blocks(topo, &topo->newtips[i]); + + /* FIXME: Tell watch code to re-evaluate all txs. */ +} + +static void remove_tx(struct tx_in_block *t) +{ + list_del_from(&t->block->txs, &t->list); +} + +static void add_tx_to_block(struct block *b, struct txwatch *w) +{ + /* We attach this to watch, so removed when that is */ + struct tx_in_block *t = tal(w, struct tx_in_block); + + t->block = b; + t->w = w; + list_add_tail(&b->txs, &t->list); + tal_add_destructor(t, remove_tx); +} + +static struct block *add_block(struct lightningd_state *dstate, + struct sha256_double *blkid, + struct sha256_double *prevblock, + struct sha256_double *txids, + u32 mediantime) +{ + size_t i; + struct topology *topo = dstate->topology; + struct block *b = tal(topo, struct block); + + assert(!block_map_get(&topo->block_map, blkid)); + + /* We fill these out in topology_changed */ + b->height = -1; + b->nexts = tal_arr(b, struct block *, 0); + + b->mediantime = mediantime; + b->blkid = *blkid; + b->prevblkid = *prevblock; + + /* See if any of those txids are interesting. */ + list_head_init(&b->txs); + for (i = 1; i < tal_count(txids); i++) { + struct txwatch *w; + + w = txwatch_hash_get(&dstate->txwatches, &txids[i]); + if (w) + add_tx_to_block(b, w); + } + + block_map_add(&topo->block_map, b); + return b; +} + +static void gather_blocks(struct lightningd_state *dstate, + struct sha256_double *blkid, + struct sha256_double *prevblock, + struct sha256_double *txids, + u32 mediantime, + ptrint_t *p) +{ + struct topology *topo = dstate->topology; + ptrdiff_t i; + + add_block(dstate, blkid, prevblock, txids, mediantime); + + /* Recurse if we need prev. */ + if (!block_map_get(&topo->block_map, prevblock)) { + bitcoind_getblock(dstate, prevblock, gather_blocks, p); + return; + } + + /* Recurse if more tips to map. */ + for (i = ptr2int(p) + 1; i < tal_count(topo->newtips); i++) { + if (!block_map_get(&topo->block_map, &topo->newtips[i])) { + bitcoind_getblock(dstate, &topo->newtips[i], + gather_blocks, int2ptr(i)); + return; + } + } + + /* All done. */ + topology_changed(dstate); + + refresh_timeout(dstate, &topology_timeout); +} + +static bool tips_changed(struct sha256_double *blockids, struct block **tips) +{ + size_t i; + + /* First time */ + if (!tips) + return true; + + if (tal_count(blockids) != tal_count(tips)) + return true; + + for (i = 0; i < tal_count(tips); i++) + if (!structeq(&blockids[i], &tips[i]->blkid)) + return true; + return false; +} + +static void check_chaintips(struct lightningd_state *dstate, + struct sha256_double *blockids, + void *arg) +{ + size_t i; + struct topology *topo = dstate->topology; + + /* We assume chaintip ordering: if we're wrong, it's just slow */ + if (!tips_changed(blockids, topo->tips)) + goto out; + + /* Start iterating on first unknown tip. */ + topo->newtips = tal_steal(topo, blockids); + for (i = 0; i < tal_count(topo->newtips); i++) { + if (block_map_get(&topo->block_map, &topo->newtips[i])) + continue; + bitcoind_getblock(dstate, &topo->newtips[i], gather_blocks, + int2ptr(i)); + return; + } + + log_unusual(dstate->base_log, "Chaintips changed but all blocks known?"); + topology_changed(dstate); + +out: + refresh_timeout(dstate, &topology_timeout); +} + +static void start_poll_chaintips(struct lightningd_state *dstate) +{ + if (!list_empty(&dstate->bitcoin_req)) { + log_unusual(dstate->base_log, + "Delaying start poll: commands in progress"); + refresh_timeout(dstate, &topology_timeout); + } else + bitcoind_get_chaintips(dstate, check_chaintips, NULL); +} + +static void init_topo(struct lightningd_state *dstate, + struct sha256_double *blkid, + struct sha256_double *prevblock, + struct sha256_double *txids, + u32 mediantime, + ptrint_t *p) +{ + struct block *b; + + b = add_block(dstate, blkid, prevblock, txids, mediantime); + b->height = ptr2int(p); + + /* Now grab chaintips immediately. */ + bitcoind_get_chaintips(dstate, check_chaintips, NULL); +} + +static void get_init_block(struct lightningd_state *dstate, + const struct sha256_double *blkid, + ptrint_t *blknum) +{ + bitcoind_getblock(dstate, blkid, init_topo, blknum); +} + +static void get_init_blockhash(struct lightningd_state *dstate, u32 blockcount, + void *unused) +{ + u32 start; + + if (blockcount < 100) + start = 0; + else + start = blockcount - 100; + + /* Start topology from 100 blocks back. */ + bitcoind_getblockhash(dstate, start, get_init_block, int2ptr(start)); +} + +void setup_topology(struct lightningd_state *dstate) +{ + dstate->topology = tal(dstate, struct topology); + dstate->topology->tips = NULL; + dstate->topology->newtips = NULL; + block_map_init(&dstate->topology->block_map); + + init_timeout(&topology_timeout, dstate->config.poll_seconds, + start_poll_chaintips, dstate); + bitcoind_getblockcount(dstate, get_init_blockhash, NULL); +} diff --git a/daemon/chaintopology.h b/daemon/chaintopology.h new file mode 100644 index 000000000..f99ce7769 --- /dev/null +++ b/daemon/chaintopology.h @@ -0,0 +1,12 @@ +#ifndef LIGHTNING_DAEMON_CHAINTOPOLOGY_H +#define LIGHTNING_DAEMON_CHAINTOPOLOGY_H +#include "config.h" +#include + +struct lightningd_state; +struct txwatch; + +size_t get_tx_depth(struct lightningd_state *dstate, const struct txwatch *w); +void setup_topology(struct lightningd_state *dstate); + +#endif /* LIGHTNING_DAEMON_CRYPTOPKT_H */ diff --git a/daemon/lightningd.c b/daemon/lightningd.c index b7629f95b..1448fb50e 100644 --- a/daemon/lightningd.c +++ b/daemon/lightningd.c @@ -1,4 +1,5 @@ #include "bitcoind.h" +#include "chaintopology.h" #include "configdir.h" #include "controlled_time.h" #include "jsonrpc.h" @@ -276,6 +277,9 @@ int main(int argc, char *argv[]) /* Create timer to do watches. */ setup_watch_timer(dstate); + /* Initialize block topology. */ + setup_topology(dstate); + /* Make sure we use the artificially-controlled time for timers */ io_time_override(controlled_time); diff --git a/daemon/lightningd.h b/daemon/lightningd.h index 9ea995a9f..c3bc201bf 100644 --- a/daemon/lightningd.h +++ b/daemon/lightningd.h @@ -62,6 +62,9 @@ struct lightningd_state { /* Any pending timers. */ struct timers timers; + /* Cached block topology. */ + struct topology *topology; + /* Our peers. */ struct list_head peers; diff --git a/daemon/timeout.h b/daemon/timeout.h index 90ffee45f..12c0cdcd9 100644 --- a/daemon/timeout.h +++ b/daemon/timeout.h @@ -2,6 +2,7 @@ #define LIGHTNING_DAEMON_TIMEOUT_H #include "config.h" +#include #include #include #include