Skip to content

Commit 1b93d7b

Browse files
committed
Add LSPS1 server side support
1 parent 19106b3 commit 1b93d7b

6 files changed

Lines changed: 708 additions & 24 deletions

File tree

src/builder.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ use crate::io::{
7171
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
7272
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
7373
};
74+
use crate::liquidity::service::lsps1::LSPS1Service;
7475
use crate::liquidity::{LSPS2ServiceConfig, LiquiditySourceBuilder, LspConfig};
7576
use crate::lnurl_auth::LnurlAuth;
7677
use crate::logger::{log_error, LdkLogger, LogLevel, LogWriter, Logger};
@@ -131,6 +132,8 @@ struct PathfindingScoresSyncConfig {
131132
struct LiquiditySourceConfig {
132133
// Acts for both LSPS1 and LSPS2 clients connecting to the given service.
133134
lsp_nodes: Vec<LspConfig>,
135+
// Act as an LSPS1 service.
136+
lsps1_service: Option<LSPS1Service>,
134137
// Act as an LSPS2 service.
135138
lsps2_service: Option<LSPS2ServiceConfig>,
136139
}
@@ -509,18 +512,22 @@ impl NodeBuilder {
509512
self
510513
}
511514

512-
/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
513-
/// channels to clients.
515+
/// Configures the [`Node`] instance to provide an [LSPS1] and/or [LSPS2] service to clients.
516+
///
517+
/// Allowing normal channel purchases and/or just-in-time channels respectively.
514518
///
515519
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
516520
///
521+
/// [LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
517522
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
518523
pub fn enable_liquidity_provider(
519-
&mut self, lsps2_service_config: LSPS2ServiceConfig,
524+
&mut self, lsps1_service_config: Option<LSPS1Service>,
525+
lsps2_service_config: Option<LSPS2ServiceConfig>,
520526
) -> &mut Self {
521527
let liquidity_source_config =
522528
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
523-
liquidity_source_config.lsps2_service = Some(lsps2_service_config);
529+
liquidity_source_config.lsps1_service = lsps1_service_config;
530+
liquidity_source_config.lsps2_service = lsps2_service_config;
524531
self
525532
}
526533

@@ -1114,14 +1121,22 @@ impl ArcedNodeBuilder {
11141121
);
11151122
}
11161123

