Skip to content

lnpeer: deduct jit channel fees from total amount#10348

Merged
ecdsa merged 2 commits intospesmilo:masterfrom
f321x:fix_jit_channel_openings_regtest
Dec 6, 2025
Merged

lnpeer: deduct jit channel fees from total amount#10348
ecdsa merged 2 commits intospesmilo:masterfrom
f321x:fix_jit_channel_openings_regtest

Conversation

@f321x
Copy link
Copy Markdown
Member

@f321x f321x commented Dec 5, 2025

Deduct the just in time channel opening fees from a htlc sets total amount so htlcs don't get timed out if they come from a just in time channel with opening fee.

Fixes the regtest on master, however trampoline payments might need some additional changes.

Related: #9584

@f321x f321x force-pushed the fix_jit_channel_openings_regtest branch from 48ca037 to b244b68 Compare December 5, 2025 16:35
@f321x f321x marked this pull request as draft December 5, 2025 16:41
Deduct the just in time channel opening fees from the total amount so
htlcs don't get timed out if they come from a just in time channel with
opening fee.

Related: spesmilo#9584
Comment thread electrum/lnpeer.py Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this probably requires the same change as below?

-                if amount_msat >= any_trampoline_onion.amt_to_forward:
+                if amount_msat >= (any_trampoline_onion.amt_to_forward - jit_opening_fees_msat):

Copy link
Copy Markdown
Member

@SomberNight SomberNight Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ehhhh maybeee... I am not so sure. but maybe I am confused... This might also introduce subtle bugs.
I mean, now you are allowing each first stage to be missing up to jit_opening_fees, and then after they are aggregated, allowing the overall second stage to be missing up to jit_opening_fees.

but you transition every first stage as soon as it is missing less than jit_opening_fees - that means if any of the later-arriving HTLCs are smaller than jit_opening_fees, the payment will not succeed. Because you already transitioned the corresponding first stage set.

Imagine the invoice is for 100 sat, jit_opening_fees = 5 sat, and the sender creates 2 trampoline MPP sets of 50 sat each. So any_trampoline_onion.amt_to_forward == 50 sat. Further imagine the final TF splits the payment to forward 10 htlcs of value 5 sat each. So the final recipient is running this code,

  • receives 9 htlcs for set1, sees that 9*5 >= 50-5, transitions it to stage2,
  • receives 9 htlcs for set2, sees that 9*5 >= 50-5, transitions it to stage2,
  • subsequent htlcs are rejected and stage2 can never complete

or am I missing something?

(for ref lightning/bolts@bc7a1a0)


This stuff is quite complicated, and it was still a draft, so I think you should have waited for @f321x's response before merging it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I did not see that. indeed, it is quite complicated

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the payment consists of a single trampoline part, which it should as we request no mpp in the invoice, this should work. In #10351 i made this more explicit by deducting the jit fees from any_trampoline_onion.amt_to_forward.total_msat instead of any_trampoline_onion.amt_to_forward.

@ecdsa ecdsa force-pushed the fix_jit_channel_openings_regtest branch from b244b68 to da99815 Compare December 6, 2025 09:50
@ecdsa ecdsa marked this pull request as ready for review December 6, 2025 09:51
@ecdsa ecdsa merged commit 4d3ead3 into spesmilo:master Dec 6, 2025
13 of 16 checks passed
Comment thread electrum/lnpeer.py
Comment on lines +3076 to +3078
# calculate the sum of just in time channel opening fees
htlc_channels = [self.lnworker.get_channel_by_short_id(scid) for scid in set(h.scid for h in mpp_set.htlcs)]
jit_opening_fees_msat = sum((c.jit_opening_fee or 0) for c in htlc_channels)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the SCID before the channel gets mined? Or is it already mined at this point? I am confused.

(To be clear, chan.jit_opening_fee being set means we are the enduser here, and not the LSP, right?)

Copy link
Copy Markdown
Member Author

