Browse Source

lnworker: use suggest_splits for multiple routes

patch-4
bitromortac 4 years ago
parent
commit
e9fe0ed22b
No known key found for this signature in database GPG Key ID: 1965063FC13BEBE2
  1. 95
      electrum/lnworker.py
  2. 2
      electrum/tests/test_lnpeer.py

95
electrum/lnworker.py

@ -76,6 +76,7 @@ from .channel_db import UpdateStatus
from .channel_db import get_mychannel_info, get_mychannel_policy
from .submarine_swaps import SwapManager
from .channel_db import ChannelInfo, Policy
from .mpp_split import suggest_splits
if TYPE_CHECKING:
from .network import Network
@ -1019,7 +1020,7 @@ class LNWallet(LNWorker):
key = payment_hash.hex()
payment_secret = lnaddr.payment_secret
invoice_pubkey = lnaddr.pubkey.serialize()
invoice_features = lnaddr.get_tag('9') or 0
invoice_features = LnFeatures(lnaddr.get_tag('9') or 0)
r_tags = lnaddr.get_routing_info('r')
t_tags = lnaddr.get_routing_info('t')
amount_to_pay = lnaddr.get_amount_msat()
@ -1377,6 +1378,16 @@ class LNWallet(LNWorker):
node_features=trampoline_features))
return route
def channels_with_funds(self) -> Dict[bytes, int]:
"""Determines a dict of channels (keyed by channel id in bytes) that
maps to their spendable amounts."""
with self.lock:
channels = {}
for cid, chan in self._channels.items():
spend_amount = int(chan.available_to_spend(HTLCOwner.LOCAL))
channels[cid] = spend_amount
return channels
@profiler
def create_routes_for_payment(
self,
@ -1385,11 +1396,83 @@ class LNWallet(LNWorker):
min_cltv_expiry,
r_tags,
invoice_features,
*, full_path: LNPaymentPath = None) -> LNPaymentRoute:
# TODO: return multiples routes if we know that a single one will not work
# initially, try with less htlcs
*, full_path: LNPaymentPath = None) -> Sequence[Tuple[LNPaymentRoute, int]]:
"""Creates multiple routes for splitting a payment over the available
private channels.
We first try to conduct the payment over a single channel. If that fails
and mpp is supported by the receiver, we will split the payment."""
try: # to send over a single channel
routes = [self.create_route_for_payment(
amount_msat,
invoice_pubkey,
min_cltv_expiry,
r_tags,
invoice_features,
None,
full_path=full_path
)]
except NoPathFound:
if invoice_features & LnFeatures.BASIC_MPP_OPT:
# Create split configurations that are rated according to our
# preference (low rating=high preference).
split_configurations = suggest_splits(
amount_msat,
self.channels_with_funds()
)
self.logger.info("Created the following splitting configurations.")
for s in split_configurations:
self.logger.info(f"{s[0]} rating: {s[1]}")
routes = []
for s in split_configurations:
try:
for chanid, part_amount_msat in s[0].items():
if part_amount_msat:
channel = self.channels[chanid]
# It could happen that the pathfinding uses a channel
# in the graph multiple times, meaning we could exhaust
# its capacity. This could be dealt with by temporarily
# iteratively blacklisting channels for this mpp attempt.
route, amt = self.create_route_for_payment(
part_amount_msat,
invoice_pubkey,
min_cltv_expiry,
r_tags,
invoice_features,
channel,
full_path=None
)
routes.append((route, amt))
break
except NoPathFound:
routes = []
continue
else:
raise
if not routes:
raise NoPathFound
else:
return routes
def create_route_for_payment(
self,
amount_msat: int,
invoice_pubkey,
min_cltv_expiry,
r_tags,
invoice_features,
outgoing_channel: Channel = None,
*, full_path: Optional[LNPaymentPath]) -> Tuple[LNPaymentRoute, int]:
route = None
channels = list(self.channels.values())
# we can constrain the payment to a single outgoing channel
if outgoing_channel:
channels = [outgoing_channel]
else:
channels = list(self.channels.values())
scid_to_my_channels = {chan.short_channel_id: chan for chan in channels
if chan.short_channel_id is not None}
@ -1465,7 +1548,7 @@ class LNWallet(LNWorker):
# add features from invoice
route[-1].node_features |= invoice_features
# return a list of routes
return [(route, amount_msat)]
return route, amount_msat
def add_request(self, amount_sat, message, expiry) -> str:
coro = self._add_request_coro(amount_sat, message, expiry)

2
electrum/tests/test_lnpeer.py

@ -175,6 +175,7 @@ class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
htlc_failed = LNWallet.htlc_failed
save_preimage = LNWallet.save_preimage
get_preimage = LNWallet.get_preimage
create_route_for_payment = LNWallet.create_route_for_payment
create_routes_for_payment = LNWallet.create_routes_for_payment
create_routes_from_invoice = LNWallet.create_routes_from_invoice
_check_invoice = staticmethod(LNWallet._check_invoice)
@ -189,6 +190,7 @@ class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
channels_for_peer = LNWallet.channels_for_peer
_calc_routing_hints_for_invoice = LNWallet._calc_routing_hints_for_invoice
handle_error_code_from_failed_htlc = LNWallet.handle_error_code_from_failed_htlc
channels_with_funds = LNWallet.channels_with_funds
class MockTransport:

Loading…
Cancel
Save