mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-01 09:40:19 +01:00
plugin: Add a list of notification topics registered by plugin
We will eventually start emitting and dispatching custom notifications from plugins just like we dispatch internal notifications. In order to get reasonable error messages we need to make sure that the topics plugins are asking for were correctly registered. When doing this we don't really care about whether the plugin that registered the notification is still alive or not (it might have died, but subscribers should stay up and running), so we keep a list of all topics attached to the `struct plugins` which gathers global plugin information.
This commit is contained in:
parent
29155c2fe8
commit
083b41f090
7 changed files with 80 additions and 9 deletions
|
@ -223,6 +223,7 @@ class Plugin(object):
|
|||
}
|
||||
|
||||
self.options: Dict[str, Dict[str, Any]] = {}
|
||||
self.notification_topics: List[str] = []
|
||||
|
||||
def convert_featurebits(
|
||||
bits: Optional[Union[int, str, bytes]]) -> Optional[str]:
|
||||
|
@ -420,6 +421,11 @@ class Plugin(object):
|
|||
self.add_option(name, None, description, opt_type="flag",
|
||||
deprecated=deprecated)
|
||||
|
||||
def add_notification_topic(self, topic: str):
|
||||
"""Announce that the plugin will emit notifications for the topic.
|
||||
"""
|
||||
self.notification_topics.append(topic)
|
||||
|
||||
def get_option(self, name: str) -> str:
|
||||
if name not in self.options:
|
||||
raise ValueError("No option with name {} registered".format(name))
|
||||
|
@ -898,6 +904,9 @@ class Plugin(object):
|
|||
'subscriptions': list(self.subscriptions.keys()),
|
||||
'hooks': hooks,
|
||||
'dynamic': self.dynamic,
|
||||
'notifications': [
|
||||
{"method": name} for name in self.notification_topics
|
||||
],
|
||||
}
|
||||
|
||||
# Compact the features a bit, not important.
|
||||
|
|
|
@ -18,12 +18,19 @@ static struct notification *find_notification_by_topic(const char* topic)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
bool notifications_have_topic(const char *topic)
|
||||
bool notifications_have_topic(const struct plugins *plugins, const char *topic)
|
||||
{
|
||||
struct notification *noti = find_notification_by_topic(topic);
|
||||
if (noti)
|
||||
return true;
|
||||
|
||||
/* Some plugin at some point announced it'd be emitting
|
||||
* notifications to this topic. We don't care if it died, just
|
||||
* that it was a valid topic at some point in time. */
|
||||
for (size_t i=0; i<tal_count(plugins->notification_topics); i++)
|
||||
if (streq(plugins->notification_topics[i], topic))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
struct onionreply;
|
||||
struct wally_psbt;
|
||||
|
||||
bool notifications_have_topic(const char *topic);
|
||||
bool notifications_have_topic(const struct plugins *plugins, const char *topic);
|
||||
|
||||
struct notification {
|
||||
const char *topic;
|
||||
|
|
|
@ -76,6 +76,7 @@ struct plugins *plugins_new(const tal_t *ctx, struct log_book *log_book,
|
|||
p->startup = true;
|
||||
p->json_cmds = tal_arr(p, struct command *, 0);
|
||||
p->blacklist = tal_arr(p, const char *, 0);
|
||||
p->notification_topics = tal_arr(p, const char *, 0);
|
||||
p->shutdown = false;
|
||||
p->plugin_idx = 0;
|
||||
#if DEVELOPER
|
||||
|
@ -103,6 +104,25 @@ 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)
|
||||
{
|
||||
|
@ -1136,14 +1156,12 @@ static const char *plugin_subscriptions_add(struct plugin *plugin,
|
|||
json_tok_full_len(s),
|
||||
json_tok_full(buffer, s));
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
if (!notifications_have_topic(topic)) {
|
||||
return tal_fmt(
|
||||
plugin,
|
||||
"topic '%s' is not a known notification topic", topic);
|
||||
}
|
||||
|
||||
tal_arr_expand(&plugin->subscriptions, topic);
|
||||
}
|
||||
return NULL;
|
||||
|
@ -1338,6 +1356,8 @@ static const char *plugin_parse_getmanifest_response(const char *buffer,
|
|||
if (!err)
|
||||
err = plugin_add_params(plugin);
|
||||
|
||||
plugin_check_subscriptions(plugin->plugins, plugin);
|
||||
|
||||
plugin->plugin_state = NEEDS_INIT;
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -128,6 +128,10 @@ struct plugins {
|
|||
/* Whether builtin plugins should be overridden as unimportant. */
|
||||
bool dev_builtin_plugins_unimportant;
|
||||
#endif /* DEVELOPER */
|
||||
|
||||
/* Notification topics that plugins have registered with us
|
||||
* and that other plugins may subscribe to. */
|
||||
const char **notification_topics;
|
||||
};
|
||||
|
||||
/* The value of a plugin option, which can have different types.
|
||||
|
|
21
tests/plugins/custom_notifications.py
Executable file
21
tests/plugins/custom_notifications.py
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env python3
|
||||
from pyln.client import Plugin
|
||||
|
||||
|
||||
plugin = Plugin()
|
||||
|
||||
|
||||
@plugin.subscribe("custom")
|
||||
def on_custom_notification(val, plugin, **kwargs):
|
||||
plugin.log("Got a custom notification {}".format(val))
|
||||
|
||||
|
||||
@plugin.method("emit")
|
||||
def emit(plugin):
|
||||
"""Emit a simple string notification to topic "custom"
|
||||
"""
|
||||
plugin.notify("custom", "Hello world")
|
||||
|
||||
|
||||
plugin.add_notification_topic("custom")
|
||||
plugin.run()
|
|
@ -2402,3 +2402,13 @@ def test_self_disable(node_factory):
|
|||
# Also works with dynamic load attempts
|
||||
with pytest.raises(RpcError, match="Disabled via selfdisable option"):
|
||||
l1.rpc.plugin_start(p2, selfdisable=True)
|
||||
|
||||
|
||||
@pytest.mark.xfail(strict=True)
|
||||
def test_custom_notification_topics(node_factory):
|
||||
plugin = os.path.join(
|
||||
os.path.dirname(__file__), "plugins", "custom_notifications.py"
|
||||
)
|
||||
l1 = node_factory.get_node(options={'plugin': plugin})
|
||||
l1.rpc.emit()
|
||||
l1.daemon.wait_for_log(r'Got a custom notification Hello world')
|
||||
|
|
Loading…
Add table
Reference in a new issue