Skip to content

Commit 27dc7b4

Browse files
Support client_trusts_lsp=true on ldk-node
implement changes introduced on lightningdevkit/rust-lightning#3838 as discussed, client_trusts_lsp is a flag set at startup. a new function receive_via_jit_channel_manual_claim is introduced to bolt11 so we allow the client to manually claim a payment (used on tests).
1 parent 1a134b4 commit 27dc7b4

File tree

7 files changed

+438
-20
lines changed

7 files changed

+438
-20
lines changed

bindings/ldk_node.udl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ dictionary LSPS2ServiceConfig {
4444
u32 max_client_to_self_delay;
4545
u64 min_payment_size_msat;
4646
u64 max_payment_size_msat;
47+
boolean client_trusts_lsp;
4748
};
4849

4950
enum LogLevel {
@@ -197,6 +198,13 @@ interface Bolt11Payment {
197198
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
198199
[Throws=NodeError]
199200
Bolt11Invoice receive_variable_amount_via_jit_channel_for_hash([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat, PaymentHash payment_hash);
201+
[Throws=NodeError]
202+
JitChannelManualClaim receive_via_jit_channel_manual_claim(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_total_lsp_fee_limit_msat);
203+
};
204+
205+
dictionary JitChannelManualClaim {
206+
Bolt11Invoice invoice;
207+
PaymentPreimage preimage;
200208
};
201209

202210
interface Bolt12Payment {

src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,7 @@ fn build_with_store_internal(
16131613
Arc::clone(&kv_store),
16141614
Arc::clone(&config),
16151615
Arc::clone(&logger),
1616+
Arc::clone(&tx_broadcaster),
16161617
);
16171618

16181619
lsc.lsps1_client.as_ref().map(|config| {

src/event.rs

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ where
487487
counterparty_node_id,
488488
channel_value_satoshis,
489489
output_script,
490-
..
490+
user_channel_id,
491491
} => {
492492
// Construct the raw transaction with the output that is paid the amount of the
493493
// channel.
@@ -506,12 +506,43 @@ where
506506
locktime,
507507
) {
508508
Ok(final_tx) => {
509-
// Give the funding transaction back to LDK for opening the channel.
510-
match self.channel_manager.funding_transaction_generated(
511-
temporary_channel_id,
512-
counterparty_node_id,
513-
final_tx,
514-
) {
509+
let needs_manual_broadcast =
510+
match self.liquidity_source.as_ref().map(|ls| {
511+
ls.as_ref().lsps2_channel_needs_manual_broadcast(
512+
counterparty_node_id,
513+
user_channel_id,
514+
)
515+
}) {
516+
Some(Ok(v)) => v,
517+
Some(Err(e)) => {
518+
log_error!(self.logger, "Failed to determine if channel needs manual broadcast: {:?}", e);
519+
false
520+
},
521+
None => false,
522+
};
523+
524+
let result = if needs_manual_broadcast {
525+
self.liquidity_source.as_ref().map(|ls| {
526+
ls.lsps2_store_funding_transaction(
527+
user_channel_id,
528+
counterparty_node_id,
529+
final_tx.clone(),
530+
);
531+
});
532+
self.channel_manager.funding_transaction_generated_manual_broadcast(
533+
temporary_channel_id,
534+
counterparty_node_id,
535+
final_tx,
536+
)
537+
} else {
538+
self.channel_manager.funding_transaction_generated(
539+
temporary_channel_id,
540+
counterparty_node_id,
541+
final_tx,
542+
)
543+
};
544+
545+
match result {
515546
Ok(()) => {},
516547
Err(APIError::APIMisuseError { err }) => {
517548
log_error!(self.logger, "Panicking due to APIMisuseError: {}", err);
@@ -550,8 +581,10 @@ where
550581
},
551582
}
552583
},
553-
LdkEvent::FundingTxBroadcastSafe { .. } => {
554-
debug_assert!(false, "We currently only support safe funding, so this event should never be emitted.");
584+
LdkEvent::FundingTxBroadcastSafe { user_channel_id, counterparty_node_id, .. } => {
585+
self.liquidity_source.as_ref().map(|ls| {
586+
ls.lsps2_funding_tx_broadcast_safe(user_channel_id, counterparty_node_id);
587+
});
555588
},
556589
LdkEvent::PaymentClaimable {
557590
payment_hash,
@@ -676,7 +709,7 @@ where
676709
match info.kind {
677710
PaymentKind::Bolt11 { preimage, .. }
678711
| PaymentKind::Bolt11Jit { preimage, .. } => {
679-
if purpose.preimage().is_none() {
712+
if preimage.is_none() || purpose.preimage().is_none() {
680713
debug_assert!(
681714
preimage.is_none(),
682715
"We would have registered the preimage if we knew"

src/ffi/types.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,15 @@ impl UniffiCustomTypeConverter for LSPSDateTime {
11641164
}
11651165
}
11661166

1167+
/// A payable invoice and its corresponding preimage for manual claiming via a JIT channel.
1168+
#[derive(Debug, Clone)]
1169+
pub struct JitChannelManualClaim {
1170+
/// The payable invoice.
1171+
pub invoice: Arc<Bolt11Invoice>,
1172+
/// The payment preimage.
1173+
pub preimage: PaymentPreimage,
1174+
}
1175+
11671176
#[cfg(test)]
11681177
mod tests {
11691178
use std::num::NonZeroU64;

src/liquidity.rs

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ use crate::{total_anchor_channels_reserve_sats, Config, Error};
5151
const LIQUIDITY_REQUEST_TIMEOUT_SECS: u64 = 5;
5252

5353
const LSPS2_GETINFO_REQUEST_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);
54-
const LSPS2_CLIENT_TRUSTS_LSP_MODE: bool = true;
5554
const LSPS2_CHANNEL_CLTV_EXPIRY_DELTA: u32 = 72;
5655

5756
struct LSPS1Client {
@@ -130,6 +129,8 @@ pub struct LSPS2ServiceConfig {
130129
pub min_payment_size_msat: u64,
131130
/// The maximum payment size that we will accept when opening a channel.
132131
pub max_payment_size_msat: u64,
132+
/// Use the client trusts lsp model
133+
pub client_trusts_lsp: bool,
133134
}
134135

135136
pub(crate) struct LiquiditySourceBuilder<L: Deref>
@@ -147,6 +148,7 @@ where
147148
kv_store: Arc<DynStore>,
148149
config: Arc<Config>,
149150
logger: L,
151+
broadcaster: Arc<Broadcaster>,
150152
}
151153

152154
impl<L: Deref> LiquiditySourceBuilder<L>
@@ -156,7 +158,7 @@ where
156158
pub(crate) fn new(
157159
wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>, keys_manager: Arc<KeysManager>,
158160
chain_source: Arc<ChainSource>, tx_broadcaster: Arc<Broadcaster>, kv_store: Arc<DynStore>,
159-
config: Arc<Config>, logger: L,
161+
config: Arc<Config>, logger: L, broadcaster: Arc<Broadcaster>,
160162
) -> Self {
161163
let lsps1_client = None;
162164
let lsps2_client = None;
@@ -173,6 +175,7 @@ where
173175
kv_store,
174176
config,
175177
logger,
178+
broadcaster,
176179
}
177180
}
178181

@@ -305,6 +308,79 @@ where
305308
self.lsps2_client.as_ref().map(|s| (s.lsp_node_id, s.lsp_address.clone()))
306309
}
307310

311+
pub(crate) fn lsps2_channel_needs_manual_broadcast(
312+
&self, counterparty_node_id: PublicKey, user_channel_id: u128,
313+
) -> Result<bool, APIError> {
314+
// if we are not in a client_trusts_lsp model, we don't check and just return false
315+
if !self.is_client_trusts_lsp() {
316+
log_debug!(self.logger, "Skipping funding transaction broadcast as client trusts LSP.");
317+
return Ok(false);
318+
}
319+
320+
// if we are in a client_trusts_lsp model, then we check if the LSP has an LSPS2 operation in progress
321+
self.lsps2_service.as_ref().map_or(Ok(false), |_| {
322+
let lsps2_service_handler = self.liquidity_manager.lsps2_service_handler();
323+
if let Some(handler) = lsps2_service_handler {
324+
handler.channel_needs_manual_broadcast(user_channel_id, &counterparty_node_id)
325+
} else {
326+
log_error!(self.logger, "LSPS2 service handler is not available.");
327+
Ok(false)
328+
}
329+
})
330+
}
331+
332+
pub(crate) fn lsps2_store_funding_transaction(
333+
&self, user_channel_id: u128, counterparty_node_id: PublicKey, funding_tx: Transaction,
334+
) {
335+
if !self.is_client_trusts_lsp() {
336+
log_debug!(self.logger, "Skipping funding transaction broadcast as client trusts LSP.");
337+
return;
338+
}
339+
self.lsps2_service.as_ref().map(|_| {
340+
let lsps2_service_handler = self.liquidity_manager.lsps2_service_handler();
341+
if let Some(handler) = lsps2_service_handler {
342+
handler
343+
.store_funding_transaction(user_channel_id, &counterparty_node_id, funding_tx)
344+
.unwrap_or_else(|e| {
345+
debug_assert!(false, "Failed to store funding transaction: {:?}", e);
346+
log_error!(self.logger, "Failed to store funding transaction: {:?}", e);
347+
});
348+
} else {
349+
log_error!(self.logger, "LSPS2 service handler is not available.");
350+
}
351+
});
352+
}
353+
354+
pub(crate) fn lsps2_funding_tx_broadcast_safe(
355+
&self, user_channel_id: u128, counterparty_node_id: PublicKey,
356+
) {
357+
if !self.is_client_trusts_lsp() {
358+
log_debug!(self.logger, "Skipping funding transaction broadcast as client trusts LSP.");
359+
return;
360+
}
361+
self.lsps2_service.as_ref().map(|_| {
362+
let lsps2_service_handler = self.liquidity_manager.lsps2_service_handler();
363+
if let Some(handler) = lsps2_service_handler {
364+
handler
365+
.set_funding_tx_broadcast_safe(user_channel_id, &counterparty_node_id)
366+
.unwrap_or_else(|e| {
367+
debug_assert!(false, "Failed to store funding transaction: {:?}", e);
368+
log_error!(self.logger, "Failed to store funding transaction: {:?}", e);
369+
});
370+
} else {
371+
log_error!(self.logger, "LSPS2 service handler is not available.");
372+
}
373+
});
374+
}
375+
376+
fn is_client_trusts_lsp(&self) -> bool {
377+
if let Some(lsps2_service) = self.lsps2_service.as_ref() {
378+
lsps2_service.service_config.client_trusts_lsp
379+
} else {
380+
false
381+
}
382+
}
383+
308384
pub(crate) async fn handle_next_event(&self) {
309385
match self.liquidity_manager.next_event_async().await {
310386
LiquidityEvent::LSPS1Client(LSPS1ClientEvent::SupportedOptionsReady {
@@ -594,7 +670,7 @@ where
594670
request_id,
595671
intercept_scid,
596672
LSPS2_CHANNEL_CLTV_EXPIRY_DELTA,
597-
LSPS2_CLIENT_TRUSTS_LSP_MODE,
673+
service_config.client_trusts_lsp,
598674
user_channel_id,
599675
)
600676
.await

src/payment/bolt11.rs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
4747
#[cfg(feature = "uniffi")]
4848
type Bolt11InvoiceDescription = crate::ffi::Bolt11InvoiceDescription;
4949

50+
#[cfg(not(feature = "uniffi"))]
51+
pub struct JitChannelManualClaim {
52+
pub invoice: Bolt11Invoice,
53+
pub preimage: PaymentPreimage,
54+
}
55+
#[cfg(feature = "uniffi")]
56+
type JitChannelManualClaim = crate::ffi::JitChannelManualClaim;
57+
5058
/// A payment handler allowing to create and pay [BOLT 11] invoices.
5159
///
5260
/// Should be retrieved by calling [`Node::bolt11_payment`].
@@ -542,13 +550,14 @@ impl Bolt11Payment {
542550
max_total_lsp_fee_limit_msat: Option<u64>,
543551
) -> Result<Bolt11Invoice, Error> {
544552
let description = maybe_try_convert_enum(description)?;
545-
let invoice = self.receive_via_jit_channel_inner(
553+
let (invoice, _) = self.receive_via_jit_channel_inner(
546554
Some(amount_msat),
547555
&description,
548556
expiry_secs,
549557
max_total_lsp_fee_limit_msat,
550558
None,
551559
None,
560+
true,
552561
)?;
553562
Ok(maybe_wrap(invoice))
554563
}
@@ -581,17 +590,40 @@ impl Bolt11Payment {
581590
max_total_lsp_fee_limit_msat: Option<u64>, payment_hash: PaymentHash,
582591
) -> Result<Bolt11Invoice, Error> {
583592
let description = maybe_try_convert_enum(description)?;
584-
let invoice = self.receive_via_jit_channel_inner(
593+
let (invoice, _) = self.receive_via_jit_channel_inner(
585594
Some(amount_msat),
586595
&description,
587596
expiry_secs,
588597
max_total_lsp_fee_limit_msat,
589598
None,
590599
Some(payment_hash),
600+
true,
591601
)?;
592602
Ok(maybe_wrap(invoice))
593603
}
594604

605+
/// Returns a payable invoice for manual claiming via a JIT channel.
606+
///
607+
/// Similar to `receive_via_jit_channel` but requires manual claiming via `claim_for_hash`.
608+
pub fn receive_via_jit_channel_manual_claim(
609+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
610+
max_total_lsp_fee_limit_msat: Option<u64>,
611+
) -> Result<JitChannelManualClaim, Error> {
612+
let description = maybe_try_convert_enum(description)?;
613+
let (invoice, preimage) = self.receive_via_jit_channel_inner(
614+
Some(amount_msat),
615+
&description,
616+
expiry_secs,
617+
max_total_lsp_fee_limit_msat,
618+
None,
619+
None,
620+
false,
621+
)?;
622+
let preimage = preimage.ok_or(Error::InvoiceCreationFailed)?;
623+
let invoice = maybe_wrap(invoice);
624+
Ok(JitChannelManualClaim { invoice, preimage })
625+
}
626+
595627
/// Returns a payable invoice that can be used to request a variable amount payment (also known
596628
/// as "zero-amount" invoice) and receive it via a newly created just-in-time (JIT) channel.
597629
///
@@ -608,13 +640,14 @@ impl Bolt11Payment {
608640
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
609641
) -> Result<Bolt11Invoice, Error> {
610642
let description = maybe_try_convert_enum(description)?;
611-
let invoice = self.receive_via_jit_channel_inner(
643+
let (invoice, _) = self.receive_via_jit_channel_inner(
612644
None,
613645
&description,
614646
expiry_secs,
615647
None,
616648
max_proportional_lsp_fee_limit_ppm_msat,
617649
None,
650+
true,
618651
)?;
619652
Ok(maybe_wrap(invoice))
620653
}
@@ -648,13 +681,14 @@ impl Bolt11Payment {
648681
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: PaymentHash,
649682
) -> Result<Bolt11Invoice, Error> {
650683
let description = maybe_try_convert_enum(description)?;
651-
let invoice = self.receive_via_jit_channel_inner(
684+
let (invoice, _) = self.receive_via_jit_channel_inner(
652685
None,
653686
&description,
654687
expiry_secs,
655688
None,
656689
max_proportional_lsp_fee_limit_ppm_msat,
657690
Some(payment_hash),
691+
true,
658692
)?;
659693
Ok(maybe_wrap(invoice))
660694
}
@@ -663,7 +697,8 @@ impl Bolt11Payment {
663697
&self, amount_msat: Option<u64>, description: &LdkBolt11InvoiceDescription,
664698
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
665699
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: Option<PaymentHash>,
666-
) -> Result<LdkBolt11Invoice, Error> {
700+
auto_claim: bool,
701+
) -> Result<(LdkBolt11Invoice, Option<PaymentPreimage>), Error> {
667702
let liquidity_source =
668703
self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
669704

@@ -721,9 +756,12 @@ impl Bolt11Payment {
721756
let id = PaymentId(payment_hash.0);
722757
let preimage =
723758
self.channel_manager.get_payment_preimage(payment_hash, payment_secret.clone()).ok();
759+
760+
let stored_preimage = if auto_claim { preimage } else { None };
761+
724762
let kind = PaymentKind::Bolt11Jit {
725763
hash: payment_hash,
726-
preimage,
764+
preimage: stored_preimage,
727765
secret: Some(payment_secret.clone()),
728766
counterparty_skimmed_fee_msat: None,
729767
lsp_fee_limits,
@@ -741,7 +779,7 @@ impl Bolt11Payment {
741779
// Persist LSP peer to make sure we reconnect on restart.
742780
self.peer_store.add_peer(peer_info)?;
743781

744-
Ok(invoice)
782+
Ok((invoice, preimage))
745783
}
746784

747785
/// Sends payment probes over all paths of a route that would be used to pay the given invoice.

0 commit comments

Comments
 (0)