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 .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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user