Fix LSPS2 variable-amount JIT fee ceiling to cover the full payment - #1095
Open
ajaysehwal wants to merge 1 commit into
Open
Fix LSPS2 variable-amount JIT fee ceiling to cover the full payment#1095ajaysehwal wants to merge 1 commit into
ajaysehwal wants to merge 1 commit into
Conversation
|
I've assigned @tnull as a reviewer! |
ajaysehwal
force-pushed
the
fix/lsps2-variable-amount-fee-ceiling
branch
from
September 11, 2026 16:04
8cc9b50 to
288f8b8
Compare
ajaysehwal
force-pushed
the
fix/lsps2-variable-amount-fee-ceiling
branch
from
September 11, 2026 16:53
e15a80e to
288f8b8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a bug in
EventHandler::lsps2_max_total_opening_fee_msatwhere the admissible-fee ceiling for the variable-amount LSPS2 JIT-channel flow (receive_variable_amount_via_jit_channel[_for_hash]) was computed from the post-skimPaymentClaimable::amount_msatinstead of the amount the payer actually sent.Per bLIP-52:
payment_size_msatis what the payer sent; the LSP forwardspayment_size_msat - opening_fee, which is theamount_msatwe observe inPaymentClaimable. The buggy code computed the ceiling ascompute_opening_fee(amount_msat, 0, max_prop_fee)— over the already-discounted amount — which is a strictly smaller base than the fee was actually charged against. As a result, for everychannel_opening_fee_ppm > 0the computed ceiling ends up below the LSP's correctly-computed fee, andfail_claimable_payment()is called, after the LSP has already opened and funded the JIT channel.Example (
channel_opening_fee_ppm = 10_000, i.e. 1%)ceil(1,000,000 × 10,000 / 1,000,000)amount_msatdelivered to us (post-skim)main—ceil(990,000 × 10,000 / 1,000,000)10,000 > 9,900→Because the fixed-amount JIT flow stores an absolute
max_total_opening_fee_msatcap (unaffected by this bug), none of the existing integration tests — which all usereceive_via_jit_channel[_for_hash]— exercise the broken branch.Fix
Reconstruct the pre-skim payment size the same way the handler already does a few lines below (when recording the invoice amount):
amount_msat.saturating_add(counterparty_skimmed_fee_msat).min_fee_msatremains hardcoded to0here (pre-existing, out of scope for this PR —LSPS2Parametersdoesn't currently carry the agreedmin_fee_msatfor the variable-amount flow; happy to follow up separately if useful).Testing
Unit tests (
src/event.rs, all passing on this branch,cargo test --lib):lsps2_proportional_fee_limit_admits_the_agreed_fee— reproduces the bug across a range of payment sizes (100k–1B msat). Confirmed this fails on unmodifiedmain(ceiling 9900 rejects the agreed 1% fee) and passes with the fix; also asserts the recomputed ceiling matches the LSP's fee exactly, not just as an upper bound.lsps2_proportional_fee_limit_of_zero_admits_no_skimmed_fee— zero-ppm / zero-skim regression guard.lsps2_absolute_fee_limit_ignores_skimmed_fee_argument—max_total_opening_fee_msat, when set, stays an unconditional cap and doesn't get the skimmed-fee argument threaded into it.lsps2_proportional_fee_limit_saturates_instead_of_panicking_on_overflow—u64::MAX-adjacent inputs don't panic; an internalcompute_opening_feeoverflow fails closed (None→ payment refused) rather than wrapping.I manually reverted just the fix (keeping the new tests) and confirmed exactly
lsps2_proportional_fee_limit_admits_the_agreed_feefails, while the other three pass either way — they pin separate properties, not this specific reconstruction bug.Integration test (
tests/integration_tests_rust.rs):lsps2_variable_amount_jit_channel_with_proportional_fee_succeeds— mirrorslsps2_client_service_integrationbut drives the flow throughreceive_variable_amount_via_jit_channel+send_using_amountend-to-end (service/client/payer nodes, real channel open, realPaymentForwarded/PaymentReceived), asserting the payment succeeds andcounterparty_skimmed_fee_msatmatches the LSP's fee. This test compiles and is believed correct (built directly from the working, executedlsps2_client_service_integrationtemplate with matching macros/helpers), but I wasn't able to execute it in my sandbox — thecorepc-node/electrsddownloadedbitcoind/electrsbinaries fail macOS code-signature validation there (unrelated to this repo). Happy to have CI confirm, or iterate if it needs adjustment.