From 72757933f0d3c9ed1970e940597a21d08c832a99 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 5 Feb 2020 23:01:28 +0100 Subject: [PATCH] pytest: Test a plugin crash while handling a hook call --- tests/plugins/hook-crash.py | 20 +++++++++++++++ tests/test_plugin.py | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100755 tests/plugins/hook-crash.py diff --git a/tests/plugins/hook-crash.py b/tests/plugins/hook-crash.py new file mode 100755 index 000000000..b70d43e37 --- /dev/null +++ b/tests/plugins/hook-crash.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +from pyln.client import Plugin +import sys + + +plugin = Plugin() + + +@plugin.hook('htlc_accepted') +def on_htlc_accepted(plugin, htlc, onion, **kwargs): + """We die silently, i.e., without returning a response + + `lightningd` should detect that and recover. + """ + plugin.log("Plugin is about to crash...") + sys.exit(1) + + +plugin.run() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index fb06f70be..c77d38f7d 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1012,3 +1012,54 @@ def test_bcli(node_factory, bitcoind, chainparams): resp = l1.rpc.call("sendrawtransaction", {"tx": "dummy"}) assert not resp["success"] and "decode failed" in resp["errmsg"] + + +@pytest.mark.xfail(strict=True) +def test_hook_crash(node_factory, executor, bitcoind): + """Verify that we fail over if a plugin crashes while handling a hook. + + We create a star topology, with l1 opening channels to the other nodes, + and then triggering the plugins on those nodes in order to exercise the + hook chain. p0 is the interesting plugin because as soon as it get called + for the htlc_accepted hook it'll crash on purpose. We should still make it + through the chain, the plugins should all be called and the payment should + still go through. + + """ + p0 = os.path.join(os.path.dirname(__file__), "plugins/hook-crash.py") + p1 = os.path.join(os.path.dirname(__file__), "plugins/hook-chain-odd.py") + p2 = os.path.join(os.path.dirname(__file__), "plugins/hook-chain-even.py") + perm = [ + (p0, p1, p2), # Crashing plugin is first in chain + (p1, p0, p2), # Crashing plugin is in the middle of the chain + (p1, p2, p0), # Crashing plugin is last in chain + ] + + l1 = node_factory.get_node() + nodes = [node_factory.get_node() for _ in perm] + + # Start them in any order and we should still always end up with each + # plugin being called and ultimately the `pay` call should succeed: + for plugins, n in zip(perm, nodes): + for p in plugins: + n.rpc.plugin_start(p) + l1.openchannel(n, 10**6, confirm=False, wait_for_announce=False) + + bitcoind.generate_block(6) + + wait_for(lambda: len(l1.rpc.listchannels()['channels']) == 2 * len(perm)) + + futures = [] + for n in nodes: + inv = n.rpc.invoice(123, "lbl", "desc")['bolt11'] + futures.append(executor.submit(l1.rpc.pay, inv)) + + for n in nodes: + n.daemon.wait_for_logs([ + r'Plugin is about to crash.', + r'plugin-hook-chain-odd.py: htlc_accepted called for payment_hash', + r'plugin-hook-chain-even.py: htlc_accepted called for payment_hash', + ]) + + # Collect the results: + [f.result(TIMEOUT) for f in futures]