Browse Source

plugin: Allow multiple plugins to register the `htlc_accepted` hook

Make the `htlc_accepted` hook the first chained hook in our repertoire. The
plugins are called one after the other in order until we have no more plugins
or the HTLC was handled by one of the plugins. If no plugins handles the HTLC
we continue to handle it internally like always.

Handling in this case means the plugin returns either `{"result": "resolve",
...}` or `{"result": "fail", ...}`.

Changelog-Changed: plugin: Multiple plugins can now register for the htlc_accepted hook.
travis-debug
Christian Decker 5 years ago
committed by Rusty Russell
parent
commit
490550d508
  1. 2
      lightningd/peer_htlcs.c
  2. 24
      tests/plugins/hook-chain-even.py
  3. 24
      tests/plugins/hook-chain-odd.py
  4. 73
      tests/test_plugin.py

2
lightningd/peer_htlcs.c

@ -873,7 +873,7 @@ htlc_accepted_hook_callback(struct htlc_accepted_hook_payload *request,
tal_free(request);
}
REGISTER_PLUGIN_HOOK(htlc_accepted, PLUGIN_HOOK_SINGLE,
REGISTER_PLUGIN_HOOK(htlc_accepted, PLUGIN_HOOK_CHAIN,
htlc_accepted_hook_callback,
struct htlc_accepted_hook_payload *,
htlc_accepted_hook_serialize,

24
tests/plugins/hook-chain-even.py

@ -0,0 +1,24 @@
#!/usr/bin/env python3
from pyln.client import Plugin
from hashlib import sha256
from binascii import hexlify
"""A simple plugin that accepts invoices with "BB"*32 preimages
"""
plugin = Plugin()
@plugin.hook('htlc_accepted')
def on_htlc_accepted(htlc, plugin, **kwargs):
preimage = b"\xBB" * 32
payment_hash = sha256(preimage).hexdigest()
preimage = hexlify(preimage).decode('ASCII')
print("htlc_accepted called for payment_hash {}".format(htlc['payment_hash']))
if htlc['payment_hash'] == payment_hash:
return {'result': 'resolve', 'payment_key': preimage}
else:
return {'result': 'continue'}
plugin.run()

24
tests/plugins/hook-chain-odd.py

@ -0,0 +1,24 @@
#!/usr/bin/env python3
from pyln.client import Plugin
from hashlib import sha256
from binascii import hexlify
"""A simple plugin that accepts invoices with "AA"*32 preimages
"""
plugin = Plugin()
@plugin.hook('htlc_accepted')
def on_htlc_accepted(htlc, plugin, **kwargs):
preimage = b"\xAA" * 32
payment_hash = sha256(preimage).hexdigest()
preimage = hexlify(preimage).decode('ASCII')
print("htlc_accepted called for payment_hash {}".format(htlc['payment_hash']))
if htlc['payment_hash'] == payment_hash:
return {'result': 'resolve', 'payment_key': preimage}
else:
return {'result': 'continue'}
plugin.run()

73
tests/test_plugin.py

@ -1,6 +1,7 @@
from collections import OrderedDict
from fixtures import * # noqa: F401,F403
from flaky import flaky # noqa: F401
from hashlib import sha256
from pyln.client import RpcError, Millisatoshi
from pyln.proto import Invoice
from utils import (
@ -859,3 +860,75 @@ def test_plugin_feature_announce(node_factory):
# Check the featurebit set in the `node_announcement`
node = l1.rpc.listnodes(l1.info['id'])['nodes'][0]
assert(int(node['features'], 16) & (1 << 103) != 0)
def test_hook_chaining(node_factory):
"""Check that hooks are called in order and the chain exits correctly
We start two nodes, l2 will have two plugins registering the same hook
(`htlc_accepted`) but handle different cases:
- the `odd` plugin only handles the "AA"*32 preimage
- the `even` plugin only handles the "BB"*32 preimage
We check that plugins are called in the order they are registering the
hook, and that they exit the call chain as soon as one plugin returns a
result that isn't `continue`. On exiting the chain the remaining plugins
are not called. If no plugin exits the chain we continue to handle
internally as usual.
"""
l1, l2 = node_factory.line_graph(2)
# Start the plugins manually instead of specifying them on the command
# line, otherwise we cannot guarantee the order in which the hooks are
# registered.
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")
l2.rpc.plugin_start(p1)
l2.rpc.plugin_start(p2)
preimage1 = b'\xAA' * 32
preimage2 = b'\xBB' * 32
preimage3 = b'\xCC' * 32
hash1 = sha256(preimage1).hexdigest()
hash2 = sha256(preimage2).hexdigest()
hash3 = sha256(preimage3).hexdigest()
inv = l2.rpc.invoice(123, 'odd', "Odd payment handled by the first plugin",
preimage="AA" * 32)['bolt11']
l1.rpc.pay(inv)
# The first plugin will handle this, the second one should not be called.
assert(l2.daemon.is_in_log(
r'plugin-hook-chain-odd.py: htlc_accepted called for payment_hash {}'.format(hash1)
))
assert(not l2.daemon.is_in_log(
r'plugin-hook-chain-even.py: htlc_accepted called for payment_hash {}'.format(hash1)
))
# The second run is with a payment_hash that `hook-chain-even.py` knows
# about. `hook-chain-odd.py` is called, it returns a `continue`, and then
# `hook-chain-even.py` resolves it.
inv = l2.rpc.invoice(
123, 'even', "Even payment handled by the second plugin", preimage="BB" * 32
)['bolt11']
l1.rpc.pay(inv)
assert(l2.daemon.is_in_log(
r'plugin-hook-chain-odd.py: htlc_accepted called for payment_hash {}'.format(hash2)
))
assert(l2.daemon.is_in_log(
r'plugin-hook-chain-even.py: htlc_accepted called for payment_hash {}'.format(hash2)
))
# And finally an invoice that neither know about, so it should get settled
# by the internal invoice handling.
inv = l2.rpc.invoice(123, 'neither', "Neither plugin handles this",
preimage="CC" * 32)['bolt11']
l1.rpc.pay(inv)
assert(l2.daemon.is_in_log(
r'plugin-hook-chain-odd.py: htlc_accepted called for payment_hash {}'.format(hash3)
))
assert(l2.daemon.is_in_log(
r'plugin-hook-chain-even.py: htlc_accepted called for payment_hash {}'.format(hash3)
))

Loading…
Cancel
Save