lightningd: add dev option to save hooks and notifications to/from plugins.
What changed, and why it matters
This commit adds a hidden developer-only command-line option called --dev-save-plugin-io. When a developer turns it on, Core Lightning writes copies of plugin hook and notification messages to files in a specified directory. It is a diagnostic/logging feature, not a change to normal node behavior, and it is not enabled by default.
No security action required. Treat as a normal developer/debugging feature. Operators should be aware that enabling --dev-save-plugin-io will write sensitive plugin RPC traffic to disk with 0600 permissions.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces a new OPT_DEV option –dev-save-plugin-io that, when set, causes the daemon to dump JSON-RPC plugin I/O (hook requests/responses and notifications) to uniquely named files under the supplied directory. It adds dev_save_plugin_io_in/out helpers in plugin.c and wires them into plugin_hook.c and plugins_notify(). Files are created with O_CREAT|O_EXCL|O_WRONLY and mode 0600, and the option is gated under developer mode. No production logic is altered.
Changed components
lightningd/options.clightningd/plugin.clightningd/plugin.hlightningd/plugin_hook.cInspect captured patch +82 / −0
diff --git a/lightningd/options.c b/lightningd/options.c
index b55238c5..252fcb54 100644
--- a/lightningd/options.c
+++ b/lightningd/options.c
@@ -931,6 +931,10 @@ static void dev_register_opts(struct lightningd *ld)
opt_set_bool,
&ld->dev_hsmd_warn_on_overgrind,
"Warn if we create signatures that are not exactly 71 bytes.");
+ clnopt_witharg("--dev-save-plugin-io", OPT_DEV,
+ opt_set_charp, opt_show_charp,
+ &ld->plugins->dev_save_io,
+ "Directory to place all plugin notifications/hooks JSON into.");
/* This is handled directly in daemon_developer_mode(), so we ignore it here */
clnopt_noarg("--dev-debug-self", OPT_DEV,
opt_ignore,
diff --git a/lightningd/plugin.c b/lightningd/plugin.c
index ccbdf83a..1dba9a94 100644
--- a/lightningd/plugin.c
+++ b/lightningd/plugin.c
@@ -4,9 +4,11 @@
#include <ccan/crc32c/crc32c.h>
#include <ccan/io/io.h>
#include <ccan/json_escape/json_escape.h>
+#include <ccan/json_out/json_out.h>
#include <ccan/mem/mem.h>
#include <ccan/opt/opt.h>
#include <ccan/pipecmd/pipecmd.h>
+#include <ccan/read_write_all/read_write_all.h>
#include <ccan/tal/path/path.h>
#include <ccan/tal/str/str.h>
#include <ccan/utf8/utf8.h>
@@ -23,6 +25,7 @@
#include <db/exec.h>
#include <dirent.h>
#include <errno.h>
+#include <fcntl.h>
#include <lightningd/io_loop_with_timers.h>
#include <lightningd/notification.h>
#include <lightningd/plugin.h>
@@ -76,6 +79,7 @@ struct plugins *plugins_new(const tal_t *ctx, struct log_book *log_book,
p->plugin_idx = 0;
p->dev_builtin_plugins_unimportant = false;
p->want_db_transaction = true;
+ p->dev_save_io = NULL;
p->subscriptions = tal(p, struct plugin_subscription_htable);
plugin_subscription_htable_init(p->subscriptions);
@@ -2534,6 +2538,10 @@ void plugins_notify(struct plugins *plugins,
if (!plugins)
return;
+ dev_save_plugin_io_out(plugins,
+ "notification_out",
+ n->method, n->stream);
+
for (struct plugin_subscription *sub
= plugin_subscription_htable_getfirst(plugins->subscriptions,
n->method, &it);
@@ -2673,3 +2681,49 @@ void shutdown_plugins(struct lightningd *ld)
}
}
}
+
+static void dev_save_plugin_io(struct plugins *plugins,
+ const char *type,
+ const char *name,
+ const char *buf, size_t len)
+{
+ static size_t counter;
+ const char *file;
+ int fd;
+
+ if (!plugins->dev_save_io)
+ return;
+
+ file = path_join(tmpctx, plugins->dev_save_io,
+ take(tal_fmt(NULL, "%s-%s-%u-%zu",
+ type, name,
+ (unsigned int)getpid(),
+ counter++)));
+ fd = open(file, O_CREAT|O_EXCL|O_WRONLY, 0600);
+ if (fd < 0 || !write_all(fd, buf, len))
+ fatal("Writing --dev-save-plugin-io %s: %s",
+ file, strerror(errno));
+ close(fd);
+}
+
+void dev_save_plugin_io_in(struct plugins *plugins,
+ const char *type,
+ const char *name,
+ const char *buffer,
+ const jsmntok_t *tok)
+{
+ dev_save_plugin_io(plugins, type, name,
+ buffer + tok->start, tok->end - tok->start);
+}
+
+void dev_save_plugin_io_out(struct plugins *plugins,
+ const char *type,
+ const char *name,
+ const struct json_stream *stream)
+{
+ size_t len;
+ const char *buf;
+
+ buf = json_out_contents(stream->jout, &len);
+ dev_save_plugin_io(plugins, type, name, buf, len);
+}
diff --git a/lightningd/plugin.h b/lightningd/plugin.h
index 14904e3f..621f3b8d 100644
--- a/lightningd/plugin.h
+++ b/lightningd/plugin.h
@@ -154,6 +154,9 @@ struct plugins {
/* Whether builtin plugins should be overridden as unimportant. */
bool dev_builtin_plugins_unimportant;
+
+ /* Whether to save all IO to a file */
+ char *dev_save_io;
};
/**
@@ -415,4 +418,17 @@ struct command_result *plugin_set_dynamic_opt(struct command *cmd,
const struct opt_table *,
const char *,
bool));
+
+/* --dev-plugin-save-io */
+void dev_save_plugin_io_in(struct plugins *plugins,
+ const char *type,
+ const char *name,
+ const char *buffer,
+ const jsmntok_t *tok);
+
+void dev_save_plugin_io_out(struct plugins *plugins,
+ const char *type,
+ const char *name,
+ const struct json_stream *stream);
+
#endif /* LIGHTNING_LIGHTNINGD_PLUGIN_H */
diff --git a/lightningd/plugin_hook.c b/lightningd/plugin_hook.c
index b22d1546..cd69b794 100644
--- a/lightningd/plugin_hook.c
+++ b/lightningd/plugin_hook.c
@@ -169,6 +169,9 @@ static void plugin_hook_callback(const char *buffer, const jsmntok_t *toks,
ph_req->hook->name, toks->end - toks->start,
buffer + toks->start);
+ dev_save_plugin_io_in(h->plugin->plugins, "hook_in",
+ ph_req->hook->name,
+ buffer, toks);
if (!ph_req->hook->deserialize_cb(ph_req->cb_arg,
buffer, resulttok)) {
tal_free(ph_req->cb_arg);
@@ -209,6 +212,11 @@ static void plugin_hook_call_next(struct plugin_hook_request *ph_req)
hook->serialize_payload(ph_req->cb_arg, req->stream, plugin);
jsonrpc_request_end(req);
+
+ dev_save_plugin_io_out(plugin->plugins,
+ "hook_out", hook->name,
+ req->stream);
+
plugin_request_send(plugin, req);
}
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.