@f321x f321x Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the SCID before the channel gets mined? Or is it already mined at this point? I am confused.

The funding transaction will only get broadcast by the channel provider once we release the preimage, so the channel is not yet mined and the SCID is None. I wasn't aware of this, it doesn't seem safe to handle channels by scid if the scid can be None for some. Using and storing the plain channel_id instead of the scid in ReceivedMPPHtlc seems like a less error prone approach.

(To be clear, chan.jit_opening_fee being set means we are the enduser here, and not the LSP, right?)

yes

Comment thread electrum/lnpeer.py
Comment on lines +2121 to 2125
if chan.jit_opening_fee:
channel_opening_fee = chan.jit_opening_fee
total_msat -= channel_opening_fee
amt_to_forward -= channel_opening_fee
else:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how exactly are JIT channels supposed to work with MPP? Is jit_opening_fee supposed to be paid only once? Here we deduct it per number of first stages I guess??? (in case of trampoline)

Copy link
Copy Markdown
Member

@SomberNight SomberNight Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer disabling MPP for JIT channels. For the first invoice when the user does not have a chan yet, just don't signal MPP, and also validate when receiving the payment on the JIT channel that it uses a single HTLC.

I want simple, easy-to-reason-about behaviour.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, imagine Dave already has a channel, that can receive 900k sats. Dave creates an invoice for 1000k sats. Dave puts routing hints into his invoice, one for the existing chan and one that might trigger the LSP to JIT-open another channel.

Let's say Alice is trying to pay that invoice and is lucky, and manages to figure out this 900k+100k sat split, sending 900k to the old chan, and 100k separately through the LSP. Do we want the LSP to open a new channel based on this smaller 100k amount?
I guess currently we would have the LSP open a chan for 200k sat, and forward the 100k on that?

funding_sat = 2 * (next_amount_msat_htlc // 1000) # try to fully spend htlcs

but what if Alice sends 900k on existing chan,
plus 20k on "new" chan,
plus 20k on "new" chan,
plus 20k on "new" chan,
plus 20k on "new" chan,
plus 20k on "new" chan,
(no trampoline involved)?
Would the LSP now open 5 new JIT channels, each funded for 40k sats?
Like wtf?

Just disable MPP.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that lnworker.get_bolt11_invoice already creates invoices with MPP flags disabled, if the payment requires a just-in-time channel. However, this does not guarantee that the sender will not attempt a MPP.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah mpp is definitely not intended, i added checks in #10351 to fail htlcs if the sender ignores our invoice and sends mpp anyways.

Comment thread electrum/lnpeer.py Outdated
Copy link
Copy Markdown
Member

@SomberNight SomberNight Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ehhhh maybeee... I am not so sure. but maybe I am confused... This might also introduce subtle bugs.
I mean, now you are allowing each first stage to be missing up to jit_opening_fees, and then after they are aggregated, allowing the overall second stage to be missing up to jit_opening_fees.

but you transition every first stage as soon as it is missing less than jit_opening_fees - that means if any of the later-arriving HTLCs are smaller than jit_opening_fees, the payment will not succeed. Because you already transitioned the corresponding first stage set.

Imagine the invoice is for 100 sat, jit_opening_fees = 5 sat, and the sender creates 2 trampoline MPP sets of 50 sat each. So any_trampoline_onion.amt_to_forward == 50 sat. Further imagine the final TF splits the payment to forward 10 htlcs of value 5 sat each. So the final recipient is running this code,

  • receives 9 htlcs for set1, sees that 9*5 >= 50-5, transitions it to stage2,
  • receives 9 htlcs for set2, sees that 9*5 >= 50-5, transitions it to stage2,
  • subsequent htlcs are rejected and stage2 can never complete

or am I missing something?

(for ref lightning/bolts@bc7a1a0)


This stuff is quite complicated, and it was still a draft, so I think you should have waited for @f321x's response before merging it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants