bwatch: add typed hash tables for watches
What changed, and why it matters
This commit is a routine internal refactor of a new plugin called bwatch. It adds typed hash tables so the plugin can quickly look up different kinds of blockchain 'watches' (things it monitors on the Bitcoin blockchain). There is no user-facing change, no bug fix, and no security-sensitive behavior introduced.
No security action required. Review as normal code-quality change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces four HTABLE_DEFINE_NODUPS_TYPE hash tables in plugins/bwatch for scriptpubkey, outpoint, scid, and blockdepth watches, along with key/hash/equality helpers and dispatch functions. It is foundational data-structure code for the bwatch block-scanner plugin. No parsing, RPC, cryptographic, or permission logic is changed; no vulnerability is evident in the diff.
Changed components
plugins/bwatch/bwatch.cplugins/bwatch/bwatch.hplugins/bwatch/bwatch_store.cplugins/bwatch/bwatch_store.hInspect captured patch +254 / −9
diff --git a/plugins/bwatch/bwatch.c b/plugins/bwatch/bwatch.c
index 0c3935a8..9ab46eed 100644
--- a/plugins/bwatch/bwatch.c
+++ b/plugins/bwatch/bwatch.c
@@ -1,5 +1,6 @@
#include "config.h"
#include <ccan/array_size/array_size.h>
+#include <common/memleak.h>
#include <plugins/bwatch/bwatch.h>
#include <plugins/bwatch/bwatch_interface.h>
#include <plugins/bwatch/bwatch_scanner.h>
@@ -17,6 +18,12 @@ static const char *init(struct command *cmd,
struct bwatch *bwatch = bwatch_of(cmd->plugin);
bwatch->plugin = cmd->plugin;
+
+ bwatch->scriptpubkey_watches = new_htable(bwatch, scriptpubkey_watches);
+ bwatch->outpoint_watches = new_htable(bwatch, outpoint_watches);
+ bwatch->scid_watches = new_htable(bwatch, scid_watches);
+ bwatch->blockdepth_watches = new_htable(bwatch, blockdepth_watches);
+
return NULL;
}
diff --git a/plugins/bwatch/bwatch.h b/plugins/bwatch/bwatch.h
index f307e09e..1052ddc3 100644
--- a/plugins/bwatch/bwatch.h
+++ b/plugins/bwatch/bwatch.h
@@ -2,16 +2,57 @@
#define LIGHTNING_PLUGINS_BWATCH_BWATCH_H
#include "config.h"
+#include <bitcoin/short_channel_id.h>
+#include <bitcoin/tx.h>
#include <plugins/libplugin.h>
+#include <wire/wire.h>
+
+/* Forward declare hash table types (defined in bwatch_store.h) */
+struct scriptpubkey_watches;
+struct outpoint_watches;
+struct scid_watches;
+struct blockdepth_watches;
+
+/* Watch type discriminator. */
+enum watch_type {
+ WATCH_SCRIPTPUBKEY,
+ WATCH_OUTPOINT,
+ WATCH_SCID,
+ WATCH_BLOCKDEPTH,
+};
+
+/* Scriptpubkey wrapper: tal-allocated bytes don't carry a length, so we
+ * keep them in a struct with an explicit length for hashing/equality. */
+struct scriptpubkey {
+ const u8 *script;
+ size_t len;
+};
+
+/* A single watch: one key plus the set of owner ids that registered it. */
+struct watch {
+ enum watch_type type;
+ u32 start_block;
+ wirestring **owners;
+ union {
+ struct scriptpubkey scriptpubkey;
+ struct bitcoin_outpoint outpoint;
+ struct short_channel_id scid;
+ } key;
+};
/* Main bwatch state.
*
- * bwatch is an out-of-process block scanner: it polls bitcoind, parses each
- * new block, and notifies lightningd (via the watchman RPCs) about chain
- * activity that lightningd has registered watches for. Subsequent commits
- * add the watch hash tables, block history, and polling timer fields. */
+ * The four watch hash tables are typed (see bwatch_store.h) so each
+ * lookup hits the right key shape (script bytes / outpoint / scid /
+ * confirm-height) without dispatching on type at every call site. */
struct bwatch {
struct plugin *plugin;
+
+ struct scriptpubkey_watches *scriptpubkey_watches;
+ struct outpoint_watches *outpoint_watches;
+ struct scid_watches *scid_watches;
+ struct blockdepth_watches *blockdepth_watches;
+
u32 poll_interval_ms;
};
diff --git a/plugins/bwatch/bwatch_store.c b/plugins/bwatch/bwatch_store.c
index 2d7742e2..68ba5baa 100644
--- a/plugins/bwatch/bwatch_store.c
+++ b/plugins/bwatch/bwatch_store.c
@@ -1,2 +1,150 @@
#include "config.h"
+#include <ccan/crypto/siphash24/siphash24.h>
+#include <ccan/mem/mem.h>
#include <plugins/bwatch/bwatch_store.h>
+
+const struct scriptpubkey *scriptpubkey_watch_keyof(const struct watch *w)
+{
+ assert(w->type == WATCH_SCRIPTPUBKEY);
+ return &w->key.scriptpubkey;
+}
+
+size_t scriptpubkey_hash(const struct scriptpubkey *scriptpubkey)
+{
+ return siphash24(siphash_seed(), scriptpubkey->script, scriptpubkey->len);
+}
+
+bool scriptpubkey_watch_eq(const struct watch *w, const struct scriptpubkey *scriptpubkey)
+{
+ return w->key.scriptpubkey.len == scriptpubkey->len &&
+ memeq(w->key.scriptpubkey.script, scriptpubkey->len,
+ scriptpubkey->script, scriptpubkey->len);
+}
+
+const struct bitcoin_outpoint *outpoint_watch_keyof(const struct watch *w)
+{
+ assert(w->type == WATCH_OUTPOINT);
+ return &w->key.outpoint;
+}
+
+size_t outpoint_hash(const struct bitcoin_outpoint *outpoint)
+{
+ size_t h1 = siphash24(siphash_seed(), &outpoint->txid, sizeof(outpoint->txid));
+ size_t h2 = siphash24(siphash_seed(), &outpoint->n, sizeof(outpoint->n));
+ return h1 ^ h2;
+}
+
+bool outpoint_watch_eq(const struct watch *w, const struct bitcoin_outpoint *outpoint)
+{
+ return bitcoin_outpoint_eq(&w->key.outpoint, outpoint);
+}
+
+const struct short_channel_id *scid_watch_keyof(const struct watch *w)
+{
+ assert(w->type == WATCH_SCID);
+ return &w->key.scid;
+}
+
+size_t scid_hash(const struct short_channel_id *scid)
+{
+ return siphash24(siphash_seed(), scid, sizeof(*scid));
+}
+
+bool scid_watch_eq(const struct watch *w, const struct short_channel_id *scid)
+{
+ return short_channel_id_eq(w->key.scid, *scid);
+}
+
+const u32 *blockdepth_watch_keyof(const struct watch *w)
+{
+ assert(w->type == WATCH_BLOCKDEPTH);
+ return &w->start_block;
+}
+
+size_t u32_hash(const u32 *height)
+{
+ return siphash24(siphash_seed(), height, sizeof(*height));
+}
+
+bool blockdepth_watch_eq(const struct watch *w, const u32 *height)
+{
+ return w->start_block == *height;
+}
+
+const char *bwatch_get_watch_type_name(enum watch_type type)
+{
+ switch (type) {
+ case WATCH_SCRIPTPUBKEY:
+ return "scriptpubkey";
+ case WATCH_OUTPOINT:
+ return "outpoint";
+ case WATCH_SCID:
+ return "scid";
+ case WATCH_BLOCKDEPTH:
+ return "blockdepth";
+ }
+ abort();
+}
+
+void bwatch_add_watch_to_hash(struct bwatch *bwatch, struct watch *w)
+{
+ switch (w->type) {
+ case WATCH_SCRIPTPUBKEY:
+ scriptpubkey_watches_add(bwatch->scriptpubkey_watches, w);
+ return;
+ case WATCH_OUTPOINT:
+ outpoint_watches_add(bwatch->outpoint_watches, w);
+ return;
+ case WATCH_SCID:
+ scid_watches_add(bwatch->scid_watches, w);
+ return;
+ case WATCH_BLOCKDEPTH:
+ blockdepth_watches_add(bwatch->blockdepth_watches, w);
+ return;
+ }
+ abort();
+}
+
+struct watch *bwatch_get_watch(struct bwatch *bwatch,
+ enum watch_type type,
+ const struct bitcoin_outpoint *outpoint,
+ const u8 *scriptpubkey,
+ const struct short_channel_id *scid,
+ const u32 *confirm_height)
+{
+ switch (type) {
+ case WATCH_SCRIPTPUBKEY: {
+ struct scriptpubkey k = {
+ .script = scriptpubkey,
+ .len = tal_bytelen(scriptpubkey),
+ };
+ return scriptpubkey_watches_get(bwatch->scriptpubkey_watches, &k);
+ }
+ case WATCH_OUTPOINT:
+ return outpoint_watches_get(bwatch->outpoint_watches, outpoint);
+ case WATCH_SCID:
+ return scid_watches_get(bwatch->scid_watches, scid);
+ case WATCH_BLOCKDEPTH:
+ return blockdepth_watches_get(bwatch->blockdepth_watches, confirm_height);
+ }
+ abort();
+}
+
+void bwatch_remove_watch_from_hash(struct bwatch *bwatch, struct watch *w)
+{
+ switch (w->type) {
+ case WATCH_SCRIPTPUBKEY:
+ scriptpubkey_watches_del(bwatch->scriptpubkey_watches, w);
+ return;
+ case WATCH_OUTPOINT:
+ outpoint_watches_del(bwatch->outpoint_watches, w);
+ return;
+ case WATCH_SCID:
+ scid_watches_del(bwatch->scid_watches, w);
+ return;
+ case WATCH_BLOCKDEPTH:
+ blockdepth_watches_del(bwatch->blockdepth_watches, w);
+ return;
+ }
+ abort();
+}
diff --git a/plugins/bwatch/bwatch_store.h b/plugins/bwatch/bwatch_store.h
index 566c0ff8..4ca60049 100644
--- a/plugins/bwatch/bwatch_store.h
+++ b/plugins/bwatch/bwatch_store.h
@@ -2,12 +2,61 @@
#define LIGHTNING_PLUGINS_BWATCH_BWATCH_STORE_H
#include "config.h"
+#include <ccan/htable/htable_type.h>
#include <plugins/bwatch/bwatch.h>
-/* Block-history and watch storage layer for bwatch.
- *
- * Subsequent commits populate this with hash tables for each watch type
- * (scriptpubkey, outpoint, scid, blockdepth) plus the lightningd datastore
- * persistence helpers. */
+/*
+ * Per-watch-type key/hash/eq triplets so HTABLE_DEFINE_NODUPS_TYPE can
+ * generate a typed hash table for each watch type. Lookups then take
+ * the natural key (raw script bytes, bitcoin_outpoint, short_channel_id,
+ * or u32 confirm height) instead of dispatching on type at every call.
+ */
+
+const struct scriptpubkey *scriptpubkey_watch_keyof(const struct watch *w);
+size_t scriptpubkey_hash(const struct scriptpubkey *scriptpubkey);
+bool scriptpubkey_watch_eq(const struct watch *w, const struct scriptpubkey *scriptpubkey);
+
+const struct bitcoin_outpoint *outpoint_watch_keyof(const struct watch *w);
+size_t outpoint_hash(const struct bitcoin_outpoint *outpoint);
+bool outpoint_watch_eq(const struct watch *w, const struct bitcoin_outpoint *outpoint);
+
+const struct short_channel_id *scid_watch_keyof(const struct watch *w);
+size_t scid_hash(const struct short_channel_id *scid);
+bool scid_watch_eq(const struct watch *w, const struct short_channel_id *scid);
+
+const u32 *blockdepth_watch_keyof(const struct watch *w);
+size_t u32_hash(const u32 *height);
+bool blockdepth_watch_eq(const struct watch *w, const u32 *height);
+
+HTABLE_DEFINE_NODUPS_TYPE(struct watch, scriptpubkey_watch_keyof,
+ scriptpubkey_hash, scriptpubkey_watch_eq,
+ scriptpubkey_watches);
+
+HTABLE_DEFINE_NODUPS_TYPE(struct watch, outpoint_watch_keyof,
+ outpoint_hash, outpoint_watch_eq,
+ outpoint_watches);
+
+HTABLE_DEFINE_NODUPS_TYPE(struct watch, scid_watch_keyof,
+ scid_hash, scid_watch_eq,
+ scid_watches);
+
+HTABLE_DEFINE_NODUPS_TYPE(struct watch, blockdepth_watch_keyof,
+ u32_hash, blockdepth_watch_eq,
+ blockdepth_watches);
+
+/* Human-readable name of a watch type, used as the second datastore key
+ * component (e.g. ["bwatch", "scriptpubkey", <hex>]) once persistence
+ * lands in a follow-up commit. */
+const char *bwatch_get_watch_type_name(enum watch_type type);
+
+/* Watch hash table operations: dispatch on watch->type. */
+void bwatch_add_watch_to_hash(struct bwatch *bwatch, struct watch *w);
+struct watch *bwatch_get_watch(struct bwatch *bwatch,
+ enum watch_type type,
+ const struct bitcoin_outpoint *outpoint,
+ const u8 *scriptpubkey,
+ const struct short_channel_id *scid,
+ const u32 *confirm_height);
+void bwatch_remove_watch_from_hash(struct bwatch *bwatch, struct watch *w);
#endif /* LIGHTNING_PLUGINS_BWATCH_BWATCH_STORE_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.