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
This commit is contained in:
@@ -51,6 +51,7 @@ from .lnrouter import fee_for_edge_msat
|
|||||||
from .json_db import StoredDict
|
from .json_db import StoredDict
|
||||||
from .invoices import PR_PAID
|
from .invoices import PR_PAID
|
||||||
from .simple_config import FEE_LN_ETA_TARGET
|
from .simple_config import FEE_LN_ETA_TARGET
|
||||||
|
from .trampoline import decode_routing_info
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .lnworker import LNGossip, LNWallet
|
from .lnworker import LNGossip, LNWallet
|
||||||
@@ -1702,12 +1703,14 @@ class Peer(Logger):
|
|||||||
next_trampoline_onion = None
|
next_trampoline_onion = None
|
||||||
invoice_features = payload["invoice_features"]["invoice_features"]
|
invoice_features = payload["invoice_features"]["invoice_features"]
|
||||||
invoice_routing_info = payload["invoice_routing_info"]["invoice_routing_info"]
|
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
|
# TODO legacy mpp payment, use total_msat from trampoline onion
|
||||||
else:
|
else:
|
||||||
self.logger.info('forward_trampoline: end-to-end')
|
self.logger.info('forward_trampoline: end-to-end')
|
||||||
invoice_features = LnFeatures.BASIC_MPP_OPT
|
invoice_features = LnFeatures.BASIC_MPP_OPT
|
||||||
next_trampoline_onion = trampoline_onion.next_packet
|
next_trampoline_onion = trampoline_onion.next_packet
|
||||||
|
r_tags = []
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception('')
|
self.logger.exception('')
|
||||||
raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
|
raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
|
||||||
@@ -1732,7 +1735,7 @@ class Peer(Logger):
|
|||||||
payment_secret=payment_secret,
|
payment_secret=payment_secret,
|
||||||
amount_to_pay=amt_to_forward,
|
amount_to_pay=amt_to_forward,
|
||||||
min_cltv_expiry=cltv_from_onion,
|
min_cltv_expiry=cltv_from_onion,
|
||||||
r_tags=[],
|
r_tags=r_tags,
|
||||||
invoice_features=invoice_features,
|
invoice_features=invoice_features,
|
||||||
fwd_trampoline_onion=next_trampoline_onion,
|
fwd_trampoline_onion=next_trampoline_onion,
|
||||||
fwd_trampoline_fee=trampoline_fee,
|
fwd_trampoline_fee=trampoline_fee,
|
||||||
|
|||||||
@@ -1407,23 +1407,24 @@ class TestPeer(ElectrumTestCase):
|
|||||||
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['square_graph'])
|
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['square_graph'])
|
||||||
await self._run_mpp(graph, {'mpp_invoice': False}, {'mpp_invoice': True})
|
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):
|
async def _run_trampoline_payment(
|
||||||
if drop_dave is None: drop_dave = []
|
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):
|
async def pay(lnaddr, pay_req):
|
||||||
self.assertEqual(PR_UNPAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash))
|
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:
|
if result:
|
||||||
self.assertEqual(PR_PAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash))
|
self.assertEqual(PR_PAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash))
|
||||||
raise PaymentDone()
|
raise PaymentDone()
|
||||||
else:
|
else:
|
||||||
raise NoPathFound()
|
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():
|
async def f():
|
||||||
await self._activate_trampoline(graph.workers['alice'])
|
await self._activate_trampoline(graph.workers['alice'])
|
||||||
async with OldTaskGroup() as group:
|
async with OldTaskGroup() as group:
|
||||||
@@ -1432,17 +1433,17 @@ class TestPeer(ElectrumTestCase):
|
|||||||
await group.spawn(peer.htlc_switch())
|
await group.spawn(peer.htlc_switch())
|
||||||
for peer in peers:
|
for peer in peers:
|
||||||
await peer.initialized
|
await peer.initialized
|
||||||
lnaddr, pay_req = self.prepare_invoice(graph.workers['dave'], include_routing_hints=True)
|
lnaddr, pay_req = self.prepare_invoice(graph.workers['dave'], include_routing_hints=include_routing_hints)
|
||||||
for p in drop_dave:
|
|
||||||
do_drop_dave(p)
|
|
||||||
await group.spawn(pay(lnaddr, pay_req))
|
await group.spawn(pay(lnaddr, pay_req))
|
||||||
|
|
||||||
graph_definition = self.GRAPH_DEFINITIONS['square_graph']
|
graph_definition = self.GRAPH_DEFINITIONS['square_graph']
|
||||||
if not direct:
|
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['alice']['channels']['carol'] = depleted_channel
|
||||||
|
graph_definition['bob']['channels']['dave'] = depleted_channel
|
||||||
# insert a channel from bob to carol
|
# 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:
|
if test_mpp_consolidation:
|
||||||
# deplete alice to carol so that all htlcs go through bob
|
# deplete alice to carol so that all htlcs go through bob
|
||||||
@@ -1475,7 +1476,9 @@ class TestPeer(ElectrumTestCase):
|
|||||||
@needs_test_with_all_chacha20_implementations
|
@needs_test_with_all_chacha20_implementations
|
||||||
async def test_payment_trampoline_legacy(self):
|
async def test_payment_trampoline_legacy(self):
|
||||||
with self.assertRaises(PaymentDone):
|
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
|
@needs_test_with_all_chacha20_implementations
|
||||||
async def test_payment_trampoline_e2e_direct(self):
|
async def test_payment_trampoline_e2e_direct(self):
|
||||||
@@ -1486,10 +1489,7 @@ class TestPeer(ElectrumTestCase):
|
|||||||
async def test_payment_trampoline_e2e_indirect(self):
|
async def test_payment_trampoline_e2e_indirect(self):
|
||||||
# must use two trampolines
|
# must use two trampolines
|
||||||
with self.assertRaises(PaymentDone):
|
with self.assertRaises(PaymentDone):
|
||||||
await self._run_trampoline_payment(is_legacy=False, direct=False, drop_dave=['bob'])
|
await self._run_trampoline_payment(is_legacy=False, direct=False)
|
||||||
# both trampolines drop dave
|
|
||||||
with self.assertRaises(NoPathFound):
|
|
||||||
await self._run_trampoline_payment(is_legacy=False, direct=False, drop_dave=['bob', 'carol'])
|
|
||||||
|
|
||||||
@needs_test_with_all_chacha20_implementations
|
@needs_test_with_all_chacha20_implementations
|
||||||
async def test_payment_multipart_trampoline_e2e(self):
|
async def test_payment_multipart_trampoline_e2e(self):
|
||||||
|
|||||||
@@ -92,10 +92,30 @@ def encode_routing_info(r_tags):
|
|||||||
for route in r_tags:
|
for route in r_tags:
|
||||||
result.append(bitstring.pack('uint:8', len(route)))
|
result.append(bitstring.pack('uint:8', len(route)))
|
||||||
for step in route:
|
for step in route:
|
||||||
pubkey, channel, feebase, feerate, cltv = step
|
pubkey, scid, 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))
|
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()
|
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]]:
|
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.
|
"""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
|
# the last trampoline onion must contain routing hints for the last trampoline
|
||||||
# node to find the recipient
|
# node to find the recipient
|
||||||
invoice_routing_info = encode_routing_info(r_tags)
|
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
|
# lnwire invoice_features for trampoline is u64
|
||||||
invoice_features = invoice_features & 0xffffffffffffffff
|
invoice_features = invoice_features & 0xffffffffffffffff
|
||||||
route[-1].invoice_routing_info = invoice_routing_info
|
route[-1].invoice_routing_info = invoice_routing_info
|
||||||
|
|||||||
Reference in New Issue
Block a user