Skip to content

Commit 529ae0d

Browse files
rustyrussellcdecker
authored andcommitted
plugins: allow plugins to disable themselves at startup.
By returning 'disable: <reason>' inside getmanifest or init result. Signed-off-by: Rusty Russell <[email protected]> Changelog-Added: plugins: plugins can now disable themselves by returning `disable`, even if marked important.
1 parent fc3e679 commit 529ae0d

File tree

5 files changed

+104
-7
lines changed

5 files changed

+104
-7
lines changed

doc/PLUGINS.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ The `dynamic` indicates if the plugin can be managed after `lightningd`
128128
has been started. Critical plugins that should not be stopped should set it
129129
to false.
130130

131+
If a `disable` member exists, the plugin will be disabled and the contents
132+
of this member is the reason why. This allows plugins to disable themselves
133+
if they are not supported in this configuration.
134+
131135
The `featurebits` object allows the plugin to register featurebits that should be
132136
announced in a number of places in [the protocol][bolt9]. They can be used to signal
133137
support for custom protocol extensions to direct peers, remote nodes and in
@@ -237,10 +241,11 @@ simple JSON object containing the options:
237241
}
238242
```
239243

240-
The plugin must respond to `init` calls, however the response can be
241-
arbitrary and will currently be discarded by `lightningd`. JSON-RPC
242-
commands were chosen over notifications in order not to force plugins
243-
to implement notifications which are not that well supported.
244+
The plugin must respond to `init` calls. The response should be a
245+
valid JSON-RPC response to the `init`, but this is not currently
246+
enforced. If the response is an object containing `result` which
247+
contains `disable` then the plugin will be disabled and the contents
248+
of this member is the reason why.
244249

245250
The `startup` field allows a plugin to detect if it was started at
246251
`lightningd` startup (true), or at runtime (false).

lightningd/plugin.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,17 @@ static const char *plugin_parse_getmanifest_response(const char *buffer,
12291229
json_tok_full_len(toks),
12301230
json_tok_full(buffer, toks));
12311231

1232+
/* Plugin can disable itself: returns why it's disabled. */
1233+
tok = json_get_member(buffer, resulttok, "disable");
1234+
if (tok) {
1235+
log_debug(plugin->log, "disabled itself: %.*s",
1236+
json_tok_full_len(tok),
1237+
json_tok_full(buffer, tok));
1238+
/* Don't get upset if this was a built-in! */
1239+
plugin->important = false;
1240+
return json_strdup(plugin, buffer, tok);
1241+
}
1242+
12321243
dynamictok = json_get_member(buffer, resulttok, "dynamic");
12331244
if (dynamictok && !json_to_bool(buffer, dynamictok, &plugin->dynamic)) {
12341245
return tal_fmt(plugin, "Bad 'dynamic' field ('%.*s')",
@@ -1572,6 +1583,19 @@ static void plugin_config_cb(const char *buffer,
15721583
const jsmntok_t *idtok,
15731584
struct plugin *plugin)
15741585
{
1586+
const char *disable;
1587+
1588+
/* Plugin can also disable itself at this stage. */
1589+
if (json_scan(tmpctx, buffer, toks, "{result:{disable:%}}",
1590+
JSON_SCAN_TAL(tmpctx, json_strdup, &disable)) == NULL) {
1591+
log_debug(plugin->log, "disabled itself at init: %s",
1592+
disable);
1593+
/* Don't get upset if this was a built-in! */
1594+
plugin->important = false;
1595+
plugin_kill(plugin, disable);
1596+
return;
1597+
}
1598+
15751599
plugin->plugin_state = INIT_COMPLETE;
15761600
plugin->timeout_timer = tal_free(plugin->timeout_timer);
15771601
if (plugin->start_cmd) {

tests/plugins/Makefile

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
PLUGIN_TESTLIBPLUGIN_SRC := tests/plugins/test_libplugin.c
1+
PLUGIN_TESTLIBPLUGIN_SRC := tests/plugins/test_libplugin.c
22
PLUGIN_TESTLIBPLUGIN_OBJS := $(PLUGIN_TESTLIBPLUGIN_SRC:.c=.o)
33

44
tests/plugins/test_libplugin: bitcoin/chainparams.o $(PLUGIN_TESTLIBPLUGIN_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS)
55

66
$(PLUGIN_TESTLIBPLUGIN_OBJS): $(PLUGIN_LIB_HEADER)
77

8+
PLUGIN_TESTSELFDISABLE_AFTER_GETMANIFEST_SRC := tests/plugins/test_selfdisable_after_getmanifest.c
9+
PLUGIN_TESTSELFDISABLE_AFTER_GETMANIFEST_OBJS := $(PLUGIN_TESTSELFDISABLE_AFTER_GETMANIFEST_SRC:.c=.o)
10+
11+
tests/plugins/test_selfdisable_after_getmanifest: bitcoin/chainparams.o $(PLUGIN_TESTSELFDISABLE_AFTER_GETMANIFEST_OBJS) common/json.o common/json_stream.o common/setup.o common/utils.o $(JSMN_OBJS) $(CCAN_OBJS)
12+
813
# Make sure these depend on everything.
9-
ALL_TEST_PROGRAMS += tests/plugins/test_libplugin
10-
ALL_C_SOURCES += $(PLUGIN_TESTLIBPLUGIN_SRC)
14+
ALL_TEST_PROGRAMS += tests/plugins/test_libplugin tests/plugins/test_selfdisable_after_getmanifest
15+
ALL_C_SOURCES += $(PLUGIN_TESTLIBPLUGIN_SRC) $(PLUGIN_TESTSELFDISABLE_AFTER_GETMANIFEST_SRC)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include <ccan/err/err.h>
2+
#include <ccan/read_write_all/read_write_all.h>
3+
#include <ccan/tal/tal.h>
4+
#include <ccan/tal/str/str.h>
5+
#include <common/json.h>
6+
#include <common/setup.h>
7+
#include <common/utils.h>
8+
#include <unistd.h>
9+
10+
/* Our normal frameworks don't (yet?) support custom post-manifest responses,
11+
* so this is open-coded */
12+
int main(int argc, char *argv[])
13+
{
14+
char *buf;
15+
int r, off;
16+
const jsmntok_t *toks, *id;
17+
18+
common_setup(argv[0]);
19+
20+
buf = tal_arr(tmpctx, char, 100);
21+
off = 0;
22+
do {
23+
r = read(STDIN_FILENO, buf + off, tal_bytelen(buf) - off);
24+
if (r < 0)
25+
err(1, "reading stdin");
26+
off += r;
27+
if (off == tal_bytelen(buf))
28+
tal_resize(&buf, off * 2);
29+
30+
toks = json_parse_simple(tmpctx, buf, off);
31+
} while (!toks);
32+
33+
/* Tell it we're disabled (reusing id). */
34+
id = json_get_member(buf, toks, "id");
35+
buf = tal_fmt(tmpctx, "{\"jsonrpc\":\"2.0\",\"id\":%.*s,\"result\":{\"disable\":\"Self-disable test after getmanifest\"} }",
36+
json_tok_full_len(id),
37+
json_tok_full(buf, id));
38+
write_all(STDOUT_FILENO, buf, strlen(buf));
39+
40+
common_shutdown();
41+
return 0;
42+
}

tests/test_plugin.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2321,3 +2321,24 @@ def n(*args, message, **kwargs):
23212321
notifications = []
23222322
l1.rpc.countdown(10)
23232323
assert notifications == []
2324+
2325+
2326+
def test_self_disable(node_factory):
2327+
"""Test that plugin can disable itself without penalty.
2328+
"""
2329+
plugin_path = os.path.join(
2330+
os.path.dirname(__file__), 'plugins/test_selfdisable'
2331+
)
2332+
l1 = node_factory.get_node(options={'important-plugin': plugin_path})
2333+
2334+
# Could happen before it gets set up.
2335+
l1.daemon.logsearch_start = 0
2336+
l1.daemon.wait_for_log('test_selfdisable: disabled itself: "Self-disable test after getmanifest"')
2337+
2338+
assert plugin_path not in [p['name'] for p in l1.rpc.plugin_list()['plugins']]
2339+
2340+
# Also works with dynamic load attempts
2341+
with pytest.raises(RpcError, match="Self-disable test after getmanifest"):
2342+
l1.rpc.plugin_start(plugin_path)
2343+
2344+
# Now test the disable-in-init-response.

0 commit comments

Comments
 (0)