lightningd: support watch-for-blockdepth primitive.
What changed, and why it matters
This commit adds a new internal building block that lets Core Lightning watch for blockchain reorganizations and track how many blocks have been added on top of a specific block height. It is purely a new infrastructure feature; there is no bug fix, no reported vulnerability, and no user-facing change described.
No security action needed. Treat as normal feature/infrastructure code; review callers when they are added later to ensure reorg callbacks handle edge cases correctly.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces a ‘blockdepthwatch’ primitive in lightningd/watch.c and watch.h, plus registration in chaintopology.c/h. It allows other parts of the daemon to register callbacks that fire when a target block height gets deeper (watch_check_block_added) or when that height is removed in a reorg (watch_check_block_removed). The implementation uses a new htable keyed by u32 block height, supports duplicate entries, and uses tal destructors for cleanup. No callers are added in this commit.
Changed components
lightningd/chaintopology.clightningd/chaintopology.hlightningd/watch.clightningd/watch.hInspect captured patch +129 / −0
diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c
index 9e9ea0fc..05e356c6 100644
--- a/lightningd/chaintopology.c
+++ b/lightningd/chaintopology.c
@@ -855,6 +855,9 @@ static void updates_complete(struct chain_topology *topo)
/* Tell lightningd about new block. */
notify_new_block(topo->bitcoind->ld);
+ /* Tell blockdepth watchers */
+ watch_check_block_added(topo, topo->tip->height);
+
/* Tell watch code to re-evaluate all txs. */
watch_topology_changed(topo);
@@ -1062,6 +1065,9 @@ static void remove_tip(struct chain_topology *topo)
/* This may have unconfirmed txs: reconfirm as we add blocks. */
watch_for_utxo_reconfirmation(topo, topo->ld->wallet);
+ /* Anyone watching for block removes */
+ watch_check_block_removed(topo, b->height);
+
block_map_del(topo->block_map, b);
/* These no longer exist, so gossipd drops any reference to them just
@@ -1236,6 +1242,7 @@ struct chain_topology *new_topology(struct lightningd *ld, struct logger *log)
topo->txwatches = new_htable(topo, txwatch_hash);
topo->txowatches = new_htable(topo, txowatch_hash);
topo->scriptpubkeywatches = new_htable(topo, scriptpubkeywatch_hash);
+ topo->blockdepthwatches = new_htable(topo, blockdepthwatch_hash);
topo->log = log;
topo->bitcoind = new_bitcoind(topo, ld, log);
topo->poll_seconds = 30;
diff --git a/lightningd/chaintopology.h b/lightningd/chaintopology.h
index e9721775..451113cd 100644
--- a/lightningd/chaintopology.h
+++ b/lightningd/chaintopology.h
@@ -141,6 +141,7 @@ struct chain_topology {
struct txwatch_hash *txwatches;
struct txowatch_hash *txowatches;
struct scriptpubkeywatch_hash *scriptpubkeywatches;
+ struct blockdepthwatch_hash *blockdepthwatches;
/* The number of headers known to the bitcoin backend at startup. Not
* updated after the initial check. */
diff --git a/lightningd/watch.c b/lightningd/watch.c
index 93712fcf..eada9fa0 100644
--- a/lightningd/watch.c
+++ b/lightningd/watch.c
@@ -469,3 +469,92 @@ bool watch_check_tx_outputs(const struct chain_topology *topo,
return tx_interesting;
}
+
+struct blockdepthwatch {
+ u32 height;
+ enum watch_result (*depthcb)(struct lightningd *ld,
+ u32 depth,
+ void *);
+ enum watch_result (*reorgcb)(struct lightningd *ld,
+ void *);
+ void *arg;
+};
+
+u32 blockdepthwatch_keyof(const struct blockdepthwatch *w)
+{
+ return w->height;
+}
+
+size_t u32_hash(u32 val)
+{
+ return siphash24(siphash_seed(), &val, sizeof(val));
+}
+
+bool blockdepthwatch_eq(const struct blockdepthwatch *w, u32 height)
+{
+ return w->height == height;
+}
+
+static void destroy_blockdepthwatch(struct blockdepthwatch *w, struct chain_topology *topo)
+{
+ blockdepthwatch_hash_del(topo->blockdepthwatches, w);
+}
+
+void watch_blockdepth_(const tal_t *ctx,
+ struct chain_topology *topo,
+ u32 blockheight,
+ enum watch_result (*depthcb)(struct lightningd *ld, u32 depth, void *),
+ enum watch_result (*reorgcb)(struct lightningd *ld, void *),
+ void *arg)
+{
+ struct blockdepthwatch *w = tal(ctx, struct blockdepthwatch);
+ w->height = blockheight;
+ w->depthcb = depthcb;
+ w->reorgcb = reorgcb;
+ w->arg = arg;
+ blockdepthwatch_hash_add(topo->blockdepthwatches, w);
+ tal_add_destructor2(w, destroy_blockdepthwatch, topo);
+}
+
+void watch_check_block_added(const struct chain_topology *topo, u32 blockheight)
+{
+ struct blockdepthwatch_hash_iter it;
+
+ /* With ccan/htable, deleting during iteration is safe: adding isn't! */
+ blockdepthwatch_hash_lock(topo->blockdepthwatches);
+ for (struct blockdepthwatch *w = blockdepthwatch_hash_first(topo->blockdepthwatches, &it);
+ w;
+ w = blockdepthwatch_hash_next(topo->blockdepthwatches, &it)) {
+ /* You are not supposed to watch future blocks! */
+ assert(blockheight >= w->height);
+
+ u32 depth = blockheight - w->height + 1;
+ enum watch_result r = w->depthcb(topo->ld, depth, w->arg);
+
+ switch (r) {
+ case DELETE_WATCH:
+ tal_free(w);
+ continue;
+ case KEEP_WATCHING:
+ continue;
+ }
+ fatal("blockdepthwatch depth callback %p returned %i", w->depthcb, r);
+ }
+ blockdepthwatch_hash_unlock(topo->blockdepthwatches);
+}
+
+void watch_check_block_removed(const struct chain_topology *topo, u32 blockheight)
+{
+ struct blockdepthwatch_hash_iter it;
+
+ /* With ccan/htable, deleting during iteration is safe. */
+ blockdepthwatch_hash_lock(topo->blockdepthwatches);
+ for (struct blockdepthwatch *w = blockdepthwatch_hash_getfirst(topo->blockdepthwatches, blockheight, &it);
+ w;
+ w = blockdepthwatch_hash_getnext(topo->blockdepthwatches, blockheight, &it)) {
+ enum watch_result r = w->reorgcb(topo->ld, w->arg);
+ assert(r == DELETE_WATCH);
+ tal_free(w);
+ }
+ blockdepthwatch_hash_unlock(topo->blockdepthwatches);
+}
diff --git a/lightningd/watch.h b/lightningd/watch.h
index 57ceb149..bd371ef6 100644
--- a/lightningd/watch.h
+++ b/lightningd/watch.h
@@ -13,6 +13,7 @@ struct txlocator;
struct txowatch;
struct txwatch;
struct scriptpubkeywatch;
+struct blockdepthwatch;
enum watch_result {
DELETE_WATCH = -1,
@@ -37,6 +38,12 @@ bool scriptpubkeywatch_eq(const struct scriptpubkeywatch *w, const struct script
HTABLE_DEFINE_DUPS_TYPE(struct scriptpubkeywatch, scriptpubkeywatch_keyof, script_with_len_hash, scriptpubkeywatch_eq,
scriptpubkeywatch_hash);
+u32 blockdepthwatch_keyof(const struct blockdepthwatch *w);
+size_t u32_hash(u32);
+bool blockdepthwatch_eq(const struct blockdepthwatch *w, u32 height);
+HTABLE_DEFINE_DUPS_TYPE(struct blockdepthwatch, blockdepthwatch_keyof, u32_hash, blockdepthwatch_eq,
+ blockdepthwatch_hash);
+
struct txwatch *watch_txid_(const tal_t *ctx,
struct chain_topology *topo,
const struct bitcoin_txid *txid,
@@ -147,11 +154,36 @@ bool unwatch_scriptpubkey_(const tal_t *ctx,
const struct txlocator *), \
(arg))
+/* Watch for this block getting deeper (or reorged out) */
+void watch_blockdepth_(const tal_t *ctx,
+ struct chain_topology *topo,
+ u32 blockheight,
+ enum watch_result (*depthcb)(struct lightningd *ld, u32 depth, void *),
+ enum watch_result (*reorgcb)(struct lightningd *ld, void *),
+ void *arg);
+
+#define watch_blockdepth(ctx, topo, blockheight, depthcb, reorgcb, arg) \
+ watch_blockdepth_((ctx), (topo), (blockheight), \
+ typesafe_cb_preargs(enum watch_result, void *, \
+ (depthcb), (arg), \
+ struct lightningd *, \
+ u32), \
+ typesafe_cb_preargs(enum watch_result, void *, \
+ (reorgcb), (arg), \
+ struct lightningd *), \
+ (arg))
+
/* Call any scriptpubkey callbacks for this tx */
bool watch_check_tx_outputs(const struct chain_topology *topo,
const struct txlocator *loc,
const struct bitcoin_tx *tx,
const struct bitcoin_txid *txid);
+/* Call anyone watching for block height increases. */
+void watch_check_block_added(const struct chain_topology *topo, u32 blockheight);
+
+/* Call anyone watching for block removals. */
+void watch_check_block_removed(const struct chain_topology *topo, u32 blockheight);
+
void watch_topology_changed(struct chain_topology *topo);
#endif /* LIGHTNING_LIGHTNINGD_WATCH_H */
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.