Browse Source
This allows us to track precise transaction depth ourselves, particularly in the case of branching. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>ppa-0.6.1
Rusty Russell
9 years ago
6 changed files with 451 additions and 0 deletions
@ -0,0 +1,429 @@ |
|||||
|
#include "bitcoind.h" |
||||
|
#include "chaintopology.h" |
||||
|
#include "lightningd.h" |
||||
|
#include "log.h" |
||||
|
#include "timeout.h" |
||||
|
#include "watch.h" |
||||
|
#include <ccan/ptrint/ptrint.h> |
||||
|
#include <ccan/structeq/structeq.h> |
||||
|
|
||||
|
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); |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
#ifndef LIGHTNING_DAEMON_CHAINTOPOLOGY_H |
||||
|
#define LIGHTNING_DAEMON_CHAINTOPOLOGY_H |
||||
|
#include "config.h" |
||||
|
#include <stddef.h> |
||||
|
|
||||
|
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 */ |
Loading…
Reference in new issue