Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c77b013
LSPS1: Add initial integration test
martinsaposnic Jun 16, 2025
06692e1
Cleanup unused code
tnull Nov 16, 2025
ab2d34b
Drop `chain_source` from `LSPS1ServiceHandler`
tnull Dec 9, 2025
56472d3
Drop `Listen`/`Confirm`/etc from `LiquidityManager`
tnull Dec 9, 2025
16d9f83
Move `PeerState` and related types to `peer_state.rs` module
tnull Nov 16, 2025
4c018b8
Drop bogus channel state handling
tnull Nov 16, 2025
79af070
Replace `insert_outbound_channel` with `PeerState::new_order`
tnull Nov 16, 2025
bcabf58
Use `PeerState::{get_order, has_active_requests}` instead of map
tnull Nov 16, 2025
b86fef1
Use `PeerState::{register,remove}_request` instead of map access
tnull Nov 16, 2025
10bbc60
Drop `OutboundCRChannel`
tnull Nov 16, 2025
52a2317
Actually remember the order state in `ChannelOrder`
tnull Nov 16, 2025
385e5a3
`LSPS1ServiceHandler`: Use `TimeProvider` when creating new orders
tnull Dec 10, 2025
c391aa4
Require `supported_options` in `LSPS1ServiceConfig`
tnull Dec 10, 2025
a58cde5
Respond to `GetOrder` requests from our saved state
tnull Dec 10, 2025
5135bd6
Add serialization logic for LSPS1 `PeerState` types
tnull Dec 11, 2025
09dcadb
Implement `LSPS1ServiceHandler` persistence and state pruning
tnull Dec 11, 2025
fbfd185
Read persisted LSPS1ServiceHandler state on startup
tnull Dec 12, 2025
57fe3cb
Add test case asserting `LSPS1ServiceState` is persisted across restarts
tnull Dec 12, 2025
cbcd1f9
Add some checks on provided payment details
tnull Dec 12, 2025
1507e60
Don't hold write lock in `LSPS{1,2}ServiceHandler::peer_disconnected`
tnull Dec 12, 2025
9680ad5
Add `invalid_token_provided` API method
tnull Dec 12, 2025
494be17
Add test case for `invalid_token_provided` flow
tnull Dec 12, 2025
c7651db
Drop `lsps1_service` cfg flag
tnull Dec 12, 2025
a2aa7c3
Fix clippy lints
tnull Dec 12, 2025
380071f
Refactor `ChannelOrder` to use `ChannelOrderState` state machine
tnull Feb 5, 2026
ad0e3b1
Add integration tests for LSPS1 order state transition API
tnull Feb 5, 2026
e46ea32
Add integration test for expired order pruning
tnull Feb 5, 2026
5ebd83e
Drop unused `LSPS1OnchainPayment` type
tnull Feb 5, 2026
922b0fa
Add `Hold` payment state per bLIP-51 spec
tnull Feb 5, 2026
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: 0 additions & 2 deletions ci/ci-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,4 @@ RUSTFLAGS="--cfg=taproot" cargo test --quiet --color always -p lightning
[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
RUSTFLAGS="--cfg=simple_close" cargo test --quiet --color always -p lightning
[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
RUSTFLAGS="--cfg=lsps1_service" cargo test --quiet --color always -p lightning-liquidity
[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
RUSTFLAGS="--cfg=peer_storage" cargo test --quiet --color always -p lightning
2 changes: 0 additions & 2 deletions fuzz/src/lsps_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ pub fn do_test(data: &[u8]) {
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&manager),
None::<Arc<dyn Filter + Send + Sync>>,
None,
kv_store,
Arc::clone(&tx_broadcaster),
None,
Expand Down
27 changes: 11 additions & 16 deletions lightning-background-processor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,6 @@ pub const NO_LIQUIDITY_MANAGER: Option<
NodeSigner = &(dyn lightning::sign::NodeSigner + Send + Sync),
AChannelManager = DynChannelManager,
CM = &DynChannelManager,
C = &(dyn chain::Filter + Send + Sync),
K = &DummyKVStore,
TimeProvider = dyn lightning_liquidity::utils::time::TimeProvider + Send + Sync,
TP = &(dyn lightning_liquidity::utils::time::TimeProvider + Send + Sync),
Expand All @@ -486,7 +485,6 @@ pub const NO_LIQUIDITY_MANAGER_SYNC: Option<
NodeSigner = &(dyn lightning::sign::NodeSigner + Send + Sync),
AChannelManager = DynChannelManager,
CM = &DynChannelManager,
C = &(dyn chain::Filter + Send + Sync),
KVStoreSync = dyn lightning::util::persist::KVStoreSync + Send + Sync,
KS = &(dyn lightning::util::persist::KVStoreSync + Send + Sync),
TimeProvider = dyn lightning_liquidity::utils::time::TimeProvider + Send + Sync,
Expand Down Expand Up @@ -829,7 +827,7 @@ use futures_util::{dummy_waker, Joiner, OptionalSelector, Selector, SelectorOutp
/// # type P2PGossipSync<UL> = lightning::routing::gossip::P2PGossipSync<Arc<NetworkGraph>, Arc<UL>, Arc<Logger>>;
/// # type ChannelManager<B, F, FE> = lightning::ln::channelmanager::SimpleArcChannelManager<ChainMonitor<B, F, FE>, B, FE, Logger>;
/// # type OnionMessenger<B, F, FE> = lightning::onion_message::messenger::OnionMessenger<Arc<lightning::sign::KeysManager>, Arc<lightning::sign::KeysManager>, Arc<Logger>, Arc<ChannelManager<B, F, FE>>, Arc<lightning::onion_message::messenger::DefaultMessageRouter<Arc<NetworkGraph>, Arc<Logger>, Arc<lightning::sign::KeysManager>>>, Arc<ChannelManager<B, F, FE>>, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler>;
/// # type LiquidityManager<B, F, FE> = lightning_liquidity::LiquidityManager<Arc<lightning::sign::KeysManager>, Arc<lightning::sign::KeysManager>, Arc<ChannelManager<B, F, FE>>, Arc<F>, Arc<Store>, Arc<DefaultTimeProvider>, Arc<B>>;
/// # type LiquidityManager<B, F, FE> = lightning_liquidity::LiquidityManager<Arc<lightning::sign::KeysManager>, Arc<lightning::sign::KeysManager>, Arc<ChannelManager<B, F, FE>>, Arc<Store>, Arc<DefaultTimeProvider>, Arc<B>>;
/// # type Scorer = RwLock<lightning::routing::scoring::ProbabilisticScorer<Arc<NetworkGraph>, Arc<Logger>>>;
/// # type PeerManager<B, F, FE, UL> = lightning::ln::peer_handler::SimpleArcPeerManager<SocketDescriptor, ChainMonitor<B, F, FE>, B, FE, Arc<UL>, Logger, F, StoreSync>;
/// # type OutputSweeper<B, D, FE, F, O> = lightning::util::sweep::OutputSweeper<Arc<B>, Arc<D>, Arc<FE>, Arc<F>, Arc<Store>, Arc<Logger>, Arc<O>>;
Expand Down Expand Up @@ -1898,7 +1896,7 @@ mod tests {
use core::sync::atomic::{AtomicBool, Ordering};
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::transaction::OutPoint;
use lightning::chain::{chainmonitor, BestBlock, Confirm, Filter};
use lightning::chain::{chainmonitor, BestBlock, Confirm};
use lightning::events::{Event, PathFailure, ReplayEvent};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
Expand Down Expand Up @@ -2054,7 +2052,6 @@ mod tests {
Arc<KeysManager>,
Arc<KeysManager>,
Arc<ChannelManager>,
Arc<dyn Filter + Sync + Send>,
Arc<Persister>,
DefaultTimeProvider,
Arc<test_utils::TestBroadcaster>,
Expand Down Expand Up @@ -2511,8 +2508,6 @@ mod tests {
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&manager),
None,
None,
Arc::clone(&kv_store),
Arc::clone(&tx_broadcaster),
None,
Expand Down Expand Up @@ -2889,10 +2884,10 @@ mod tests {
let kv_store = KVStoreSyncWrapper(kv_store_sync);

// Yes, you can unsafe { turn off the borrow checker }
let lm_async: &'static LiquidityManager<_, _, _, _, _, _, _> = unsafe {
let lm_async: &'static LiquidityManager<_, _, _, _, _, _> = unsafe {
&*(nodes[0].liquidity_manager.get_lm_async()
as *const LiquidityManager<_, _, _, _, _, _, _>)
as &'static LiquidityManager<_, _, _, _, _, _, _>
as *const LiquidityManager<_, _, _, _, _, _>)
as &'static LiquidityManager<_, _, _, _, _, _>
};
let sweeper_async: &'static OutputSweeper<_, _, _, _, _, _, _> = unsafe {
&*(nodes[0].sweeper.sweeper_async() as *const OutputSweeper<_, _, _, _, _, _, _>)
Expand Down Expand Up @@ -3408,10 +3403,10 @@ mod tests {
let kv_store = KVStoreSyncWrapper(kv_store_sync);

// Yes, you can unsafe { turn off the borrow checker }
let lm_async: &'static LiquidityManager<_, _, _, _, _, _, _> = unsafe {
let lm_async: &'static LiquidityManager<_, _, _, _, _, _> = unsafe {
&*(nodes[0].liquidity_manager.get_lm_async()
as *const LiquidityManager<_, _, _, _, _, _, _>)
as &'static LiquidityManager<_, _, _, _, _, _, _>
as *const LiquidityManager<_, _, _, _, _, _>)
as &'static LiquidityManager<_, _, _, _, _, _>
};
let sweeper_async: &'static OutputSweeper<_, _, _, _, _, _, _> = unsafe {
&*(nodes[0].sweeper.sweeper_async() as *const OutputSweeper<_, _, _, _, _, _, _>)
Expand Down Expand Up @@ -3635,10 +3630,10 @@ mod tests {
let (exit_sender, exit_receiver) = tokio::sync::watch::channel(());

// Yes, you can unsafe { turn off the borrow checker }
let lm_async: &'static LiquidityManager<_, _, _, _, _, _, _> = unsafe {
let lm_async: &'static LiquidityManager<_, _, _, _, _, _> = unsafe {
&*(nodes[0].liquidity_manager.get_lm_async()
as *const LiquidityManager<_, _, _, _, _, _, _>)
as &'static LiquidityManager<_, _, _, _, _, _, _>
as *const LiquidityManager<_, _, _, _, _, _>)
as &'static LiquidityManager<_, _, _, _, _, _>
};
let sweeper_async: &'static OutputSweeper<_, _, _, _, _, _, _> = unsafe {
&*(nodes[0].sweeper.sweeper_async() as *const OutputSweeper<_, _, _, _, _, _, _>)
Expand Down
1 change: 0 additions & 1 deletion lightning-liquidity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ parking_lot = { version = "0.12", default-features = false }
level = "forbid"
# When adding a new cfg attribute, ensure that it is added to this list.
check-cfg = [
"cfg(lsps1_service)",
"cfg(c_bindings)",
"cfg(backtrace)",
"cfg(ldk_bench)",
Expand Down
2 changes: 0 additions & 2 deletions lightning-liquidity/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ pub enum LiquidityEvent {
/// An LSPS1 (Channel Request) client event.
LSPS1Client(lsps1::event::LSPS1ClientEvent),
/// An LSPS1 (Channel Request) server event.
#[cfg(lsps1_service)]
LSPS1Service(lsps1::event::LSPS1ServiceEvent),
/// An LSPS2 (JIT Channel) client event.
LSPS2Client(lsps2::event::LSPS2ClientEvent),
Expand All @@ -57,7 +56,6 @@ impl From<lsps1::event::LSPS1ClientEvent> for LiquidityEvent {
}
}

#[cfg(lsps1_service)]
impl From<lsps1::event::LSPS1ServiceEvent> for LiquidityEvent {
fn from(event: lsps1::event::LSPS1ServiceEvent) -> Self {
Self::LSPS1Service(event)
Expand Down
21 changes: 0 additions & 21 deletions lightning-liquidity/src/lsps1/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ pub enum LSPS1ClientEvent {
}

/// An event which an LSPS1 server should take some action in response to.
#[cfg(lsps1_service)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LSPS1ServiceEvent {
/// A client has selected the parameters to use from the supported options of the LSP
Expand All @@ -165,26 +164,6 @@ pub enum LSPS1ServiceEvent {
/// The order requested by the client.
order: LSPS1OrderParams,
},
/// A request from client to check the status of the payment.
///
/// An event to poll for checking payment status either onchain or lightning.
///
/// You must call [`LSPS1ServiceHandler::update_order_status`] to update the client
/// regarding the status of the payment and order.
///
/// **Note: ** This event will *not* be persisted across restarts.
///
/// [`LSPS1ServiceHandler::update_order_status`]: crate::lsps1::service::LSPS1ServiceHandler::update_order_status
CheckPaymentConfirmation {
/// An identifier that must be passed to [`LSPS1ServiceHandler::update_order_status`].
///
/// [`LSPS1ServiceHandler::update_order_status`]: crate::lsps1::service::LSPS1ServiceHandler::update_order_status
request_id: LSPSRequestId,
/// The node id of the client making the information request.
counterparty_node_id: PublicKey,
/// The order id of order with pending payment.
order_id: LSPS1OrderId,
},
/// If error is encountered, refund the amount if paid by the client.
///
/// **Note: ** This event will *not* be persisted across restarts.
Expand Down
2 changes: 1 addition & 1 deletion lightning-liquidity/src/lsps1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
pub mod client;
pub mod event;
pub mod msgs;
#[cfg(lsps1_service)]
pub(crate) mod peer_state;
pub mod service;
102 changes: 88 additions & 14 deletions lightning-liquidity/src/lsps1/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ use crate::lsps0::ser::{
};

use bitcoin::{Address, FeeRate, OutPoint};

use lightning::offers::offer::Offer;
use lightning::util::ser::{Readable, Writeable};
use lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum};
use lightning_invoice::Bolt11Invoice;

use serde::{Deserialize, Serialize};
Expand All @@ -30,13 +31,31 @@ pub(crate) const LSPS1_CREATE_ORDER_METHOD_NAME: &str = "lsps1.create_order";
pub(crate) const LSPS1_GET_ORDER_METHOD_NAME: &str = "lsps1.get_order";

pub(crate) const _LSPS1_CREATE_ORDER_REQUEST_INVALID_PARAMS_ERROR_CODE: i32 = -32602;
#[cfg(lsps1_service)]
pub(crate) const LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE: i32 = 100;
pub(crate) const LSPS1_GET_ORDER_REQUEST_ORDER_NOT_FOUND_ERROR_CODE: i32 = 101;
pub(crate) const LSPS1_CREATE_ORDER_REQUEST_UNRECOGNIZED_OR_STALE_TOKEN_ERROR_CODE: i32 = 102;

/// The identifier of an order.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash)]
pub struct LSPS1OrderId(pub String);

impl Writeable for LSPS1OrderId {
fn write<W: lightning::util::ser::Writer>(
&self, writer: &mut W,
) -> Result<(), lightning::io::Error> {
self.0.write(writer)
}
}

impl Readable for LSPS1OrderId {
fn read<R: bitcoin::io::Read>(
reader: &mut R,
) -> Result<Self, lightning::ln::msgs::DecodeError> {
let inner = Readable::read(reader)?;
Ok(Self(inner))
}
}

/// A request made to an LSP to retrieve the supported options.
///
/// Please refer to the [bLIP-51 / LSPS1
Expand Down Expand Up @@ -126,6 +145,16 @@ pub struct LSPS1OrderParams {
pub announce_channel: bool,
}

impl_writeable_tlv_based!(LSPS1OrderParams, {
(0, lsp_balance_sat, required),
(2, client_balance_sat, required),
(4, required_channel_confirmations, required),
(6, funding_confirms_within_blocks, required),
(8, channel_expiry_blocks, required),
(10, token, option),
(12, announce_channel, required),
});

/// A response to a [`LSPS1CreateOrderRequest`].
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct LSPS1CreateOrderResponse {
Expand Down Expand Up @@ -156,6 +185,12 @@ pub enum LSPS1OrderState {
Failed,
}

impl_writeable_tlv_based_enum!(LSPS1OrderState,
(0, Created) => {},
(2, Completed) => {},
(4, Failed) => {}
);

/// Details regarding how to pay for an order.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct LSPS1PaymentInfo {
Expand All @@ -167,6 +202,12 @@ pub struct LSPS1PaymentInfo {
pub onchain: Option<LSPS1OnchainPaymentInfo>,
}

impl_writeable_tlv_based!(LSPS1PaymentInfo, {
(0, bolt11, option),
(2, bolt12, option),
(4, onchain, option),
});

/// A Lightning payment using BOLT 11.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct LSPS1Bolt11PaymentInfo {
Expand All @@ -184,6 +225,14 @@ pub struct LSPS1Bolt11PaymentInfo {
pub invoice: Bolt11Invoice,
}

impl_writeable_tlv_based!(LSPS1Bolt11PaymentInfo, {
(0, state, required),
(2, expires_at, required),
(4, fee_total_sat, required),
(6, order_total_sat, required),
(8, invoice, required),
});

/// A Lightning payment using BOLT 12.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct LSPS1Bolt12PaymentInfo {
Expand All @@ -202,6 +251,14 @@ pub struct LSPS1Bolt12PaymentInfo {
pub offer: Offer,
}

impl_writeable_tlv_based!(LSPS1Bolt12PaymentInfo, {
(0, state, required),
(2, expires_at, required),
(4, fee_total_sat, required),
(6, order_total_sat, required),
(8, offer, required),
});

/// An onchain payment.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct LSPS1OnchainPaymentInfo {
Expand Down Expand Up @@ -233,6 +290,17 @@ pub struct LSPS1OnchainPaymentInfo {
pub refund_onchain_address: Option<Address>,
}

impl_writeable_tlv_based!(LSPS1OnchainPaymentInfo, {
(0, state, required),
(2, expires_at, required),
(4, fee_total_sat, required),
(6, order_total_sat, required),
(8, address, required),
(10, min_onchain_payment_confirmations, option),
(12, min_fee_for_0conf, required),
(14, refund_onchain_address, option),
});

/// The state of a payment.
///
/// *Note*: Previously, the spec also knew a `CANCELLED` state for BOLT11 payments, which has since
Expand All @@ -242,24 +310,24 @@ pub struct LSPS1OnchainPaymentInfo {
pub enum LSPS1PaymentState {
/// A payment is expected.
ExpectPayment,
/// A sufficient payment has been received.
/// A payment has been received but the channel has not yet been opened.
///
/// This indicates the LSP has received the payment (e.g., Lightning HTLC held,
/// or on-chain transaction detected) but has not yet published the funding transaction.
Hold,
/// A sufficient payment has been received and the channel has been opened.
Paid,
/// The payment has been refunded.
#[serde(alias = "CANCELLED")]
Refunded,
}

/// Details regarding a detected on-chain payment.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct LSPS1OnchainPayment {
/// The outpoint of the payment.
pub outpoint: String,
/// The amount of satoshi paid.
#[serde(with = "string_amount")]
pub sat: u64,
/// Indicates if the LSP regards the transaction as sufficiently confirmed.
pub confirmed: bool,
}
impl_writeable_tlv_based_enum!(LSPS1PaymentState,
(0, ExpectPayment) => {},
(2, Hold) => {},
(4, Paid) => {},
(6, Refunded) => {}
);

/// Details regarding the state of an ordered channel.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
Expand All @@ -272,6 +340,12 @@ pub struct LSPS1ChannelInfo {
pub expires_at: LSPSDateTime,
}

impl_writeable_tlv_based!(LSPS1ChannelInfo, {
(0, funded_at, required),
(2, funding_outpoint, required),
(4, expires_at, required),
});

/// A request made to an LSP to retrieve information about an previously made order.
///
/// Please refer to the [bLIP-51 / LSPS1
Expand Down
Loading
Loading