Skip to content

Commit b3af267

Browse files
committed
onion_messages: filter correct feature when creating paths
Filter channels/peers in `get_blinded_paths_to_me` depending on the context (onion message or blinded payment path). Raise according exceptions when no channel/peer is available.
1 parent 2ebfb99 commit b3af267

2 files changed

Lines changed: 33 additions & 22 deletions

File tree

electrum/onion_message.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ def now() -> float:
6666
PAYMENT_PATHS_MAX = 3
6767

6868

69+
class NoOnionMessagePeers(Exception): pass
70+
class NoRouteBlindingChannelPeers(Exception): pass
71+
72+
6973
class NoRouteFound(Exception):
7074
def __init__(self, *args, peer_address: 'LNPeerAddr' = None):
7175
Exception.__init__(self, *args)
@@ -413,23 +417,25 @@ def get_blinded_paths_to_me(
413417
) -> Tuple[Sequence[dict], Sequence[dict]]:
414418
"""construct a list of blinded paths.
415419
current logic:
416-
- uses channels peers if not onion_message
417-
- uses current onion_message capable channel peers if exist and if onion_message
418-
- otherwise, uses current onion_message capable peers if onion_message
419-
- reply_path introduction points are direct peers only (TODO: longer paths)"""
420+
- uses active channel peers if my_channels not provided
421+
- if onion_message, filters channels for onion_message feature
422+
- if not onion_message, filters channels for route_blinding feature
423+
- if onion_message and no suitable channel peers, tries onion_message capable peers
424+
- raises if no blinded path could be generated
425+
- reply_path introduction points are direct peers only (TODO: longer paths)
426+
"""
420427
# TODO: build longer paths and/or add dummy hops to increase privacy
421428
if not my_channels:
422-
my_active_channels = [chan for chan in lnwallet.channels.values() if chan.is_active()]
423-
my_channels = my_active_channels
429+
my_channels = [chan for chan in lnwallet.channels.values() if chan.is_active()]
424430

425-
if onion_message:
426-
my_channels = [chan for chan in my_channels if lnwallet.lnpeermgr.get_peer_by_pubkey(chan.node_id) and
427-
lnwallet.lnpeermgr.get_peer_by_pubkey(chan.node_id).their_features.supports(LnFeatures.OPTION_ONION_MESSAGE_OPT)]
431+
required_features = LnFeatures.OPTION_ONION_MESSAGE_OPT if onion_message else LnFeatures.OPTION_ROUTE_BLINDING_OPT
432+
my_channels = [chan for chan in my_channels if lnwallet.lnpeermgr.get_peer_by_pubkey(chan.node_id) and
433+
lnwallet.lnpeermgr.get_peer_by_pubkey(chan.node_id).their_features.supports(required_features)]
428434

429435
result = []
430436
payinfos = []
431437
mynodeid = lnwallet.node_keypair.pubkey
432-
if len(my_channels):
438+
if my_channels:
433439
rchans = random_shuffled_copy(my_channels)
434440
for chan in rchans[:max_paths]:
435441
hop_extras = None
@@ -448,16 +454,22 @@ def get_blinded_paths_to_me(
448454
channels=[chan] if not onion_message else None,
449455
)
450456
result.append(blinded_path)
451-
elif onion_message:
452-
# we can use peers even without channels for onion messages
453-
my_onionmsg_peers = [peer for peer in lnwallet.lnpeermgr.peers.values() if
454-
peer.their_features.supports(LnFeatures.OPTION_ONION_MESSAGE_OPT)]
455-
if len(my_onionmsg_peers):
457+
458+
if not result:
459+
if not onion_message:
460+
raise NoRouteBlindingChannelPeers('no OPTION_ROUTE_BLINDING capable channel peers')
461+
else:
462+
# fall back to peers without channels for onion messages
463+
my_onionmsg_peers = [peer for peer in lnwallet.lnpeermgr.peers.values() if
464+
peer.their_features.supports(LnFeatures.OPTION_ONION_MESSAGE_OPT)]
465+
if not my_onionmsg_peers:
466+
raise NoOnionMessagePeers('no ONION_MESSAGE capable peers')
456467
rpeers = random_shuffled_copy(my_onionmsg_peers)
457468
for peer in rpeers[:max_paths]:
458469
blinded_path = create_blinded_path(os.urandom(32), [peer.pubkey, mynodeid], final_recipient_data)
459470
result.append(blinded_path)
460471

472+
assert result
461473
return result, payinfos
462474

463475

@@ -709,9 +721,6 @@ def _send_pending_message(self, key: bytes) -> None:
709721
# unless explicitly set in payload, generate reply_path here
710722
path_id = self._path_id_from_payload_and_key(payload, key)
711723
reply_paths = get_blinded_reply_paths(self.lnwallet, path_id, max_paths=1)
712-
if not reply_paths:
713-
raise Exception(f'Could not create a reply_path for {key=}. No active peers?')
714-
715724
final_payload['reply_path'] = {'path': reply_paths}
716725

717726
# NOTE: we could also try alternate paths to introduction point (the non-blinded part of the route)

tests/test_onion_message.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
MIN_FINAL_CLTV_DELTA_BUFFER_INVOICE)
2424
from electrum.onion_message import (
2525
create_blinded_path, OnionMessageManager, NoRouteFound, Timeout,
26-
create_route_to_introduction_point, get_blinded_paths_to_me
26+
create_route_to_introduction_point, get_blinded_paths_to_me, NoOnionMessagePeers
2727
)
2828
from electrum.util import bfh, read_json_file, OldTaskGroup, get_asyncio_loop
2929
from electrum.logging import console_stderr_handler
@@ -272,12 +272,13 @@ def __init__(self):
272272
self.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS = True
273273

274274

275-
class MockPeer:
276-
their_features = LnFeatures(LnFeatures.OPTION_ONION_MESSAGE_OPT)
275+
ONION_MESSAGE_CAPABLE_PEER_FEATURES = LnFeatures(LnFeatures.OPTION_ONION_MESSAGE_OPT)
277276

278-
def __init__(self, pubkey, on_send_message=None):
277+
class MockPeer:
278+
def __init__(self, pubkey, on_send_message=None, their_features=ONION_MESSAGE_CAPABLE_PEER_FEATURES):
279279
self.pubkey = pubkey
280280
self.on_send_message = on_send_message
281+
self.their_features = their_features
281282

282283
async def wait_one_htlc_switch_iteration(self, *args):
283284
pass
@@ -502,6 +503,7 @@ async def test_get_blinded_paths_to_me_payment(self):
502503
bob_update = decode_msg(bob_update_raw)[1]
503504
bob_update['raw'] = bob_update_raw
504505
alice_chan.set_remote_update(bob_update)
506+
alice.lnpeermgr.get_peer_by_pubkey(bob.node_keypair.pubkey).their_features |= LnFeatures.OPTION_ROUTE_BLINDING_OPT
505507

506508
final_recipient_data = {'path_id': {'data': os.urandom(32)}}
507509
paths, payinfos = get_blinded_paths_to_me(alice, final_recipient_data, onion_message=False)

0 commit comments

Comments
 (0)