Skip to content

lightning-liquidity: Refactor LSPS1 service-side#4282

Open
tnull wants to merge 29 commits intolightningdevkit:mainfrom
tnull:2025-11-lsps1-refactor
Open

lightning-liquidity: Refactor LSPS1 service-side#4282
tnull wants to merge 29 commits intolightningdevkit:mainfrom
tnull:2025-11-lsps1-refactor

Conversation

@tnull
Copy link
Contributor

@tnull tnull commented Dec 12, 2025

Closes #3480.

We 'refactor' (rewrite) the LSPS1ServiceHandler, move state handling to a dedicated PeerState, add an STM pattern, add persistence for the service state, add some more critical API paths, add test coverage, and finally remove the cfg(lsps1_service) flag.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Dec 12, 2025

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnull tnull marked this pull request as draft December 12, 2025 15:28
@tnull tnull force-pushed the 2025-11-lsps1-refactor branch 4 times, most recently from 773316a to fb519ab Compare December 12, 2025 16:08
@codecov
Copy link

codecov bot commented Dec 12, 2025

Codecov Report

❌ Patch coverage is 73.15668% with 233 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.22%. Comparing base (f43803d) to head (922b0fa).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
lightning-liquidity/src/lsps1/service.rs 60.33% 123 Missing and 17 partials ⚠️
lightning-liquidity/src/lsps1/peer_state.rs 84.88% 29 Missing and 31 partials ⚠️
lightning/src/util/ser.rs 48.27% 10 Missing and 5 partials ⚠️
lightning-liquidity/src/persist.rs 65.51% 6 Missing and 4 partials ⚠️
lightning-liquidity/src/manager.rs 82.05% 2 Missing and 5 partials ⚠️
lightning-liquidity/src/lsps1/msgs.rs 90.90% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4282      +/-   ##
==========================================
+ Coverage   86.01%   86.22%   +0.21%     
==========================================
  Files         156      158       +2     
  Lines      102857   103776     +919     
  Branches   102857   103776     +919     
