Skip to content

Commit aeb92a2

Browse files
committed
Add LSPS1 server side support
1 parent 1cc60c8 commit aeb92a2

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
@@ -69,6 +69,7 @@ use crate::io::{
6969
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
7070
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
7171
};
72+
use crate::liquidity::service::lsps1::LSPS1Service;
7273
use crate::liquidity::{LSPS2ServiceConfig, LiquiditySourceBuilder, LspConfig};
7374
use crate::lnurl_auth::LnurlAuth;
7475
use crate::logger::{log_error, LdkLogger, LogLevel, LogWriter, Logger};
@@ -129,6 +130,8 @@ struct PathfindingScoresSyncConfig {
129130
struct LiquiditySourceConfig {
130131
// Acts for both LSPS1 and LSPS2 clients connecting to the given service.
131132
lsp_nodes: Vec<LspConfig>,
133+
// Act as an LSPS1 service.
134+
lsps1_service: Option<LSPS1Service>,
132135
// Act as an LSPS2 service.
133136
lsps2_service: Option<LSPS2ServiceConfig>,
134137
}
@@ -507,18 +510,22 @@ impl NodeBuilder {
507510
self
508511
}
509512

510-
/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
511-
/// channels to clients.
513+
/// Configures the [`Node`] instance to provide an [LSPS1] and/or [LSPS2] service to clients.
514+
///
515+
/// Allowing normal channel purchases and/or just-in-time channels respectively.
512516
///
513517
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
514518
///
519+
/// [LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
515520
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
516521
pub fn enable_liquidity_provider(
517-
&mut self, lsps2_service_config: LSPS2ServiceConfig,
522+
&mut self, lsps1_service_config: Option<LSPS1Service>,
523+
lsps2_service_config: Option<LSPS2ServiceConfig>,
518524
) -> &mut Self {
519525
let liquidity_source_config =
520526
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
521-
liquidity_source_config.lsps2_service = Some(lsps2_service_config);
527+
liquidity_source_config.lsps1_service = lsps1_service_config;
528+
liquidity_source_config.lsps2_service = lsps2_service_config;
522529
self
523530
}
524531

@@ -1104,14 +1111,22 @@ impl ArcedNodeBuilder {
11041111
);
11051112
}
11061113