1117-
/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
1118-
/// channels to clients.
1124+
/// Configures the [`Node`] instance to provide an [LSPS1] and/or [LSPS2] service to clients.
1125+
///
1126+
/// Allowing normal channel purchases and/or just-in-time channels respectively.
11191127
///
11201128
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
11211129
///
1130+
/// [LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
11221131
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
1123-
pub fn enable_liquidity_provider(&self, lsps2_service_config: LSPS2ServiceConfig) {
1124-
self.inner.write().expect("lock").enable_liquidity_provider(lsps2_service_config);
1132+
pub fn enable_liquidity_provider(
1133+
&self, lsps1_service_config: Option<LSPS1Service>,
1134+
lsps2_service_config: Option<LSPS2ServiceConfig>,
1135+
) {
1136+
self.inner
1137+
.write()
1138+
.expect("lock")
1139+
.enable_liquidity_provider(lsps1_service_config, lsps2_service_config);
11251140
}
11261141

11271142
/// Sets the used storage directory path.
@@ -2177,6 +2192,10 @@ fn build_with_store_internal(
21772192
lsc.lsps2_service.as_ref().map(|config| {
21782193
liquidity_source_builder.lsps2_service(promise_secret, config.clone())
21792194
});
2195+
2196+
lsc.lsps1_service
2197+
.as_ref()
2198+
.map(|config| liquidity_source_builder.lsps1_service(*config));
21802199
}
21812200

21822201
let liquidity_source = runtime
@@ -2236,6 +2255,8 @@ fn build_with_store_internal(
22362255

22372256
liquidity_source.lsps2_service().set_peer_manager(Arc::downgrade(&peer_manager));
22382257

2258+
liquidity_source.lsps1_service().set_peer_manager(Arc::downgrade(&peer_manager));
2259+
22392260
let connection_manager = Arc::new(ConnectionManager::new(
22402261
Arc::clone(&peer_manager),
22412262
config.tor_config.clone(),

src/event.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use crate::io::{
4444
EVENT_QUEUE_PERSISTENCE_KEY, EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE,
4545
EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE,
4646
};
47+
use crate::liquidity::service::lsps1::{PendingLSPS1Channel, PendingLSPS1Order};
4748
use crate::liquidity::LiquiditySource;
4849
use crate::logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger};
4950
use crate::payment::asynchronous::om_mailbox::OnionMessageMailbox;
@@ -834,6 +835,124 @@ where
834835
counterparty_skimmed_fee_msat,
835836
..
836837
} => {
838+
// We intercept early and check if the payment was an LSPS1
839+
// order payment and handle properly.
840+
if let Ok(bytes) = self.event_queue.kv_store.read(
841+
"lsps1_pending_orders",
842+
"",
843+
&payment_hash.0.to_string(),
844+
) {
845+
if let Ok(pending_order) = PendingLSPS1Order::read(&mut &bytes[..]) {
846+
let (payment_preimage, payment_method) = match purpose {
847+
PaymentPurpose::Bolt11InvoicePayment { payment_preimage, .. } => (
848+
payment_preimage,
849+
lightning_liquidity::lsps1::service::PaymentMethod::Bolt11,
850+
),
851+
PaymentPurpose::Bolt12OfferPayment { payment_preimage, .. } => (
852+
payment_preimage,
853+
lightning_liquidity::lsps1::service::PaymentMethod::Bolt12,
854+
),
855+
_ => (None, lightning_liquidity::lsps1::service::PaymentMethod::Bolt11),
856+
};
857+
858+
if let Some(preimage) = payment_preimage {
859+
let expected_msat =
860+
pending_order.order_total_amount_sat.saturating_mul(1000);
861+
862+
if amount_msat < expected_msat {
863+
log_error!(
864+
self.logger,
865+
"Refused LSPS1 payment: underpaid. Expected {} msat, received {} msat.",
866+
expected_msat,
867+
amount_msat
868+
);
869+
self.channel_manager.fail_htlc_backwards(&payment_hash);
870+
return Ok(());
871+
}
872+
873+
self.runtime.block_on(async {
874+
self.liquidity_source
875+
.lsps1_service()
876+
.handle_order_payment_received(
877+
pending_order.counterparty_node_id,
878+
pending_order.request_id.into(),
879+
payment_method,
880+
)
881+
.await
882+
});
883+
884+
self.channel_manager.claim_funds(preimage);
885+
886+
let mut config = self.channel_manager.get_current_config();
887+
888+
// We set the forwarding fee to 0 for now as we're getting paid by the channel fee.
889+
config.channel_config.forwarding_fee_base_msat = 0;
890+
891+
let channel_size_sat = pending_order.order_params.lsp_balance_sat
892+
+ pending_order.order_params.client_balance_sat;
893+
894+
let push_msat =
895+
pending_order.order_params.client_balance_sat.saturating_mul(1000);
896+
897+
let user_channel_id: u128 = u128::from_ne_bytes(
898+
self.keys_manager.get_secure_random_bytes()[..16]
899+
.try_into()
900+
.expect("slice is exactly 16 bytes"),
901+
);
902+
903+
let pending_channel = PendingLSPS1Channel {
904+
order_id: pending_order.request_id.into().clone(),
905+
channel_expiry_blocks: pending_order
906+
.order_params
907+
.channel_expiry_blocks,
908+
};
909+
910+
let _ = self.event_queue.kv_store.write(
911+
"lsps1_pending_channels",
912+
"",
913+
&user_channel_id.to_string(),
914+
pending_channel.encode(),
915+
);
916+
917+
if let Err(e) = self.channel_manager.create_channel(
918+
pending_order.counterparty_node_id,
919+
channel_size_sat,
920+
push_msat,
921+
user_channel_id,
922+
None,
923+
Some(config),
924+
) {
925+
log_error!(
926+
self.logger,
927+
"Failed to open LSPS1 channel after claiming funds: {:?}",
928+
e
929+
);
930+
self.liquidity_source
931+
.lsps1_service()
932+
.handle_order_failed_and_refunded(
933+
pending_order.counterparty_node_id,
934+
pending_order.request_id.into(),
935+
)
936+
.await
937+
}
938+
939+
let _ = self.event_queue.kv_store.remove(
940+
"lsps1_pending_orders",
941+
"",
942+
&payment_hash.0.to_string(),
943+
false,
944+
);
945+
} else {
946+
log_error!(
947+
self.logger,
948+
"Failed to claim LSPS1 payment: preimage unknown or unsupported payment purpose."
949+
);
950+
self.channel_manager.fail_htlc_backwards(&payment_hash);
951+
}
952+
return Ok(());
953+
}
954+
}
955+
837956
let (payment_id, mut payment_info) =
838957
self.resolve_inbound_payment_id(payment_id, &payment_hash).await?;
839958
if let Some(info) = payment_info.as_ref() {
@@ -1779,6 +1898,58 @@ where
17791898
counterparty_node_id,
17801899
);
17811900

1901+
// We check if this event was triggered by an LSPS1 order and handle it properly
1902+
if let Ok(bytes) = self.event_queue.kv_store.read(
1903+
"lsps1_pending_channels",
1904+
"",
1905+
&user_channel_id.to_string(),
1906+
) {
1907+
if let Ok(pending_channel) = PendingLSPS1Channel::read(&mut &bytes[..]) {
1908+
let now_secs = std::time::SystemTime::now()
1909+
.duration_since(std::time::UNIX_EPOCH)
1910+
.unwrap_or_default()
1911+
.as_secs();
1912+
1913+
let funded_at =
1914+
lightning_liquidity::lsps0::ser::LSPSDateTime::from_unix_timestamp(
1915+
now_secs,
1916+
)
1917+
.expect("Valid timestamp");
1918+
1919+
let expiry_secs =
1920+
now_secs + (pending_channel.channel_expiry_blocks as u64 * 600);
1921+
let expires_at =
1922+
lightning_liquidity::lsps0::ser::LSPSDateTime::from_unix_timestamp(
1923+
expiry_secs,
1924+
)
1925+
.expect("Valid timestamp");
1926+
1927+
let channel_info = LSPS1ChannelInfo {
1928+
funded_at,
1929+
funding_outpoint: funding_txo.into_bitcoin_outpoint(),
1930+
expires_at,
1931+
};
1932+
1933+
self.runtime.block_on(async {
1934+
self.liquidity_source
1935+
.lsps1_service()
1936+
.handle_order_channel_opened(
1937+
counterparty_node_id,
1938+
pending_channel.order_id,
1939+
channel_info,
1940+
)
1941+
.await
1942+
});
1943+
1944+
let _ = self.event_queue.kv_store.remove(
1945+
"lsps1_pending_channels",
1946+
"",
1947+
&user_channel_id.to_string(),
1948+
false,
1949+
);
1950+
}
1951+
}
1952+
17821953
let former_temporary_channel_id = former_temporary_channel_id.expect(
17831954
"LDK Node has only ever persisted ChannelPending events from rust-lightning 0.0.115 or later",
17841955
);

0 commit comments

Comments
 (0)