diff --git a/contrib/pyln-client/pyln/client/plugin.py b/contrib/pyln-client/pyln/client/plugin.py index 2673febab..d14ae5d3c 100644 --- a/contrib/pyln-client/pyln/client/plugin.py +++ b/contrib/pyln-client/pyln/client/plugin.py @@ -659,10 +659,13 @@ class Plugin(object): self.log(traceback.format_exc()) def _dispatch_notification(self, request: Request) -> None: - if request.method not in self.subscriptions: - raise ValueError("No subscription for {name} found.".format( - name=request.method)) - func = self.subscriptions[request.method] + if request.method in self.subscriptions: + func = self.subscriptions[request.method] + # Wildcard 'all' subscriptions using asterisk + elif '*' in self.subscriptions: + func = self.subscriptions['*'] + else: + raise ValueError(f"No subscription for {request.method} found.") try: self._exec_func(func, request) diff --git a/tests/plugins/all_notifications.py b/tests/plugins/all_notifications.py new file mode 100755 index 000000000..cdcb96744 --- /dev/null +++ b/tests/plugins/all_notifications.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +from pyln.client import Plugin +import sys + + +plugin = Plugin() + + +@plugin.subscribe("*") +def on_any_notification(request, **kwargs): + plugin.log("notification {}: {}".format(request.method, kwargs)) + if request.method == 'shutdown': + # A plugin which subscribes to shutdown is expected to exit itself. + sys.exit(0) + + +plugin.run() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 0e046a17f..fddf1fc7a 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -4248,3 +4248,20 @@ def test_plugin_persist_option(node_factory): assert c['value_str'] == "Static option" assert c['plugin'] == plugin_path assert l1.rpc.call("hello") == "Static option world" + + +def test_all_subscription(node_factory, directory): + """Ensure that registering for all notifications works.""" + plugin = os.path.join(os.getcwd(), 'tests/plugins/all_notifications.py') + + l1, l2 = node_factory.line_graph(2, opts={"plugin": plugin}) + + l1.stop() + + # There will be a lot of these! + for notstr in ("block_added: {'block_added': {'hash': ", + "balance_snapshot: {'balance_snapshot': {'node_id': ", + "connect: {'connect': {'id': ", + "channel_state_changed: {'channel_state_changed': {'peer_id': ", + "shutdown: {}"): + assert l1.daemon.is_in_log(f".*plugin-all_notifications.py: notification {notstr}.*")