Skip to content

Commit 285d09e

Browse files
committed
Add LSPS2-backed BOLT12 JIT offer routing
Wrap `DefaultRouter` in `LSPS2BOLT12Router`, register LSPS2 buy results against `OfferId`, and expose `Bolt12Payment` JIT receive APIs that create offers and route inbound blinded paths through LSPS2 intercept parameters. Co-Authored-By: HAL 9000
1 parent 73d795b commit 285d09e

5 files changed

Lines changed: 342 additions & 72 deletions

File tree

src/builder.rs

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use lightning::util::persist::{
3939
};
4040
use lightning::util::ser::ReadableArgs;
4141
use lightning::util::sweep::OutputSweeper;
42+
use lightning_liquidity::lsps2::router::LSPS2BOLT12Router;
4243
use lightning_persister::fs_store::v1::FilesystemStore;
4344
use vss_client::headers::VssHeaderProvider;
4445

@@ -1627,13 +1628,14 @@ fn build_with_store_internal(
16271628
}
16281629

16291630
let scoring_fee_params = ProbabilisticScoringFeeParameters::default();
1630-
let router = Arc::new(DefaultRouter::new(
1631+
let inner_router = DefaultRouter::new(
16311632
Arc::clone(&network_graph),
16321633
Arc::clone(&logger),
16331634
Arc::clone(&keys_manager),
16341635
Arc::clone(&scorer),
16351636
scoring_fee_params,
1636-
));
1637+
);
1638+
let router = Arc::new(LSPS2BOLT12Router::new(inner_router, Arc::clone(&keys_manager)));
16371639

16381640
let mut user_config = default_user_config(&config);
16391641

@@ -1790,56 +1792,62 @@ fn build_with_store_internal(
17901792
},
17911793
};
17921794