==========================================
+ Hits        88474    89484    +1010     
+ Misses      11876    11705     -171     
- Partials     2507     2587      +80     
Flag Coverage Δ
tests 86.22% <73.15%> (+0.21%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tnull tnull force-pushed the 2025-11-lsps1-refactor branch 2 times, most recently from b96685b to c6eb6b3 Compare December 15, 2025 12:27
@tnull tnull self-assigned this Dec 18, 2025
@tnull tnull moved this to Goal: Merge in Weekly Goals Dec 18, 2025
@tnull tnull added this to the 0.3 milestone Jan 29, 2026
martinsaposnic and others added 17 commits February 4, 2026 15:05
We add the first LSPS1 integration test. This is based on the unfinished
work in lightningdevkit#3864, but
rebased to account for the new ways we now do integration test setup.
.. for which we got warnings
We previously considered tracking payment confirmations as part of the
handler. However, we can considerably simplify our logic if we stick
with the current approach of having the LSPs track the payment status
and update us when prompted through events.
Now that we don't do on-chain tracking in LSPS1, we can drop quite a few
`LiquidityManager` parameters and generics, which were only added in
anticipation of tracking on-chain state.

Signed-off-by: Elias Rohrer <dev@tnull.de>
We move the `PeerState` related types to a new module. In the following
commits we'll bit-by-bit drop the `pub(super)`s introduced here,
asserting better separation of state and logic going forward.
.. we will re-add a proper state machine in a later commit, but for now
we can just drop all of this half-baked logic that doesn't actually do
anything.
.. requiring less access to internals
Previously, we'd directly access the internal `outbound_` map of
`PeerState`. Here we refactor the code to avoid this.

Note this also highlighted a bug in that we currently don't actually
update/persist the order state in `update_order_state`. We don't fix
this here, but just improve isolation for now, as all state update
behavior will be reworked later.
We introduce two new methods on `PeerState` to avoid direct access to
the internal `pending_requests` map.
The `OutboundChannel` construct simply wrapped `ChannelOrder` which we
can now simply use directly.
We here remember and update the order state and channel details in
`ChannelOrder`
Since we by now have the `TimeProvider` trait, we might as well use it
in `LSPS1ServiceHandler` instead of requiring the user to provide a
`created_at` manually.

Signed-off-by: Elias Rohrer <dev@tnull.de>
In the future we might want to inline the fields in `LSPS1ServiceConfig`
(especially once some are added that we'd want to always/never set for
the user), but for now we just make the `supported_options` field in
`LSPS1ServiceConfig` required, avoiding some dangerous `unwrap`s.
Previously, we'd use an event to have the user check the order status
and then call back in. As we already track the order status, we here
change that to a model where we respond immediately based on our state
and have the user/LSP update that state whenever it detects a change
(e.g., a received payment, reorg, etc.). In the next commmit we will
add/modify the corresponding API methods to do so.
We add the serializations for all types that will be persisted as part
of the `PeerState`.
We follow the model already employed in LSPS2/LSPS5 and implement
state pruning and persistence for `LSPS1ServiceHandler` state.

Signed-off-by: Elias Rohrer <dev@tnull.de>
.. we read the persisted state in `LiquidityManager::new`

Signed-off-by: Elias Rohrer <dev@tnull.de>
tnull added 7 commits February 4, 2026 15:51
As per spec, we check that the user provides at least one payment detail
*and* that they don't provide onchain payment details if
`refund_onchain_address` is unset.
We add a method that allows the LSP to signal to the client the token
they used was invalid.

We use the `102` error code as proposed in
lightning/blips#68.
We test the just-added API.

Co-authored by Claude AI
@tnull tnull force-pushed the 2025-11-lsps1-refactor branch from c6eb6b3 to a2aa7c3 Compare February 4, 2026 14:53
@tnull
Copy link
Contributor Author

tnull commented Feb 4, 2026

Rebased to resolve conflicts.

tnull added 5 commits February 5, 2026 13:31
This refactors `ChannelOrder` to use an internal state machine enum
`ChannelOrderState` that:
- Encapsulates state-specific data in variants (e.g., `channel_info`
  only available in `CompletedAndChannelOpened`)
- Provides type-safe state transitions
- Replaces the generic `update_order_status` API with specific
  transition methods: `order_payment_received`, `order_channel_opened`,
  and `order_failed_and_refunded`

The state machine has four states:
- `ExpectingPayment`: Initial state, awaiting payment
- `OrderPaid`: Payment received, awaiting channel open
- `CompletedAndChannelOpened`: Terminal state with channel info
- `FailedAndRefunded`: Terminal state for failed/refunded orders

Co-Authored-By: HAL 9000
Signed-off-by: Elias Rohrer <dev@tnull.de>
Add two new integration tests to cover the new public API methods:

- `lsps1_order_state_transitions`: Tests the full flow of
  `order_payment_received` followed by `order_channel_opened`,
  verifying that payment states are updated correctly and channel
  info is returned after the channel is opened.

- `lsps1_order_failed_and_refunded`: Tests the `order_failed_and_refunded`
  method, verifying that payment states are set to Refunded.

Co-Authored-By: HAL 9000
Add `lsps1_expired_orders_are_pruned_and_not_persisted` test that verifies:

- Orders with expired payment details (expires_at in the past) are
  accessible before persist() is called
- After persist() is called, expired orders in ExpectingPayment state
  are pruned and no longer accessible
- Pruned orders are not recovered after restart, confirming that
  the pruning also removes the persisted state

Co-Authored-By: HAL 9000
The bLIP-51 specification defines a `HOLD` intermediate payment state:
- `EXPECT_PAYMENT` -> `HOLD` -> `PAID` (success path)
- `EXPECT_PAYMENT` -> `REFUNDED` (failure before payment)
- `HOLD` -> `REFUNDED` (failure after payment received)

This commit adds the `Hold` variant to `LSPS1PaymentState` and updates
the state machine transitions:

- `payment_received()` now sets payment state to `Hold` (not `Paid`)
- `channel_opened()` transitions payment state from `Hold` to `Paid`
- Tests updated to verify the correct state at each transition

This allows LSPs to properly communicate when a payment has been
received but the channel has not yet been opened (e.g., Lightning
HTLC held, or on-chain tx detected but channel funding not published).

Co-Authored-By: HAL 9000
@tnull tnull marked this pull request as ready for review February 5, 2026 12:58
@tnull tnull requested a review from TheBlueMatt February 5, 2026 12:58
@tnull
Copy link
Contributor Author

tnull commented Feb 5, 2026

Should be good for review.

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

Labels

None yet

Projects

Status: Goal: Merge

Development

Successfully merging this pull request may close these issues.

LSPS1 integration tracking issue

3 participants