bwatch: gate chain polling behind --experimental-bwatch
What changed, and why it matters
This commit adds an opt-in flag called --experimental-bwatch. The bwatch plugin still loads, but it will not poll the Bitcoin blockchain or process any chain watches unless the user explicitly enables the new flag. It is a feature-gating change, not a fix for an active security bug.
No security action required. Reviewers may want to confirm that bwatch RPCs behave safely when the plugin is loaded but inert, and that documentation clearly states the experimental status.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces a boolean experimental field to the bwatch state and a plugin option –experimental-bwatch. In init(), if experimental is false, the plugin returns early before replaying persisted block history or starting the chain poll timer. The plugin remains registered so its RPCs and options are available, but it stays inert by default. Tests are updated to pass the new flag so existing bwatch test coverage continues to work.
Changed components
plugins/bwatch/bwatch.cplugins/bwatch/bwatch.hdoc/lightningd-config.5.mdtests/test_plugin.pyInspect captured patch +27 / −2
diff --git a/doc/lightningd-config.5.md b/doc/lightningd-config.5.md
index 847a0757..d3a98b1c 100644
--- a/doc/lightningd-config.5.md
+++ b/doc/lightningd-config.5.md
@@ -583,6 +583,11 @@ command, so they invoices can also be paid onchain.
Delay between polls for new blocks from `bitcoind` (default: 30000).
+* **experimental-bwatch** [plugin `bwatch`]
+
+ Enable the experimental *bwatch* chain watcher. Without this, the
+ plugin stays loaded but does not poll `bitcoind` or process watches.
+
### Networking options
Note that for simple setups, the implicit *autolisten* option does the
diff --git a/plugins/bwatch/bwatch.c b/plugins/bwatch/bwatch.c
index 4e548608..da373a6f 100644
--- a/plugins/bwatch/bwatch.c
+++ b/plugins/bwatch/bwatch.c
@@ -448,6 +448,16 @@ static const char *init(struct command *cmd,
bwatch->block_history = tal_arr(bwatch, struct block_record_wire, 0);
+ /* Default to "no chain seen yet"; bwatch_load_block_history will
+ * overwrite these from the datastore when we're enabled. */
+ bwatch->current_height = 0;
+ memset(&bwatch->current_blockhash, 0, sizeof(bwatch->current_blockhash));
+
+ /* bwatch is opt-in: leave the plugin loaded but skip chain polling until the
+ * user passes --experimental-bwatch. */
+ if (!bwatch->experimental)
+ return NULL;
+
/* Replay persisted block history. load_block_history sets
* current_height / current_blockhash from the most recent record;
* if there are no records, fall back to zero so the first poll
@@ -481,12 +491,18 @@ int main(int argc, char *argv[])
setup_locale();
bwatch = tal(NULL, struct bwatch);
bwatch->poll_interval_ms = 30000;
+ bwatch->experimental = false;
plugin_main(argv, init, take(bwatch), PLUGIN_RESTARTABLE, true, NULL,
commands, ARRAY_SIZE(commands),
NULL, 0,
NULL, 0,
NULL, 0,
+ plugin_option("experimental-bwatch", "flag",
+ "experimental: enable the bwatch chain"
+ " watcher (off by default)",
+ flag_option, flag_jsonfmt,
+ &bwatch->experimental),
plugin_option("bwatch-poll-interval", "int",
"Milliseconds between chain polls (default: 30000)",
u32_option, u32_jsonfmt, &bwatch->poll_interval_ms),
diff --git a/plugins/bwatch/bwatch.h b/plugins/bwatch/bwatch.h
index ab60286a..3fe886b0 100644
--- a/plugins/bwatch/bwatch.h
+++ b/plugins/bwatch/bwatch.h
@@ -70,6 +70,10 @@ struct bwatch {
/* Active poll timer; rescheduled at the end of every poll cycle. */
struct plugin_timer *poll_timer;
u32 poll_interval_ms;
+
+ /* Opt-in: bwatch is loaded but stays inert (no chain polling, no
+ * watch processing) unless the user passes --experimental-bwatch. */
+ bool experimental;
};
/* Helper: get last block_history (or NULL) */
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 679b8e0b..e224e363 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -32,8 +32,8 @@ import sys
import time
import unittest
-# Fast bwatch polling for tests (plugin default is 30s). Pass explicitly per node.
-BWATCH_OPTS = {'bwatch-poll-interval': 500}
+# bwatch is opt-in (--experimental-bwatch); also speed up polling for tests.
+BWATCH_OPTS = {'experimental-bwatch': None, 'bwatch-poll-interval': 500}
def wait_bwatch_caught_up(node, timeout=TIMEOUT):
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.