1793-
let (liquidity_source, custom_message_handler) =
1794-
if let Some(lsc) = liquidity_source_config.as_ref() {
1795-
let mut liquidity_source_builder = LiquiditySourceBuilder::new(
1796-
Arc::clone(&wallet),
1797-
Arc::clone(&channel_manager),
1798-
Arc::clone(&keys_manager),
1799-
Arc::clone(&chain_source),
1800-
Arc::clone(&tx_broadcaster),
1801-
Arc::clone(&kv_store),
1802-
Arc::clone(&config),
1803-
Arc::clone(&logger),
1804-
);
1795+
let (liquidity_source, custom_message_handler) = if let Some(lsc) =
1796+
liquidity_source_config.as_ref()
1797+
{
1798+
let mut liquidity_source_builder = LiquiditySourceBuilder::new(
1799+
Arc::clone(&wallet),
1800+
Arc::clone(&channel_manager),
1801+
Arc::clone(&keys_manager),
1802+
Arc::clone(&router),
1803+
Arc::clone(&chain_source),
1804+
Arc::clone(&tx_broadcaster),
1805+
Arc::clone(&kv_store),
1806+
Arc::clone(&config),
1807+
Some(Arc::clone(&onion_messenger)
1808+
as Arc<
1809+
dyn lightning::onion_message::messenger::OnionMessageInterceptor + Send + Sync,
1810+
>),
1811+
Arc::clone(&logger),
1812+
);
18051813

1806-
lsc.lsps1_client.as_ref().map(|config| {
1807-
liquidity_source_builder.lsps1_client(
1808-
config.node_id,
1809-
config.address.clone(),
1810-
config.token.clone(),
1811-
)
1812-
});
1814+
lsc.lsps1_client.as_ref().map(|config| {
1815+
liquidity_source_builder.lsps1_client(
1816+
config.node_id,
1817+
config.address.clone(),
1818+
config.token.clone(),
1819+
)
1820+
});
18131821

1814-
lsc.lsps2_client.as_ref().map(|config| {
1815-
liquidity_source_builder.lsps2_client(
1816-
config.node_id,
1817-
config.address.clone(),
1818-
config.token.clone(),
1819-
)
1820-
});
1822+
lsc.lsps2_client.as_ref().map(|config| {
1823+
liquidity_source_builder.lsps2_client(
1824+
config.node_id,
1825+
config.address.clone(),
1826+
config.token.clone(),
1827+
)
1828+
});
18211829

1822-
let promise_secret = {
1823-
let lsps_xpriv = derive_xprv(
1824-
Arc::clone(&config),
1825-
&seed_bytes,
1826-
LSPS_HARDENED_CHILD_INDEX,
1827-
Arc::clone(&logger),
1828-
)?;
1829-
lsps_xpriv.private_key.secret_bytes()
1830-
};
1831-
lsc.lsps2_service.as_ref().map(|config| {
1832-
liquidity_source_builder.lsps2_service(promise_secret, config.clone())
1833-
});
1834-
1835-
let liquidity_source = runtime
1836-
.block_on(async move { liquidity_source_builder.build().await.map(Arc::new) })?;
1837-
let custom_message_handler =
1838-
Arc::new(NodeCustomMessageHandler::new_liquidity(Arc::clone(&liquidity_source)));
1839-
(Some(liquidity_source), custom_message_handler)
1840-
} else {
1841-
(None, Arc::new(NodeCustomMessageHandler::new_ignoring()))
1830+
let promise_secret = {
1831+
let lsps_xpriv = derive_xprv(
1832+
Arc::clone(&config),
1833+
&seed_bytes,
1834+
LSPS_HARDENED_CHILD_INDEX,
1835+
Arc::clone(&logger),
1836+
)?;
1837+
lsps_xpriv.private_key.secret_bytes()
18421838
};
1839+
lsc.lsps2_service
1840+
.as_ref()
1841+
.map(|config| liquidity_source_builder.lsps2_service(promise_secret, config.clone()));
1842+
1843+
let liquidity_source = runtime
1844+
.block_on(async move { liquidity_source_builder.build().await.map(Arc::new) })?;
1845+
let custom_message_handler =
1846+
Arc::new(NodeCustomMessageHandler::new_liquidity(Arc::clone(&liquidity_source)));
1847+
(Some(liquidity_source), custom_message_handler)
1848+
} else {
1849+
(None, Arc::new(NodeCustomMessageHandler::new_ignoring()))
1850+
};
18431851

18441852
let msg_handler = match gossip_source.as_gossip_sync() {
18451853
GossipSync::P2P(p2p_gossip_sync) => MessageHandler {

src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,9 +901,13 @@ impl Node {
901901
#[cfg(not(feature = "uniffi"))]
902902
pub fn bolt12_payment(&self) -> Bolt12Payment {
903903
Bolt12Payment::new(
904+
Arc::clone(&self.runtime),
904905
Arc::clone(&self.channel_manager),
906+
Arc::clone(&self.connection_manager),
907+
self.liquidity_source.clone(),
905908
Arc::clone(&self.keys_manager),
906909
Arc::clone(&self.payment_store),
910+
Arc::clone(&self.peer_store),
907911
Arc::clone(&self.config),
908912
Arc::clone(&self.is_running),
909913
Arc::clone(&self.logger),
@@ -917,9 +921,13 @@ impl Node {
917921
#[cfg(feature = "uniffi")]
918922
pub fn bolt12_payment(&self) -> Arc<Bolt12Payment> {
919923
Arc::new(Bolt12Payment::new(
924+
Arc::clone(&self.runtime),
920925
Arc::clone(&self.channel_manager),
926+
Arc::clone(&self.connection_manager),
927+
self.liquidity_source.clone(),
921928
Arc::clone(&self.keys_manager),
922929
Arc::clone(&self.payment_store),
930+
Arc::clone(&self.peer_store),
923931
Arc::clone(&self.config),
924932
Arc::clone(&self.is_running),
925933
Arc::clone(&self.logger),

src/liquidity.rs

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use lightning::events::HTLCHandlingFailureType;
1919
use lightning::ln::channelmanager::{InterceptId, MIN_FINAL_CLTV_EXPIRY_DELTA};
2020
use lightning::ln::msgs::SocketAddress;
2121
use lightning::ln::types::ChannelId;
22+
use lightning::offers::offer::OfferId;
23+
use lightning::onion_message::messenger::OnionMessageInterceptor;
2224
use lightning::routing::router::{RouteHint, RouteHintHop};
2325
use lightning::sign::EntropySource;
2426
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, InvoiceBuilder, RoutingFees};
@@ -32,6 +34,7 @@ use lightning_liquidity::lsps1::msgs::{
3234
use lightning_liquidity::lsps2::client::LSPS2ClientConfig as LdkLSPS2ClientConfig;
3335
use lightning_liquidity::lsps2::event::{LSPS2ClientEvent, LSPS2ServiceEvent};
3436
use lightning_liquidity::lsps2::msgs::{LSPS2OpeningFeeParams, LSPS2RawOpeningFeeParams};
37+
use lightning_liquidity::lsps2::router::LSPS2Bolt12InvoiceParameters;
3538
use lightning_liquidity::lsps2::service::LSPS2ServiceConfig as LdkLSPS2ServiceConfig;
3639
use lightning_liquidity::lsps2::utils::compute_opening_fee;
3740
use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig};
@@ -44,7 +47,8 @@ use crate::connection::ConnectionManager;
4447
use crate::logger::{log_debug, log_error, log_info, LdkLogger, Logger};
4548
use crate::runtime::Runtime;
4649
use crate::types::{
47-
Broadcaster, ChannelManager, DynStore, KeysManager, LiquidityManager, PeerManager, Wallet,
50+
Broadcaster, ChannelManager, DynStore, KeysManager, LiquidityManager, PeerManager, Router,
51+
Wallet,
4852
};
4953
use crate::{total_anchor_channels_reserve_sats, Config, Error};
5054

@@ -154,11 +158,13 @@ where
154158
lsps2_service: Option<LSPS2Service>,
155159
wallet: Arc<Wallet>,
156160
channel_manager: Arc<ChannelManager>,
161+
router: Arc<Router>,
157162
keys_manager: Arc<KeysManager>,
158163
chain_source: Arc<ChainSource>,
159164
tx_broadcaster: Arc<Broadcaster>,
160165
kv_store: Arc<DynStore>,
161166
config: Arc<Config>,
167+
onion_message_interceptor: Option<Arc<dyn OnionMessageInterceptor + Send + Sync>>,
162168
logger: L,
163169
}
164170

@@ -168,8 +174,10 @@ where
168174
{
169175
pub(crate) fn new(
170176
wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>, keys_manager: Arc<KeysManager>,
171-
chain_source: Arc<ChainSource>, tx_broadcaster: Arc<Broadcaster>, kv_store: Arc<DynStore>,
172-
config: Arc<Config>, logger: L,
177+
router: Arc<Router>, chain_source: Arc<ChainSource>, tx_broadcaster: Arc<Broadcaster>,
178+
kv_store: Arc<DynStore>, config: Arc<Config>,
179+
onion_message_interceptor: Option<Arc<dyn OnionMessageInterceptor + Send + Sync>>,
180+
logger: L,
173181
) -> Self {
174182
let lsps1_client = None;
175183
let lsps2_client = None;
@@ -180,11 +188,13 @@ where
180188
lsps2_service,
181189
wallet,
182190
channel_manager,
191+
router,
183192
keys_manager,
184193
chain_source,
185194
tx_broadcaster,
186195
kv_store,
187196
config,
197+
onion_message_interceptor,
188198
logger,
189199
}
190200
}
@@ -262,7 +272,7 @@ where
262272
Arc::clone(&self.tx_broadcaster),
263273
liquidity_service_config,
264274
liquidity_client_config,
265-
None,
275+
self.onion_message_interceptor,
266276
)
267277
.await
268278
.map_err(|_| BuildError::ReadFailed)?,
@@ -274,6 +284,7 @@ where
274284
lsps2_service: self.lsps2_service,
275285
wallet: self.wallet,
276286
channel_manager: self.channel_manager,
287+
router: self.router,
277288
peer_manager: RwLock::new(None),
278289
keys_manager: self.keys_manager,
279290
liquidity_manager,
@@ -292,6 +303,7 @@ where
292303
lsps2_service: Option<LSPS2Service>,
293304
wallet: Arc<Wallet>,
294305
channel_manager: Arc<ChannelManager>,
306+
router: Arc<Router>,
295307
peer_manager: RwLock<Option<Weak<PeerManager>>>,
296308
keys_manager: Arc<KeysManager>,
297309
liquidity_manager: Arc<LiquidityManager>,
@@ -1192,6 +1204,104 @@ where
11921204
Ok((invoice, min_prop_fee_ppm_msat))
11931205
}
11941206

1207+
pub(crate) async fn lsps2_register_bolt12_payment_paths(
1208+
&self, offer_id: OfferId, amount_msat: u64, max_total_lsp_fee_limit_msat: Option<u64>,
1209+
) -> Result<u64, Error> {
1210+
let fee_response = self.lsps2_request_opening_fee_params().await?;
1211+
1212+
let (min_total_fee_msat, min_opening_params) = fee_response
1213+
.opening_fee_params_menu
1214+
.into_iter()
1215+
.filter_map(|params| {
1216+
if amount_msat < params.min_payment_size_msat
1217+
|| amount_msat > params.max_payment_size_msat
1218+
{
1219+
log_debug!(self.logger,
1220+
"Skipping LSP-offered JIT parameters as the payment of {}msat doesn't meet LSP limits (min: {}msat, max: {}msat)",
1221+
amount_msat,
1222+
params.min_payment_size_msat,
1223+
params.max_payment_size_msat
1224+
);
1225+
None
1226+
} else {
1227+
compute_opening_fee(amount_msat, params.min_fee_msat, params.proportional as u64)
1228+
.map(|fee| (fee, params))
1229+
}
1230+
})
1231+
.min_by_key(|p| p.0)
1232+
.ok_or_else(|| {
1233+
log_error!(self.logger, "Failed to handle response from liquidity service",);
1234+
Error::LiquidityRequestFailed
1235+
})?;
1236+
1237+
if let Some(max_total_lsp_fee_limit_msat) = max_total_lsp_fee_limit_msat {
1238+
if min_total_fee_msat > max_total_lsp_fee_limit_msat {
1239+
log_error!(self.logger,
1240+
"Failed to request inbound JIT channel as LSP's requested total opening fee of {}msat exceeds our fee limit of {}msat",
1241+
min_total_fee_msat, max_total_lsp_fee_limit_msat
1242+
);
1243+
return Err(Error::LiquidityFeeTooHigh);
1244+
}
1245+
}
1246+
1247+
let buy_response =
1248+
self.lsps2_send_buy_request(Some(amount_msat), min_opening_params).await?;
1249+
self.register_lsps2_bolt12_payment_paths(offer_id, buy_response)?;
1250+
1251+
Ok(min_total_fee_msat)
1252+
}
1253+
1254+
pub(crate) async fn lsps2_register_variable_amount_bolt12_payment_paths(
1255+
&self, offer_id: OfferId, max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
1256+
) -> Result<u64, Error> {
1257+
let fee_response = self.lsps2_request_opening_fee_params().await?;
1258+
1259+
let (min_prop_fee_ppm_msat, min_opening_params) = fee_response
1260+
.opening_fee_params_menu
1261+
.into_iter()
1262+
.map(|params| (params.proportional as u64, params))
1263+
.min_by_key(|p| p.0)
1264+
.ok_or_else(|| {
1265+
log_error!(self.logger, "Failed to handle response from liquidity service",);
1266+
Error::LiquidityRequestFailed
1267+
})?;
1268+
1269+
if let Some(max_proportional_lsp_fee_limit_ppm_msat) =
1270+
max_proportional_lsp_fee_limit_ppm_msat
1271+
{
1272+
if min_prop_fee_ppm_msat > max_proportional_lsp_fee_limit_ppm_msat {
1273+
log_error!(self.logger,
1274+
"Failed to request inbound JIT channel as LSP's requested proportional opening fee of {} ppm msat exceeds our fee limit of {} ppm msat",
1275+
min_prop_fee_ppm_msat,
1276+
max_proportional_lsp_fee_limit_ppm_msat
1277+
);
1278+
return Err(Error::LiquidityFeeTooHigh);
1279+
}
1280+
}
1281+
1282+
let buy_response = self.lsps2_send_buy_request(None, min_opening_params).await?;
1283+
self.register_lsps2_bolt12_payment_paths(offer_id, buy_response)?;
1284+
1285+
Ok(min_prop_fee_ppm_msat)
1286+
}
1287+
1288+
fn register_lsps2_bolt12_payment_paths(
1289+
&self, offer_id: OfferId, buy_response: LSPS2BuyResponse,
1290+
) -> Result<(), Error> {
1291+
let lsps2_client = self.lsps2_client.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
1292+
1293+
self.router.register_offer(
1294+
offer_id,
1295+
LSPS2Bolt12InvoiceParameters {
1296+
counterparty_node_id: lsps2_client.lsp_node_id,
1297+
intercept_scid: buy_response.intercept_scid,
1298+
cltv_expiry_delta: buy_response.cltv_expiry_delta,
1299+
},
1300+
);
1301+
1302+
Ok(())
1303+
}
1304+
11951305
async fn lsps2_request_opening_fee_params(&self) -> Result<LSPS2FeeResponse, Error> {
11961306
let lsps2_client = self.lsps2_client.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
11971307

@@ -1304,7 +1414,14 @@ where
13041414
src_node_id: lsps2_client.lsp_node_id,
13051415
short_channel_id: buy_response.intercept_scid,
13061416
fees: RoutingFees { base_msat: 0, proportional_millionths: 0 },
1307-
cltv_expiry_delta: buy_response.cltv_expiry_delta as u16,
1417+
cltv_expiry_delta: u16::try_from(buy_response.cltv_expiry_delta).map_err(|_| {
1418+
log_error!(
1419+
self.logger,
1420+
"Failed to create JIT invoice as LSPS2 CLTV delta {} exceeds supported range",
1421+
buy_response.cltv_expiry_delta
1422+
);
1423+
Error::LiquidityRequestFailed
1424+
})?,
13081425
htlc_minimum_msat: None,
13091426
htlc_maximum_msat: None,
13101427
}]);

0 commit comments

Comments
 (0)