1107-
/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
1108-
/// channels to clients.
1114+
/// Configures the [`Node`] instance to provide an [LSPS1] and/or [LSPS2] service to clients.
1115+
///
1116+
/// Allowing normal channel purchases and/or just-in-time channels respectively.
11091117
///
11101118
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
11111119
///
1120+
/// [LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
11121121
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
1113-
pub fn enable_liquidity_provider(&self, lsps2_service_config: LSPS2ServiceConfig) {
1114-
self.inner.write().expect("lock").enable_liquidity_provider(lsps2_service_config);
1122+
pub fn enable_liquidity_provider(
1123+
&self, lsps1_service_config: Option<LSPS1Service>,
1124+
lsps2_service_config: Option<LSPS2ServiceConfig>,
1125+
) {
1126+
self.inner
1127+
.write()
1128+
.expect("lock")
1129+
.enable_liquidity_provider(lsps1_service_config, lsps2_service_config);
11151130
}
11161131

11171132
/// Sets the used storage directory path.
@@ -2124,6 +2139,10 @@ fn build_with_store_internal(
21242139
lsc.lsps2_service.as_ref().map(|config| {
21252140
liquidity_source_builder.lsps2_service(promise_secret, config.clone())
21262141
});
2142+
2143+
lsc.lsps1_service
2144+
.as_ref()
2145+
.map(|config| liquidity_source_builder.lsps1_service(*config));
21272146
}
21282147

21292148
let liquidity_source = runtime
@@ -2183,6 +2202,8 @@ fn build_with_store_internal(
21832202

21842203
liquidity_source.lsps2_service().set_peer_manager(Arc::downgrade(&peer_manager));
21852204

2205+
liquidity_source.lsps1_service().set_peer_manager(Arc::downgrade(&peer_manager));
2206+
21862207
let connection_manager = Arc::new(ConnectionManager::new(
21872208
Arc::clone(&peer_manager),
21882209
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;
@@ -724,6 +725,124 @@ where
724725
counterparty_skimmed_fee_msat,
725726
..
726727
} => {
728+
// We intercept early and check if the payment was an LSPS1
729+
// order payment and handle properly.
730+
if let Ok(bytes) = self.event_queue.kv_store.read(
731+
"lsps1_pending_orders",
732+
"",
733+
&payment_hash.0.to_string(),
734+
) {
735+
if let Ok(pending_order) = PendingLSPS1Order::read(&mut &bytes[..]) {
736+
let (payment_preimage, payment_method) = match purpose {
737+
PaymentPurpose::Bolt11InvoicePayment { payment_preimage, .. } => (
738+
payment_preimage,
739+
lightning_liquidity::lsps1::service::PaymentMethod::Bolt11,
740+
),
741+
PaymentPurpose::Bolt12OfferPayment { payment_preimage, .. } => (
742+
payment_preimage,
743+
lightning_liquidity::lsps1::service::PaymentMethod::Bolt12,
744+
),
745+
_ => (None, lightning_liquidity::lsps1::service::PaymentMethod::Bolt11),
746+
};
747+
748+
if let Some(preimage) = payment_preimage {
749+
let expected_msat =
750+
pending_order.order_total_amount_sat.saturating_mul(1000);
751+
752+
if amount_msat < expected_msat {
753+
log_error!(
754+
self.logger,
755+
"Refused LSPS1 payment: underpaid. Expected {} msat, received {} msat.",
756+
expected_msat,
757+
amount_msat
758+
);
759+
self.channel_manager.fail_htlc_backwards(&payment_hash);
760+
return Ok(());
761+
}
762+
763+
self.runtime.block_on(async {
764+
self.liquidity_source
765+
.lsps1_service()
766+
.handle_order_payment_received(
767+
pending_order.counterparty_node_id,
768+
pending_order.request_id.into(),
769+
payment_method,
770+
)
771+
.await
772+
});
773+
774+
self.channel_manager.claim_funds(preimage);
775+
776+
let mut config = self.channel_manager.get_current_config();
777+
778+
// We set the forwarding fee to 0 for now as we're getting paid by the channel fee.
779+
config.channel_config.forwarding_fee_base_msat = 0;
780+
781+
let channel_size_sat = pending_order.order_params.lsp_balance_sat
782+
+ pending_order.order_params.client_balance_sat;
783+
784+
let push_msat =
785+
pending_order.order_params.client_balance_sat.saturating_mul(1000);
786+
787+
let user_channel_id: u128 = u128::from_ne_bytes(
788+
self.keys_manager.get_secure_random_bytes()[..16]
789+
.try_into()
790+
.expect("slice is exactly 16 bytes"),
791+
);
792+
793+
let pending_channel = PendingLSPS1Channel {
794+
order_id: pending_order.request_id.into().clone(),
795+
channel_expiry_blocks: pending_order
796+
.order_params
797+
.channel_expiry_blocks,
798+
};
799+
800+
let _ = self.event_queue.kv_store.write(
801+
"lsps1_pending_channels",
802+
"",
803+
&user_channel_id.to_string(),
804+
pending_channel.encode(),
805+
);
806+
807+
if let Err(e) = self.channel_manager.create_channel(
808+
pending_order.counterparty_node_id,
809+
channel_size_sat,
810+
push_msat,
811+
user_channel_id,
812+
None,
813+
Some(config),
814+
) {
815+
log_error!(
816+
self.logger,
817+
"Failed to open LSPS1 channel after claiming funds: {:?}",
818+
e
819+
);
820+
self.liquidity_source
821+
.lsps1_service()
822+
.handle_order_failed_and_refunded(
823+
pending_order.counterparty_node_id,
824+
pending_order.request_id.into(),
825+
)
826+
.await
827+
}
828+
829+
let _ = self.event_queue.kv_store.remove(
830+
"lsps1_pending_orders",
831+
"",
832+
&payment_hash.0.to_string(),
833+
false,
834+
);
835+
} else {
836+
log_error!(
837+
self.logger,
838+
"Failed to claim LSPS1 payment: preimage unknown or unsupported payment purpose."
839+
);
840+
self.channel_manager.fail_htlc_backwards(&payment_hash);
841+
}
842+
return Ok(());
843+
}
844+
}
845+
727846
let payment_id = PaymentId(payment_hash.0);
728847
let payment_info = self.payment_store.get(&payment_id);
729848
if let Some(info) = payment_info.as_ref() {
@@ -1518,6 +1637,58 @@ where
15181637
counterparty_node_id,
15191638
);
15201639

1640+
// We check if this event was triggered by an LSPS1 order and handle it properly
1641+
if let Ok(bytes) = self.event_queue.kv_store.read(
1642+
"lsps1_pending_channels",
1643+
"",
1644+
&user_channel_id.to_string(),
1645+
) {
1646+
if let Ok(pending_channel) = PendingLSPS1Channel::read(&mut &bytes[..]) {
1647+
let now_secs = std::time::SystemTime::now()
1648+
.duration_since(std::time::UNIX_EPOCH)
1649+
.unwrap_or_default()
1650+
.as_secs();
1651+
1652+
let funded_at =
1653+
lightning_liquidity::lsps0::ser::LSPSDateTime::from_unix_timestamp(
1654+
now_secs,
1655+
)
1656+
.expect("Valid timestamp");
1657+
1658+
let expiry_secs =
1659+
now_secs + (pending_channel.channel_expiry_blocks as u64 * 600);
1660+
let expires_at =
1661+
lightning_liquidity::lsps0::ser::LSPSDateTime::from_unix_timestamp(
1662+
expiry_secs,
1663+
)
1664+
.expect("Valid timestamp");
1665+
1666+
let channel_info = LSPS1ChannelInfo {
1667+
funded_at,
1668+
funding_outpoint: funding_txo.into_bitcoin_outpoint(),
1669+
expires_at,
1670+
};
1671+
1672+
self.runtime.block_on(async {
1673+
self.liquidity_source
1674+
.lsps1_service()
1675+
.handle_order_channel_opened(
1676+
counterparty_node_id,
1677+
pending_channel.order_id,
1678+
channel_info,
1679+
)
1680+
.await
1681+
});
1682+
1683+
let _ = self.event_queue.kv_store.remove(
1684+
"lsps1_pending_channels",
1685+
"",
1686+
&user_channel_id.to_string(),
1687+
false,
1688+
);
1689+
}
1690+
}
1691+
15211692
let former_temporary_channel_id = former_temporary_channel_id.expect(
15221693
"LDK Node has only ever persisted ChannelPending events from rust-lightning 0.0.115 or later",
15231694
);

0 commit comments

Comments
 (0)