Skip to content

Commit 0a20f85

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

6 files changed

Lines changed: 711 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.clone()));
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: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use lightning::util::errors::APIError;
3131
use lightning::util::persist::KVStore;
3232
use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer};
3333
use lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum};
34+
use lightning_liquidity::lsps1::msgs::{LSPS1ChannelInfo, LSPS1OrderId};
3435
use lightning_liquidity::lsps2::utils::compute_opening_fee;
3536
use lightning_types::payment::{PaymentHash, PaymentPreimage};
3637

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

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

0 commit comments

Comments
 (0)