Browse Source

trampoline forwarding: use routing hints

unit tests:
 - remove 'drop_dave' flag, replace it by depleted channel
 - add test for trampoline forwarding using routing hints
 - lower attempts to 2
master
ThomasV 2 years ago
parent
commit
e206d264c8
  1. 7
      electrum/lnpeer.py
  2. 36
      electrum/tests/test_lnpeer.py
  3. 25
      electrum/trampoline.py

7
electrum/lnpeer.py

@ -51,6 +51,7 @@ from .lnrouter import fee_for_edge_msat
from .json_db import StoredDict
from .invoices import PR_PAID
from .simple_config import FEE_LN_ETA_TARGET
from .trampoline import decode_routing_info
if TYPE_CHECKING:
from .lnworker import LNGossip, LNWallet
@ -1702,12 +1703,14 @@ class Peer(Logger):
next_trampoline_onion = None
invoice_features = payload["invoice_features"]["invoice_features"]
invoice_routing_info = payload["invoice_routing_info"]["invoice_routing_info"]
# TODO use invoice_routing_info
r_tags = decode_routing_info(invoice_routing_info)
self.logger.info(f'r_tags {r_tags}')
# TODO legacy mpp payment, use total_msat from trampoline onion
else:
self.logger.info('forward_trampoline: end-to-end')
invoice_features = LnFeatures.BASIC_MPP_OPT
next_trampoline_onion = trampoline_onion.next_packet
r_tags = []
except Exception as e:
self.logger.exception('')
raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
@ -1732,7 +1735,7 @@ class Peer(Logger):
payment_secret=payment_secret,
amount_to_pay=amt_to_forward,
min_cltv_expiry=cltv_from_onion,
r_tags=[],
r_tags=r_tags,
invoice_features=invoice_features,
fwd_trampoline_onion=next_trampoline_onion,
fwd_trampoline_fee=trampoline_fee,

36
electrum/tests/test_lnpeer.py

@ -1407,23 +1407,24 @@ class TestPeer(ElectrumTestCase):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['square_graph'])
await self._run_mpp(graph, {'mpp_invoice': False}, {'mpp_invoice': True})
async def _run_trampoline_payment(self, is_legacy, direct, drop_dave=None, test_mpp_consolidation=False):
if drop_dave is None: drop_dave = []
async def _run_trampoline_payment(
self, *,
is_legacy=False,
direct=False,
test_mpp_consolidation=False,
include_routing_hints=True, # only relevant if is_legacy is True
attempts=2,
):
async def pay(lnaddr, pay_req):
self.assertEqual(PR_UNPAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash))
result, log = await graph.workers['alice'].pay_invoice(pay_req, attempts=10)
result, log = await graph.workers['alice'].pay_invoice(pay_req, attempts=attempts)
if result:
self.assertEqual(PR_PAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash))
raise PaymentDone()
else:
raise NoPathFound()
def do_drop_dave(t):
# this will trigger UNKNOWN_NEXT_PEER
dave_node_id = graph.workers['dave'].node_keypair.pubkey
graph.workers[t].peers.pop(dave_node_id)
async def f():
await self._activate_trampoline(graph.workers['alice'])
async with OldTaskGroup() as group:
@ -1432,17 +1433,17 @@ class TestPeer(ElectrumTestCase):
await group.spawn(peer.htlc_switch())
for peer in peers:
await peer.initialized
lnaddr, pay_req = self.prepare_invoice(graph.workers['dave'], include_routing_hints=True)
for p in drop_dave:
do_drop_dave(p)
lnaddr, pay_req = self.prepare_invoice(graph.workers['dave'], include_routing_hints=include_routing_hints)
await group.spawn(pay(lnaddr, pay_req))
graph_definition = self.GRAPH_DEFINITIONS['square_graph']
if not direct:
# deplete channel from alice to carol
# deplete channel from alice to carol and from bob to dave
graph_definition['alice']['channels']['carol'] = depleted_channel
graph_definition['bob']['channels']['dave'] = depleted_channel
# insert a channel from bob to carol
graph_definition['bob']['channels']['carol'] = high_fee_channel
graph_definition['bob']['channels']['carol'] = low_fee_channel
# now the only route possible is alice -> bob -> carol -> dave
if test_mpp_consolidation:
# deplete alice to carol so that all htlcs go through bob
@ -1475,7 +1476,9 @@ class TestPeer(ElectrumTestCase):
@needs_test_with_all_chacha20_implementations
async def test_payment_trampoline_legacy(self):
with self.assertRaises(PaymentDone):
await self._run_trampoline_payment(is_legacy=True, direct=False)
await self._run_trampoline_payment(is_legacy=True, direct=False, include_routing_hints=True)
with self.assertRaises(NoPathFound):
await self._run_trampoline_payment(is_legacy=True, direct=False, include_routing_hints=False)
@needs_test_with_all_chacha20_implementations
async def test_payment_trampoline_e2e_direct(self):
@ -1486,10 +1489,7 @@ class TestPeer(ElectrumTestCase):
async def test_payment_trampoline_e2e_indirect(self):
# must use two trampolines
with self.assertRaises(PaymentDone):
await self._run_trampoline_payment(is_legacy=False, direct=False, drop_dave=['bob'])
# both trampolines drop dave
with self.assertRaises(NoPathFound):
await self._run_trampoline_payment(is_legacy=False, direct=False, drop_dave=['bob', 'carol'])
await self._run_trampoline_payment(is_legacy=False, direct=False)
@needs_test_with_all_chacha20_implementations
async def test_payment_multipart_trampoline_e2e(self):

25
electrum/trampoline.py

@ -92,10 +92,30 @@ def encode_routing_info(r_tags):
for route in r_tags:
result.append(bitstring.pack('uint:8', len(route)))
for step in route:
pubkey, channel, feebase, feerate, cltv = step
result.append(bitstring.BitArray(pubkey) + bitstring.BitArray(channel) + bitstring.pack('intbe:32', feebase) + bitstring.pack('intbe:32', feerate) + bitstring.pack('intbe:16', cltv))
pubkey, scid, feebase, feerate, cltv = step
result.append(
bitstring.BitArray(pubkey) \
+ bitstring.BitArray(scid)\
+ bitstring.pack('intbe:32', feebase)\
+ bitstring.pack('intbe:32', feerate)\
+ bitstring.pack('intbe:16', cltv))
return result.tobytes()
def decode_routing_info(s: bytes):
s = bitstring.BitArray(s)
r_tags = []
n = 8*(33 + 8 + 4 + 4 + 2)
while s:
route = []
length, s = s[0:8], s[8:]
length = length.unpack('uint:8')[0]
for i in range(length):
chunk, s = s[0:n], s[n:]
item = chunk.unpack('bytes:33, bytes:8, intbe:32, intbe:32, intbe:16')
route.append(item)
r_tags.append(route)
return r_tags
def is_legacy_relay(invoice_features, r_tags) -> Tuple[bool, Set[bytes]]:
"""Returns if we deal with a legacy payment and the list of trampoline pubkeys in the invoice.
@ -213,6 +233,7 @@ def create_trampoline_route(
# the last trampoline onion must contain routing hints for the last trampoline
# node to find the recipient
invoice_routing_info = encode_routing_info(r_tags)
assert invoice_routing_info == encode_routing_info(decode_routing_info(invoice_routing_info))
# lnwire invoice_features for trampoline is u64
invoice_features = invoice_features & 0xffffffffffffffff
route[-1].invoice_routing_info = invoice_routing_info

Loading…
Cancel
Save