bwatch: thread per-watch parameter through block scanning
What changed, and why it matters
This commit refactors a Core Lightning plugin so that block scanning can be limited to a single user-provided watch instead of checking every active watch. It is a preparatory change for adding a 'rescan' feature and does not, by itself, change any externally reachable behavior. There is no indication it fixes a security bug or introduces a vulnerability.
No immediate security action required. Treat as normal code review for the upcoming rescan feature; verify in follow-up commits that the rescan caller validates the watch pointer and block range before invoking the single-watch path.
Security signals we found
No security-relevant keywords in commit title or message
Refactoring only: existing all-watches path unchanged
New single-watch path is not invoked by any caller in this commit
No input validation changes or trust-boundary crossings
No memory-management changes beyond local iterators and comparisons
Evidence from the diff
The commit adds a const struct watch *w parameter to bwatch_process_block_txs and bwatch_check_scid_watches. When w is NULL the existing all-watches scan behavior is preserved; when non-NULL only that watch is evaluated. New helper functions (check_tx_scriptpubkey, check_tx_outpoint, check_tx_for_single_watch) implement the single-watch path using the same matching logic as the existing all-watches code. The public header is updated accordingly. No callers pass a non-NULL watch in this commit, so the new path is not yet exercised.
Changed components
plugins/bwatch/bwatch.cplugins/bwatch/bwatch_scanner.cplugins/bwatch/bwatch_scanner.hInspect captured patch +105 / −13
diff --git a/plugins/bwatch/bwatch.c b/plugins/bwatch/bwatch.c
index 9d5571fe..b5fd7275 100644
--- a/plugins/bwatch/bwatch.c
+++ b/plugins/bwatch/bwatch.c
@@ -168,7 +168,7 @@ static struct command_result *handle_block(struct command *cmd,
* fire for the same block. */
bwatch_check_blockdepth_watches(cmd, bwatch, block_height);
bwatch_process_block_txs(cmd, bwatch, block, block_height,
- &blockhash);
+ &blockhash, NULL);
}
/* Update state */
diff --git a/plugins/bwatch/bwatch_scanner.c b/plugins/bwatch/bwatch_scanner.c
index 6f268c96..46619dae 100644
--- a/plugins/bwatch/bwatch_scanner.c
+++ b/plugins/bwatch/bwatch_scanner.c
@@ -1,5 +1,6 @@
#include "config.h"
#include <bitcoin/tx.h>
+#include <ccan/mem/mem.h>
#include <common/utils.h>
#include <plugins/bwatch/bwatch_interface.h>
#include <plugins/bwatch/bwatch_scanner.h>
@@ -90,6 +91,74 @@ static void check_tx_against_all_watches(struct command *cmd,
check_outpoint_watches(cmd, bwatch, tx, blockheight, blockhash, txindex);
}
+/* Check tx outputs against a single scriptpubkey watch (rescan path). */
+static void check_tx_scriptpubkey(struct command *cmd,
+ const struct bitcoin_tx *tx,
+ const struct watch *w,
+ u32 blockheight,
+ const struct bitcoin_blkid *blockhash,
+ u32 txindex)
+{
+ for (size_t i = 0; i < tx->wtx->num_outputs; i++) {
+ if (memeq(tx->wtx->outputs[i].script,
+ tx->wtx->outputs[i].script_len,
+ w->key.scriptpubkey.script,
+ w->key.scriptpubkey.len)) {
+ bwatch_send_watch_found(cmd, tx, blockheight, w,
+ txindex, i);
+ /* Same scriptpubkey may appear in multiple outputs. */
+ }
+ }
+}
+
+/* Check tx inputs against a single outpoint watch (rescan path). */
+static void check_tx_outpoint(struct command *cmd,
+ const struct bitcoin_tx *tx,
+ const struct watch *w,
+ u32 blockheight,
+ const struct bitcoin_blkid *blockhash,
+ u32 txindex)
+{
+ for (size_t i = 0; i < tx->wtx->num_inputs; i++) {
+ struct bitcoin_outpoint outpoint;
+
+ bitcoin_tx_input_get_txid(tx, i, &outpoint.txid);
+ outpoint.n = tx->wtx->inputs[i].index;
+
+ if (bitcoin_outpoint_eq(&outpoint, &w->key.outpoint)) {
+ bwatch_send_watch_found(cmd, tx, blockheight, w,
+ txindex, i);
+ return; /* an outpoint can only be spent once */
+ }
+ }
+}
+
+/* Dispatch a single watch against one tx (rescan path). */
+static void check_tx_for_single_watch(struct command *cmd,
+ const struct watch *w,
+ const struct bitcoin_tx *tx,
+ u32 blockheight,
+ const struct bitcoin_blkid *blockhash,
+ u32 txindex)
+{
+ switch (w->type) {
+ case WATCH_SCRIPTPUBKEY:
+ check_tx_scriptpubkey(cmd, tx, w, blockheight, blockhash, txindex);
+ break;
+ case WATCH_OUTPOINT:
+ check_tx_outpoint(cmd, tx, w, blockheight, blockhash, txindex);
+ break;
+ case WATCH_SCID:
+ /* scid watches don't scan transactions: txindex is encoded in
+ * the scid key, so bwatch_check_scid_watches handles them
+ * directly at the block level. */
+ break;
+ case WATCH_BLOCKDEPTH:
+ /* blockdepth watches fire per block; no per-tx work. */
+ break;
+ }
+}
+
/* Fire watch_found for a scid watch anchored to this block. */
static void maybe_fire_scid_watch(struct command *cmd,
const struct bitcoin_block *block,
@@ -132,13 +201,18 @@ static void maybe_fire_scid_watch(struct command *cmd,
bwatch_send_watch_found(cmd, tx, blockheight, w, txindex, outnum);
}
-/* Walk every scid watch and fire watch_found for any whose encoded
- * blockheight matches this block. */
-static void check_scid_watches(struct command *cmd,
+void bwatch_check_scid_watches(struct command *cmd,
struct bwatch *bwatch,
const struct bitcoin_block *block,
- u32 blockheight)
+ u32 blockheight,
+ const struct watch *w)
{
+ if (w) {
+ if (w->type == WATCH_SCID)
+ maybe_fire_scid_watch(cmd, block, blockheight, w);
+ return;
+ }
+
struct scid_watches_iter it;
struct watch *scid_w;
@@ -153,13 +227,19 @@ void bwatch_process_block_txs(struct command *cmd,
struct bwatch *bwatch,
const struct bitcoin_block *block,
u32 blockheight,
- const struct bitcoin_blkid *blockhash)
+ const struct bitcoin_blkid *blockhash,
+ const struct watch *w)
{
- for (size_t i = 0; i < tal_count(block->tx); i++)
- check_tx_against_all_watches(cmd, bwatch, block->tx[i],
- blockheight, blockhash, i);
+ for (size_t i = 0; i < tal_count(block->tx); i++) {
+ if (w)
+ check_tx_for_single_watch(cmd, w, block->tx[i],
+ blockheight, blockhash, i);
+ else
+ check_tx_against_all_watches(cmd, bwatch, block->tx[i],
+ blockheight, blockhash, i);
+ }
- check_scid_watches(cmd, bwatch, block, blockheight);
+ bwatch_check_scid_watches(cmd, bwatch, block, blockheight, w);
}
/* Fire depth notifications for every active blockdepth watch.
diff --git a/plugins/bwatch/bwatch_scanner.h b/plugins/bwatch/bwatch_scanner.h
index 4769d26c..a8a81f6f 100644
--- a/plugins/bwatch/bwatch_scanner.h
+++ b/plugins/bwatch/bwatch_scanner.h
@@ -4,13 +4,25 @@
#include "config.h"
#include <plugins/bwatch/bwatch.h>
-/* Scan every transaction in a block against the active scriptpubkey
- * and outpoint watches, firing watch_found for each match. */
+/* Scan a block against scriptpubkey and outpoint watches, firing
+ * watch_found for each match. If `w` is NULL all active watches are
+ * checked (normal polling); if non-NULL only that watch is checked
+ * (single-watch rescan). */
void bwatch_process_block_txs(struct command *cmd,
struct bwatch *bwatch,
const struct bitcoin_block *block,
u32 blockheight,
- const struct bitcoin_blkid *blockhash);
+ const struct bitcoin_blkid *blockhash,
+ const struct watch *w);
+
+/* Fire watch_found for scid watches anchored to this block.
+ * w==NULL walks every scid watch (normal polling); w non-NULL
+ * fires only that watch (single-watch rescan). */
+void bwatch_check_scid_watches(struct command *cmd,
+ struct bwatch *bwatch,
+ const struct bitcoin_block *block,
+ u32 blockheight,
+ const struct watch *w);
/* Fire depth notifications for every active blockdepth watch at
* new_height. Called once per new block on the happy path. */
Why this scored 19/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.