|
|
@ -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) |
|
|
|