Skip to content

Disable mpp flags in invoice creation if jit channel is required and consider available liquidity #9587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion electrum/lnpeer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1870,7 +1870,7 @@ def log_fail_reason(reason: str):
next_chan = self.lnworker.get_channel_by_short_id(next_chan_scid)

if self.lnworker.features.supports(LnFeatures.OPTION_ZEROCONF_OPT):
next_peer = self.lnworker.get_peer_by_scid_alias(next_chan_scid)
next_peer = self.lnworker.get_peer_by_static_jit_scid_alias(next_chan_scid)
else:
next_peer = None

Expand Down
35 changes: 27 additions & 8 deletions electrum/lnworker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,7 @@ async def handle_onchain_state(self, chan: Channel):
self.logger.info('REBROADCASTING CLOSING TX')
await self.network.try_broadcasting(force_close_tx, 'force-close')

def get_peer_by_scid_alias(self, scid_alias: bytes) -> Optional[Peer]:
def get_peer_by_static_jit_scid_alias(self, scid_alias: bytes) -> Optional[Peer]:
for nodeid, peer in self.peers.items():
if scid_alias == self._scid_alias_of_node(nodeid):
return peer
Expand All @@ -1161,7 +1161,7 @@ def _scid_alias_of_node(self, nodeid: bytes) -> bytes:
# scid alias for just-in-time channels
return sha256(b'Electrum' + nodeid)[0:8]

def get_scid_alias(self) -> bytes:
def get_static_jit_scid_alias(self) -> bytes:
return self._scid_alias_of_node(self.node_keypair.pubkey)

@log_exceptions
Expand Down Expand Up @@ -2112,11 +2112,15 @@ def get_bolt11_invoice(

assert amount_msat is None or amount_msat > 0
timestamp = int(time.time())
routing_hints = self.calc_routing_hints_for_invoice(amount_msat, channels=channels)
self.logger.info(f"creating bolt11 invoice with routing_hints: {routing_hints}")
needs_jit: bool = self.receive_requires_jit_channel(amount_msat)
routing_hints = self.calc_routing_hints_for_invoice(amount_msat, channels=channels, needs_jit=needs_jit)
self.logger.info(f"creating bolt11 invoice with routing_hints: {routing_hints}, jit: {needs_jit}")
invoice_features = self.features.for_invoice()
if not self.uses_trampoline():
invoice_features &= ~ LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM
if needs_jit:
# jit only works with single htlcs, mpp will cause LSP to open channels for each htlc
invoice_features &= ~ LnFeatures.BASIC_MPP_OPT & ~ LnFeatures.BASIC_MPP_REQ
payment_secret = self.get_payment_secret(payment_hash)
amount_btc = amount_msat/Decimal(COIN*1000) if amount_msat else None
if expiry == 0:
Expand Down Expand Up @@ -2505,14 +2509,14 @@ def htlc_failed(
else:
self.logger.info(f"waiting for other htlcs to fail (phash={payment_hash.hex()})")

def calc_routing_hints_for_invoice(self, amount_msat: Optional[int], channels=None):
def calc_routing_hints_for_invoice(self, amount_msat: Optional[int], channels=None, needs_jit=False):
"""calculate routing hints (BOLT-11 'r' field)"""
routing_hints = []
if self.config.ZEROCONF_TRUSTED_NODE:
if needs_jit:
node_id, rest = extract_nodeid(self.config.ZEROCONF_TRUSTED_NODE)
alias_or_scid = self.get_scid_alias()
alias_or_scid = self.get_static_jit_scid_alias()
routing_hints.append(('r', [(node_id, alias_or_scid, 0, 0, 144)]))
# no need for more
# no need for more because we cannot receive enough through the others and mpp is disabled for jit
channels = []
else:
if channels is None:
Expand Down Expand Up @@ -2665,6 +2669,21 @@ def recv_capacity(chan):
can_receive_msat = max(recv_chan_msats)
return Decimal(can_receive_msat) / 1000

def receive_requires_jit_channel(self, amount_msat: Optional[int]) -> bool:
"""Returns true if we cannot receive the amount and have set up a trusted LSP node.
Cannot work reliably with 0 amount invoices as we don't know if we are able to receive it.
"""
# a trusted zeroconf node is configured
if (self.config.ZEROCONF_TRUSTED_NODE
# the zeroconf node is a peer, it doesn't make sense to request a channel from an offline LSP
and extract_nodeid(self.config.ZEROCONF_TRUSTED_NODE)[0] in self.peers
# we cannot receive the amount specified
and ((amount_msat and self.num_sats_can_receive() < (amount_msat // 1000))
# or we cannot receive anything, and it's a 0 amount invoice
or (not amount_msat and self.num_sats_can_receive() < 1))):
return True
return False

def _suggest_channels_for_rebalance(self, direction, amount_sat) -> Sequence[Tuple[Channel, int]]:
"""
Suggest a channel and amount to send/receive with that channel, so that we will be able to receive/send amount_sat
Expand Down