bwatch: scan blocks for scriptpubkey and outpoint matches
What changed, and why it matters
This commit adds the actual scanning logic to a new 'bwatch' plugin that watches the Bitcoin blockchain. Before this change, the plugin fetched blocks but never inspected the transactions inside them. Now, after each block is downloaded, it walks through every transaction and checks whether any output matches a watched Bitcoin address (scriptpubkey) or whether any input spends a watched previous transaction output (outpoint). When a match is found, it sends a notification back to the main lightningd process. This is a normal feature-completion commit for a block-watcher plugin; it does not by itself look like a security fix or vulnerability.
Treat as routine feature development. Review the bwatch_send_watch_found() implementation and the watch hash tables for correctness, memory safety, and proper handling of duplicate or stale watches. If this plugin is intended to protect on-chain funds, ensure the scanner cannot miss blocks or transactions during reorgs or fast sync.
Security signals we found
New block scanning logic that touches watched scriptpubkeys and outpoints
Sanity check on watch start_block with LOG_BROKEN logging
No input validation or bounds checks visible beyond existing library helpers
No explicit memory-zeroing or constant-time operations visible in the diff
Plugin now reconstructs spent outpoints from transaction inputs
Evidence from the diff
The change implements bwatch_process_block_txs() and helpers check_scriptpubkey_watches() and check_outpoint_watches(). These iterate over each transaction in a fetched bitcoin_block, perform hash-table lookups against bwatch->scriptpubkey_watches and bwatch->outpoint_watches, and call bwatch_send_watch_found() for matches. It also adds a start_block sanity check that logs a LOG_BROKEN message and skips the match if the watch’s start_block is greater than the current block height. The commit completes the previously stubbed ‘Block scanning layer’ described in the header.
Changed components
plugins/bwatch/bwatch.cplugins/bwatch/bwatch_scanner.cplugins/bwatch/bwatch_scanner.hInspect captured patch +110 / −4
diff --git a/plugins/bwatch/bwatch.c b/plugins/bwatch/bwatch.c
index 72366ae4..3a6b05c0 100644
--- a/plugins/bwatch/bwatch.c
+++ b/plugins/bwatch/bwatch.c
@@ -162,6 +162,9 @@ static struct command_result *handle_block(struct command *cmd,
bwatch_remove_tip(cmd, bwatch);
return fetch_block_handle(cmd, bwatch->current_height + 1);
}
+
+ bwatch_process_block_txs(cmd, bwatch, block, block_height,
+ &blockhash);
}
/* Update state */
diff --git a/plugins/bwatch/bwatch_scanner.c b/plugins/bwatch/bwatch_scanner.c
index 9eff5964..d64c7138 100644
--- a/plugins/bwatch/bwatch_scanner.c
+++ b/plugins/bwatch/bwatch_scanner.c
@@ -1,2 +1,102 @@
#include "config.h"
+#include <bitcoin/tx.h>
+#include <common/utils.h>
+#include <plugins/bwatch/bwatch_interface.h>
#include <plugins/bwatch/bwatch_scanner.h>
+#include <plugins/bwatch/bwatch_store.h>
+#include <plugins/libplugin.h>
+
+/*
+ * ============================================================================
+ * TRANSACTION WATCH CHECKING
+ * ============================================================================
+ */
+
+/* Check all scriptpubkey watches via hash lookup */
+static void check_scriptpubkey_watches(struct command *cmd,
+ struct bwatch *bwatch,
+ const struct bitcoin_tx *tx,
+ u32 blockheight,
+ const struct bitcoin_blkid *blockhash,
+ u32 txindex)
+{
+ struct bitcoin_txid txid;
+
+ bitcoin_txid(tx, &txid);
+
+ for (size_t i = 0; i < tx->wtx->num_outputs; i++) {
+ struct watch *w;
+ struct scriptpubkey k = {
+ .script = tx->wtx->outputs[i].script,
+ .len = tx->wtx->outputs[i].script_len
+ };
+
+ w = scriptpubkey_watches_get(bwatch->scriptpubkey_watches, &k);
+ if (!w)
+ continue;
+ if (w->start_block != UINT32_MAX
+ && blockheight < w->start_block) {
+ plugin_log(cmd->plugin, LOG_BROKEN,
+ "Watch for script %s on height >= %u found on block %u???",
+ tal_hexstr(tmpctx, k.script, k.len),
+ w->start_block, blockheight);
+ continue;
+ }
+ bwatch_send_watch_found(cmd, tx, blockheight, w, txindex, i);
+ }
+}
+
+/* Check all outpoint watches via hash lookup */
+static void check_outpoint_watches(struct command *cmd,
+ struct bwatch *bwatch,
+ const struct bitcoin_tx *tx,
+ u32 blockheight,
+ const struct bitcoin_blkid *blockhash,
+ u32 txindex)
+{
+ for (size_t i = 0; i < tx->wtx->num_inputs; i++) {
+ struct watch *w;
+ struct bitcoin_outpoint outpoint;
+
+ bitcoin_tx_input_get_txid(tx, i, &outpoint.txid);
+ outpoint.n = tx->wtx->inputs[i].index;
+
+ w = outpoint_watches_get(bwatch->outpoint_watches, &outpoint);
+ if (!w)
+ continue;
+ if (w->start_block != UINT32_MAX
+ && blockheight < w->start_block) {
+ plugin_log(cmd->plugin, LOG_BROKEN,
+ "Watch for outpoint %s on height >= %u found on block %u???",
+ fmt_bitcoin_outpoint(tmpctx, &outpoint),
+ w->start_block, blockheight);
+ continue;
+ }
+ bwatch_send_watch_found(cmd, tx, blockheight, w, txindex, i);
+ }
+}
+
+/* Check a tx against all watches (during normal block processing).
+ * UTXO spend tracking is handled by lightningd via outpoint watches
+ * (wallet/utxo/<outpoint> fires wallet_utxo_spent_watch_found). */
+static void check_tx_against_all_watches(struct command *cmd,
+ struct bwatch *bwatch,
+ const struct bitcoin_tx *tx,
+ u32 blockheight,
+ const struct bitcoin_blkid *blockhash,
+ u32 txindex)
+{
+ check_scriptpubkey_watches(cmd, bwatch, tx, blockheight, blockhash, txindex);
+ check_outpoint_watches(cmd, bwatch, tx, blockheight, blockhash, txindex);
+}
+
+void bwatch_process_block_txs(struct command *cmd,
+ struct bwatch *bwatch,
+ const struct bitcoin_block *block,
+ u32 blockheight,
+ const struct bitcoin_blkid *blockhash)
+{
+ for (size_t i = 0; i < tal_count(block->tx); i++)
+ check_tx_against_all_watches(cmd, bwatch, block->tx[i],
+ blockheight, blockhash, i);
+}
diff --git a/plugins/bwatch/bwatch_scanner.h b/plugins/bwatch/bwatch_scanner.h
index ac4e62d1..3d52c063 100644
--- a/plugins/bwatch/bwatch_scanner.h
+++ b/plugins/bwatch/bwatch_scanner.h
@@ -4,9 +4,12 @@
#include "config.h"
#include <plugins/bwatch/bwatch.h>
-/* Block scanning layer for bwatch.
- *
- * Subsequent commits add per-watch-type matchers that walk a block's
- * transactions and fire watch_found notifications back to lightningd. */
+/* Scan every transaction in a block against the active scriptpubkey
+ * and outpoint watches, firing watch_found for each match. */
+void bwatch_process_block_txs(struct command *cmd,
+ struct bwatch *bwatch,
+ const struct bitcoin_block *block,
+ u32 blockheight,
+ const struct bitcoin_blkid *blockhash);
#endif /* LIGHTNING_PLUGINS_BWATCH_BWATCH_SCANNER_H */
Why this scored 24/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.