mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-21 08:04:26 +01:00
pytest: Add a test for keysend
It currently uses a borrowed sending implementation from the noise plugin, but we'll implement that functionality in the native keysend plugin next.
This commit is contained in:
committed by
Rusty Russell
parent
1b32cc1c73
commit
568773daad
118
tests/plugins/keysend.py
Executable file
118
tests/plugins/keysend.py
Executable file
@@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Temporary keysend plugin until we implement it in C
|
||||||
|
|
||||||
|
This plugin is just used to test the ability to receive keysend payments until
|
||||||
|
we implement it in `plugins/keysend.c`. Most of this code is borrowed from the
|
||||||
|
noise plugin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyln.client import Plugin, RpcError
|
||||||
|
from pyln.proto.onion import TlvPayload, Tu32Field, Tu64Field
|
||||||
|
from binascii import hexlify
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
plugin = Plugin()
|
||||||
|
TLV_KEYSEND_PREIMAGE = 5482373484
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_payload(n, blockheight):
|
||||||
|
"""Serialize a legacy payload.
|
||||||
|
"""
|
||||||
|
block, tx, out = n['channel'].split('x')
|
||||||
|
payload = hexlify(struct.pack(
|
||||||
|
"!cQQL", b'\x00',
|
||||||
|
int(block) << 40 | int(tx) << 16 | int(out),
|
||||||
|
int(n['amount_msat']),
|
||||||
|
blockheight + n['delay'])).decode('ASCII')
|
||||||
|
payload += "00" * 12
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def buildpath(plugin, node_id, payload, amt, exclusions):
|
||||||
|
blockheight = plugin.rpc.getinfo()['blockheight']
|
||||||
|
route = plugin.rpc.getroute(node_id, amt, 10, exclude=exclusions)['route']
|
||||||
|
first_hop = route[0]
|
||||||
|
# Need to shift the parameters by one hop
|
||||||
|
hops = []
|
||||||
|
for h, n in zip(route[:-1], route[1:]):
|
||||||
|
# We tell the node h about the parameters to use for n (a.k.a. h + 1)
|
||||||
|
hops.append({
|
||||||
|
"type": "legacy",
|
||||||
|
"pubkey": h['id'],
|
||||||
|
"payload": serialize_payload(n, blockheight)
|
||||||
|
})
|
||||||
|
|
||||||
|
pl = TlvPayload()
|
||||||
|
pl.fields.append(Tu64Field(2, amt))
|
||||||
|
pl.fields.append(Tu32Field(4, route[-1]['delay']))
|
||||||
|
|
||||||
|
for f in payload.fields:
|
||||||
|
pl.add_field(f.typenum, f.value)
|
||||||
|
|
||||||
|
# The last hop has a special payload:
|
||||||
|
hops.append({
|
||||||
|
"type": "tlv",
|
||||||
|
"pubkey": route[-1]['id'],
|
||||||
|
"payload": hexlify(pl.to_bytes()).decode('ASCII'),
|
||||||
|
})
|
||||||
|
print(f"Keysend payload {hexlify(pl.to_bytes())}")
|
||||||
|
return first_hop, hops, route
|
||||||
|
|
||||||
|
|
||||||
|
def deliver(node_id, payload, amt, payment_hash, max_attempts=5):
|
||||||
|
"""Do your best to deliver `payload` to `node_id`.
|
||||||
|
"""
|
||||||
|
exclusions = []
|
||||||
|
payment_hash = hexlify(payment_hash).decode('ASCII')
|
||||||
|
|
||||||
|
for attempt in range(max_attempts):
|
||||||
|
plugin.log("Starting attempt {} to deliver message to {}".format(attempt, node_id))
|
||||||
|
|
||||||
|
first_hop, hops, route = buildpath(plugin, node_id, payload, amt, exclusions)
|
||||||
|
onion = plugin.rpc.createonion(hops=hops, assocdata=payment_hash)
|
||||||
|
|
||||||
|
plugin.rpc.sendonion(
|
||||||
|
onion=onion['onion'],
|
||||||
|
first_hop=first_hop,
|
||||||
|
payment_hash=payment_hash,
|
||||||
|
shared_secrets=onion['shared_secrets'],
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
plugin.rpc.waitsendpay(payment_hash=payment_hash)
|
||||||
|
return {'route': route, 'payment_hash': payment_hash, 'attempt': attempt}
|
||||||
|
except RpcError as e:
|
||||||
|
failcode = e.error['data']['failcode']
|
||||||
|
failingidx = e.error['data']['erring_index']
|
||||||
|
if failcode == 16399 or failingidx == len(hops):
|
||||||
|
return {
|
||||||
|
'route': route,
|
||||||
|
'payment_hash': payment_hash,
|
||||||
|
'attempt': attempt + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.log("Retrying delivery.")
|
||||||
|
|
||||||
|
# TODO Store the failing channel in the exclusions
|
||||||
|
raise ValueError('Could not reach destination {node_id}'.format(node_id=node_id))
|
||||||
|
|
||||||
|
|
||||||
|
@plugin.method('keysend')
|
||||||
|
def keysend(node_id, amount, plugin):
|
||||||
|
payload = TlvPayload()
|
||||||
|
payment_key = os.urandom(32)
|
||||||
|
payment_hash = hashlib.sha256(payment_key).digest()
|
||||||
|
payload.add_field(TLV_KEYSEND_PREIMAGE, payment_key)
|
||||||
|
res = deliver(
|
||||||
|
node_id,
|
||||||
|
payload,
|
||||||
|
amt=amount,
|
||||||
|
payment_hash=payment_hash
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
plugin.run()
|
||||||
@@ -2959,3 +2959,28 @@ def test_excluded_adjacent_routehint(node_factory, bitcoind):
|
|||||||
# This will make it reject the routehint.
|
# This will make it reject the routehint.
|
||||||
with pytest.raises(RpcError, match=r'Route wanted fee of 1msat'):
|
with pytest.raises(RpcError, match=r'Route wanted fee of 1msat'):
|
||||||
l1.rpc.pay(bolt11=inv['bolt11'], maxfeepercent=0, exemptfee=0)
|
l1.rpc.pay(bolt11=inv['bolt11'], maxfeepercent=0, exemptfee=0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_keysend(node_factory):
|
||||||
|
# Use a temporary python plugin until we implement a native one
|
||||||
|
plugin_path = os.path.join(os.getcwd(), 'tests/plugins/keysend.py')
|
||||||
|
opts = {'plugin': plugin_path}
|
||||||
|
amt = 10000
|
||||||
|
l1, l2, l3 = node_factory.line_graph(3, opts=opts, wait_for_announce=True)
|
||||||
|
|
||||||
|
# Send an indirect one from l1 to l3
|
||||||
|
l1.rpc.keysend(l3.info['id'], amt)
|
||||||
|
invs = l3.rpc.listinvoices()['invoices']
|
||||||
|
assert(len(invs) == 1)
|
||||||
|
|
||||||
|
inv = invs[0]
|
||||||
|
print(inv)
|
||||||
|
assert(inv['msatoshi_received'] >= amt)
|
||||||
|
|
||||||
|
# Now send a direct one instead from l1 to l2
|
||||||
|
l1.rpc.keysend(l2.info['id'], amt)
|
||||||
|
invs = l2.rpc.listinvoices()['invoices']
|
||||||
|
assert(len(invs) == 1)
|
||||||
|
|
||||||
|
inv = invs[0]
|
||||||
|
assert(inv['msatoshi_received'] >= amt)
|
||||||
|
|||||||
Reference in New Issue
Block a user