-
Notifications
You must be signed in to change notification settings - Fork 901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
plugin: Allow plugins to publish and subscribe to custom notifications #4496
Merged
rustyrussell
merged 17 commits into
ElementsProject:master
from
cdecker:plugin-notifications
May 3, 2021
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
711353f
plugin: Add a list of notification topics registered by plugin
cdecker 79e4ff9
plugin: Move the notification subscription check into a second phase
cdecker 96eb558
plugin: Store the notification topics announced by the plugins
cdecker 0925383
plugin: Implement custom notification dispatch for plugins
cdecker bdbfbb1
plugin: Prevent plugins from registering native notification topics
cdecker 03df5fb
plugin: Move list of notification topics to each plugin
cdecker d4c0fc0
plugin: Remember the shortname for a plugin
cdecker 8d30301
plugin: Wrap custom notifications in a dict with additional origin
cdecker 6a7e001
plugin: Restrict plugin notifications only to announced topics
cdecker 07a73ae
cleanup: Make lnprototest run only with DEVELOPER=1
cdecker 87d32bb
libplugin: Add notification topics to plugin_main
cdecker cdf53d3
libplugin: Add notification topics to the manifest response
cdecker aebb54a
libplugin: Add functions to start and end notifications
cdecker 799f8ce
plugin: Make unannounced notification topics no longer fatal
cdecker 41b4cec
pay: Add notification for pay_success
cdecker 4e3c9d6
pay: Add pay_failure notification
cdecker 9faa907
docs: Document the custom plugin notifications
cdecker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,9 +103,29 @@ void plugins_free(struct plugins *plugins) | |
tal_free(plugins); | ||
} | ||
|
||
/* Check that all the plugin's subscriptions are actually for known | ||
* notification topics. Emit a warning if that's not the case, but | ||
* don't kill the plugin. */ | ||
static void plugin_check_subscriptions(struct plugins *plugins, | ||
struct plugin *plugin) | ||
{ | ||
if (plugin->subscriptions == NULL) | ||
return; | ||
|
||
for (size_t i = 0; i < tal_count(plugin->subscriptions); i++) { | ||
const char *topic = plugin->subscriptions[i]; | ||
if (!notifications_have_topic(plugins, topic)) | ||
log_unusual( | ||
plugin->log, | ||
"topic '%s' is not a known notification topic", | ||
topic); | ||
} | ||
} | ||
|
||
/* Once they've all replied with their manifests, we can order them. */ | ||
static void check_plugins_manifests(struct plugins *plugins) | ||
{ | ||
struct plugin *plugin; | ||
struct plugin **depfail; | ||
|
||
if (plugins_any_in_state(plugins, AWAITING_GETMANIFEST_RESPONSE)) | ||
|
@@ -121,6 +141,12 @@ static void check_plugins_manifests(struct plugins *plugins) | |
"Cannot meet required hook dependencies"); | ||
} | ||
|
||
/* Check that all the subscriptions are matched with real | ||
* topics. */ | ||
list_for_each(&plugins->plugins, plugin, list) { | ||
plugin_check_subscriptions(plugin->plugins, plugin); | ||
} | ||
|
||
/* As startup, we break out once all getmanifest are returned */ | ||
if (plugins->startup) | ||
io_break(plugins); | ||
|
@@ -214,17 +240,18 @@ struct plugin *plugin_register(struct plugins *plugins, const char* path TAKES, | |
p = tal(plugins, struct plugin); | ||
p->plugins = plugins; | ||
p->cmd = tal_strdup(p, path); | ||
p->shortname = path_basename(p, p->cmd); | ||
p->start_cmd = start_cmd; | ||
|
||
p->plugin_state = UNCONFIGURED; | ||
p->js_arr = tal_arr(p, struct json_stream *, 0); | ||
p->used = 0; | ||
p->notification_topics = tal_arr(p, const char *, 0); | ||
p->subscriptions = NULL; | ||
p->dynamic = false; | ||
p->index = plugins->plugin_idx++; | ||
|
||
p->log = new_log(p, plugins->log_book, NULL, "plugin-%s", | ||
path_basename(tmpctx, p->cmd)); | ||
p->log = new_log(p, plugins->log_book, NULL, "plugin-%s", p->shortname); | ||
p->methods = tal_arr(p, const char *, 0); | ||
list_head_init(&p->plugin_opts); | ||
|
||
|
@@ -390,6 +417,18 @@ static const char *plugin_notify_handle(struct plugin *plugin, | |
return NULL; | ||
} | ||
|
||
/* Check if the plugin is allowed to send a notification of the | ||
* specified topic, i.e., whether the plugin has announced the topic | ||
* correctly in its manifest. */ | ||
static bool plugin_notification_allowed(const struct plugin *plugin, const char *topic) | ||
{ | ||
for (size_t i=0; i<tal_count(plugin->notification_topics); i++) | ||
if (streq(plugin->notification_topics[i], topic)) | ||
return true; | ||
|
||
return false; | ||
} | ||
|
||
/* Returns the error string, or NULL */ | ||
static const char *plugin_notification_handle(struct plugin *plugin, | ||
const jsmntok_t *toks) | ||
|
@@ -399,7 +438,8 @@ static const char *plugin_notification_handle(struct plugin *plugin, | |
const jsmntok_t *toks) | ||
{ | ||
const jsmntok_t *methtok, *paramstok; | ||
|
||
const char *methname; | ||
struct jsonrpc_notification *n; | ||
methtok = json_get_member(plugin->buffer, toks, "method"); | ||
paramstok = json_get_member(plugin->buffer, toks, "params"); | ||
|
||
|
@@ -420,11 +460,25 @@ static const char *plugin_notification_handle(struct plugin *plugin, | |
} else if (json_tok_streq(plugin->buffer, methtok, "message") | ||
|| json_tok_streq(plugin->buffer, methtok, "progress")) { | ||
return plugin_notify_handle(plugin, methtok, paramstok); | ||
} else { | ||
return tal_fmt(plugin, "Unknown notification method %.*s", | ||
json_tok_full_len(methtok), | ||
json_tok_full(plugin->buffer, methtok)); | ||
} | ||
|
||
methname = json_strdup(tmpctx, plugin->buffer, methtok); | ||
|
||
if (!plugin_notification_allowed(plugin, methname)) { | ||
log_unusual(plugin->log, | ||
"Plugin attempted to send a notification to topic " | ||
"\"%s\" it hasn't declared in its manifest, not " | ||
"forwarding to subscribers.", | ||
methname); | ||
} else if (notifications_have_topic(plugin->plugins, methname)) { | ||
n = jsonrpc_notification_start(NULL, methname); | ||
json_add_string(n->stream, "origin", plugin->shortname); | ||
json_add_tok(n->stream, "payload", paramstok, plugin->buffer); | ||
jsonrpc_notification_end(n); | ||
|
||
plugins_notify(plugin->plugins, take(n)); | ||
} | ||
return NULL; | ||
} | ||
|
||
/* Returns the error string, or NULL */ | ||
|
@@ -1136,14 +1190,12 @@ static const char *plugin_subscriptions_add(struct plugin *plugin, | |
json_tok_full_len(s), | ||
json_tok_full(buffer, s)); | ||
} | ||
topic = json_strdup(plugin, plugin->buffer, s); | ||
|
||
if (!notifications_have_topic(topic)) { | ||
return tal_fmt( | ||
plugin, | ||
"topic '%s' is not a known notification topic", topic); | ||
} | ||
|
||
/* We add all subscriptions while parsing the | ||
* manifest, without checking that they exist, since | ||
* later plugins may also emit notifications of custom | ||
* types that we don't know about yet. */ | ||
topic = json_strdup(plugin, plugin->buffer, s); | ||
tal_arr_expand(&plugin->subscriptions, topic); | ||
} | ||
return NULL; | ||
|
@@ -1248,6 +1300,52 @@ static void plugin_manifest_timeout(struct plugin *plugin) | |
fatal("Can't recover from plugin failure, terminating."); | ||
} | ||
|
||
static const char *plugin_notifications_add(const char *buffer, | ||
const jsmntok_t *result, | ||
struct plugin *plugin) | ||
{ | ||
char *name; | ||
const jsmntok_t *method, *obj; | ||
const jsmntok_t *notifications = | ||
json_get_member(buffer, result, "notifications"); | ||
|
||
if (!notifications) | ||
return NULL; | ||
|
||
if (notifications->type != JSMN_ARRAY) | ||
return tal_fmt(plugin, | ||
"\"result.notifications\" is not an array"); | ||
|
||
for (size_t i = 0; i < notifications->size; i++) { | ||
obj = json_get_arr(notifications, i); | ||
Comment on lines
+1319
to
+1320
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (obj->type != JSMN_OBJECT) | ||
return tal_fmt( | ||
plugin, | ||
"\"result.notifications[%zu]\" is not an object", | ||
i); | ||
|
||
method = json_get_member(buffer, obj, "method"); | ||
if (method == NULL || method->type != JSMN_STRING) | ||
return tal_fmt(plugin, | ||
"\"result.notifications[%zu].name\" " | ||
"missing or not a string.", | ||
i); | ||
|
||
name = json_strdup(plugin, buffer, method); | ||
|
||
if (notifications_topic_is_native(name)) | ||
return tal_fmt(plugin, | ||
"plugin attempted to register a native " | ||
"notification topic \"%s\", these may " | ||
"however only be sent by lightningd", | ||
name); | ||
|
||
tal_arr_expand(&plugin->notification_topics, name); | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
static const char *plugin_parse_getmanifest_response(const char *buffer, | ||
const jsmntok_t *toks, | ||
const jsmntok_t *idtok, | ||
|
@@ -1328,7 +1426,9 @@ static const char *plugin_parse_getmanifest_response(const char *buffer, | |
} | ||
} | ||
|
||
err = plugin_opts_add(plugin, buffer, resulttok); | ||
err = plugin_notifications_add(buffer, resulttok, plugin); | ||
if (!err) | ||
err = plugin_opts_add(plugin, buffer, resulttok); | ||
if (!err) | ||
err = plugin_rpcmethods_add(plugin, buffer, resulttok); | ||
if (!err) | ||
|
@@ -1763,7 +1863,6 @@ void json_add_opt_plugins_array(struct json_stream *response, | |
bool important) | ||
{ | ||
struct plugin *p; | ||
const char *plugin_name; | ||
struct plugin_opt *opt; | ||
const char *opt_name; | ||
|
||
|
@@ -1778,9 +1877,7 @@ void json_add_opt_plugins_array(struct json_stream *response, | |
json_add_string(response, "path", p->cmd); | ||
|
||
/* FIXME: use executables basename until plugins can define their names */ | ||
plugin_name = path_basename(NULL, p->cmd); | ||
json_add_string(response, "name", plugin_name); | ||
tal_free(plugin_name); | ||
json_add_string(response, "name", p->shortname); | ||
|
||
if (!list_empty(&p->plugin_opts)) { | ||
json_object_start(response, "options"); | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: tal_count(NULL) == 0, so this loop doesn't need a precursor check.