bwatch: add addwatch/delwatch helpers
What changed, and why it matters
This commit adds two helper functions for managing blockchain watches in a new plugin called bwatch. It lets different parts of the program register interest in a transaction or output, merge duplicate registrations, and remove their interest when done. There is no obvious security bug in the change itself; it is routine infrastructure code.
No immediate security action required. Review the callers (RPC handlers and daemon restart paths) in later commits to confirm owner_id values are trustworthy and that start_block lowering cannot be abused to force expensive rescans.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces bwatch_add_watch() and bwatch_del_watch() in plugins/bwatch/bwatch_store.c. These helpers combine in-memory hash-table updates with persistent datastore writes. add_watch merges owner lists when the same watch key is registered again and lowers start_block if the new registration needs to scan further back. del_watch removes only the requesting owner and deletes the watch once no owners remain. The code uses the project’s normal memory allocation helpers (tal_arr, tal_strdup, tal_free) and logs duplicate/unexpected removals. No input validation, concurrency, or cryptographic logic is present in this diff.
Changed components
plugins/bwatch/bwatch_store.cplugins/bwatch/bwatch_store.hInspect captured patch +133 / −0
diff --git a/plugins/bwatch/bwatch_store.c b/plugins/bwatch/bwatch_store.c
index f9766e0d..27d7687f 100644
--- a/plugins/bwatch/bwatch_store.c
+++ b/plugins/bwatch/bwatch_store.c
@@ -489,3 +489,114 @@ void bwatch_load_watches_from_datastore(struct command *cmd, struct bwatch *bwat
load_watches_by_type(cmd, bwatch, WATCH_SCID);
load_watches_by_type(cmd, bwatch, WATCH_BLOCKDEPTH);
}
+
+/* -1 means "not found" */
+static int find_owner(wirestring **owners, const char *owner_id)
+{
+ for (size_t i = 0; i < tal_count(owners); i++) {
+ if (streq(owners[i], owner_id))
+ return i;
+ }
+ return -1;
+}
+
+struct watch *bwatch_add_watch(struct command *cmd,
+ 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,
+ u32 start_block,
+ const char *owner_id TAKES)
+{
+ struct watch *w = bwatch_get_watch(bwatch, type, outpoint, scriptpubkey,
+ scid, confirm_height);
+
+ if (w) {
+ bool lowered = start_block < w->start_block;
+ bool found_owner = (find_owner(w->owners, owner_id) != -1);
+ if (lowered)
+ w->start_block = start_block;
+ if (!found_owner)
+ tal_arr_expand(&w->owners,
+ tal_strdup(w->owners, owner_id));
+ bwatch_save_watch_to_datastore(cmd, w);
+ /* Always rescan even if owner is already registered: stateless
+ * restarters (e.g. onchaind) re-register on startup and need
+ * missed spend events replayed. */
+ plugin_log(cmd->plugin, LOG_DBG,
+ found_owner
+ ? (lowered
+ ? "Owner %s already watching, lowering start_block to %u"
+ : "Owner %s already watching, rescanning for missed events at %u")
+ : "Owner %s added to existing watch, start_block %u",
+ owner_id, w->start_block);
+ return w;
+ }
+
+ w = tal(bwatch, struct watch);
+ w->type = type;
+ w->start_block = start_block;
+ switch (w->type) {
+ case WATCH_SCRIPTPUBKEY:
+ w->key.scriptpubkey.len = tal_bytelen(scriptpubkey);
+ w->key.scriptpubkey.script = tal_dup_talarr(w, u8, scriptpubkey);
+ break;
+ case WATCH_OUTPOINT:
+ w->key.outpoint = *outpoint;
+ break;
+ case WATCH_SCID:
+ w->key.scid = *scid;
+ break;
+ case WATCH_BLOCKDEPTH:
+ /* confirm_height == start_block for blockdepth watches;
+ * already set from start_block above. */
+ break;
+ }
+ w->owners = tal_arr(w, wirestring *, 1);
+ w->owners[0] = tal_strdup(w->owners, owner_id);
+ bwatch_save_watch_to_datastore(cmd, w);
+ bwatch_add_watch_to_hash(bwatch, w);
+ return w;
+}
+
+void bwatch_del_watch(struct command *cmd,
+ 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,
+ const char *owner_id)
+{
+ struct watch *w = bwatch_get_watch(bwatch, type, outpoint, scriptpubkey,
+ scid, confirm_height);
+ int owner_off;
+
+ if (!w) {
+ plugin_log(cmd->plugin, LOG_DBG,
+ "Attempted to remove non-existent %s watch (already gone)",
+ bwatch_get_watch_type_name(type));
+ return;
+ }
+
+ owner_off = find_owner(w->owners, owner_id);
+ if (owner_off < 0) {
+ plugin_log(cmd->plugin, LOG_BROKEN,
+ "Attempted to remove watch for owner %s but it wasn't watching",
+ owner_id);
+ return;
+ }
+
+ tal_free(w->owners[owner_off]);
+ tal_arr_remove(&w->owners, owner_off);
+
+ if (tal_count(w->owners) == 0) {
+ bwatch_delete_watch_from_datastore(cmd, w);
+ bwatch_remove_watch_from_hash(bwatch, w);
+ tal_free(w);
+ } else {
+ bwatch_save_watch_to_datastore(cmd, w);
+ }
+}
diff --git a/plugins/bwatch/bwatch_store.h b/plugins/bwatch/bwatch_store.h
index 90131e36..194d2f03 100644
--- a/plugins/bwatch/bwatch_store.h
+++ b/plugins/bwatch/bwatch_store.h
@@ -77,4 +77,26 @@ void bwatch_save_watch_to_datastore(struct command *cmd, const struct watch *w);
void bwatch_delete_watch_from_datastore(struct command *cmd, const struct watch *w);
void bwatch_load_watches_from_datastore(struct command *cmd, struct bwatch *bwatch);
+/* High-level add/del that combine hash-table updates and datastore writes,
+ * and merge owner sets / lower start_block when the same key is registered
+ * multiple times. */
+struct watch *bwatch_add_watch(struct command *cmd,
+ 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,
+ u32 start_block,
+ const char *owner_id TAKES);
+
+void bwatch_del_watch(struct command *cmd,
+ 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,
+ const char *owner_id);
+
#endif /* LIGHTNING_PLUGINS_BWATCH_BWATCH_STORE_H */
Why this scored 12/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.