Skip to content

Commit 8040fcc

Browse files
author
anon
committed
feat: min_cltv_expiry_delta for hold invoices
1 parent 3996cf1 commit 8040fcc

4 files changed

Lines changed: 116 additions & 5 deletions

File tree

bindings/ldk_node.udl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,14 @@ interface Bolt11Payment {
209209
[Throws=NodeError]
210210
Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash);
211211
[Throws=NodeError]
212+
Bolt11Invoice receive_for_hash_with_min_cltv_expiry_delta(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash, u16 min_cltv_expiry_delta);
213+
[Throws=NodeError]
212214
Bolt11Invoice receive_variable_amount([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs);
213215
[Throws=NodeError]
214216
Bolt11Invoice receive_variable_amount_for_hash([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash);
215217
[Throws=NodeError]
218+
Bolt11Invoice receive_variable_amount_for_hash_with_min_cltv_expiry_delta([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash, u16 min_cltv_expiry_delta);
219+
[Throws=NodeError]
216220
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
217221
[Throws=NodeError]
218222
Bolt11Invoice receive_via_jit_channel_for_hash(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat, PaymentHash payment_hash);

src/payment/bolt11.rs

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,8 @@ impl Bolt11Payment {
412412
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
413413
) -> Result<Bolt11Invoice, Error> {
414414
let description = maybe_try_convert_enum(description)?;
415-
let invoice = self.receive_inner(Some(amount_msat), &description, expiry_secs, None)?;
415+
let invoice =
416+
self.receive_inner(Some(amount_msat), &description, expiry_secs, None, None)?;
416417
Ok(maybe_wrap(invoice))
417418
}
418419

@@ -435,8 +436,44 @@ impl Bolt11Payment {
435436
payment_hash: PaymentHash,
436437
) -> Result<Bolt11Invoice, Error> {
437438
let description = maybe_try_convert_enum(description)?;
438-
let invoice =
439-
self.receive_inner(Some(amount_msat), &description, expiry_secs, Some(payment_hash))?;
439+
let invoice = self.receive_inner(
440+
Some(amount_msat),
441+
&description,
442+
expiry_secs,
443+
Some(payment_hash),
444+
None,
445+
)?;
446+
Ok(maybe_wrap(invoice))
447+
}
448+
449+
/// Returns a payable invoice that can be used to request a payment of the amount
450+
/// given for the given payment hash.
451+
///
452+
/// We will register the given payment hash and emit a [`PaymentClaimable`] event once
453+
/// the inbound payment arrives.
454+
///
455+
/// `min_cltv_expiry_delta` sets the minimum CLTV delta to use for the final hop.
456+
///
457+
/// **Note:** users *MUST* handle this event and claim the payment manually via
458+
/// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given
459+
/// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via
460+
/// [`fail_for_hash`].
461+
///
462+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
463+
/// [`claim_for_hash`]: Self::claim_for_hash
464+
/// [`fail_for_hash`]: Self::fail_for_hash
465+
pub fn receive_for_hash_with_min_cltv_expiry_delta(
466+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
467+
payment_hash: PaymentHash, min_cltv_expiry_delta: u16,
468+
) -> Result<Bolt11Invoice, Error> {
469+
let description = maybe_try_convert_enum(description)?;
470+
let invoice = self.receive_inner(
471+
Some(amount_msat),
472+
&description,
473+
expiry_secs,
474+
Some(payment_hash),
475+
Some(min_cltv_expiry_delta),
476+
)?;
440477
Ok(maybe_wrap(invoice))
441478
}
442479

@@ -448,7 +485,7 @@ impl Bolt11Payment {
448485
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
449486
) -> Result<Bolt11Invoice, Error> {
450487
let description = maybe_try_convert_enum(description)?;
451-
let invoice = self.receive_inner(None, &description, expiry_secs, None)?;
488+
let invoice = self.receive_inner(None, &description, expiry_secs, None, None)?;
452489
Ok(maybe_wrap(invoice))
453490
}
454491

@@ -470,19 +507,53 @@ impl Bolt11Payment {
470507
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
471508
) -> Result<Bolt11Invoice, Error> {
472509
let description = maybe_try_convert_enum(description)?;
473-
let invoice = self.receive_inner(None, &description, expiry_secs, Some(payment_hash))?;
510+
let invoice =
511+
self.receive_inner(None, &description, expiry_secs, Some(payment_hash), None)?;
512+
Ok(maybe_wrap(invoice))
513+
}
514+
515+
/// Returns a payable invoice that can be used to request a payment for the given payment hash
516+
/// and the amount to be determined by the user, also known as a "zero-amount" invoice.
517+
///
518+
/// We will register the given payment hash and emit a [`PaymentClaimable`] event once
519+
/// the inbound payment arrives.
520+
///
521+
/// `min_cltv_expiry_delta` sets the minimum CLTV delta to use for the final hop.
522+
///
523+
/// **Note:** users *MUST* handle this event and claim the payment manually via
524+
/// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given
525+
/// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via
526+
/// [`fail_for_hash`].
527+
///
528+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
529+
/// [`claim_for_hash`]: Self::claim_for_hash
530+
/// [`fail_for_hash`]: Self::fail_for_hash
531+
pub fn receive_variable_amount_for_hash_with_min_cltv_expiry_delta(
532+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
533+
min_cltv_expiry_delta: u16,
534+
) -> Result<Bolt11Invoice, Error> {
535+
let description = maybe_try_convert_enum(description)?;
536+
let invoice = self.receive_inner(
537+
None,
538+
&description,
539+
expiry_secs,
540+
Some(payment_hash),
541+
Some(min_cltv_expiry_delta),
542+
)?;
474543
Ok(maybe_wrap(invoice))
475544
}
476545

477546
pub(crate) fn receive_inner(
478547
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
479548
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
549+
min_cltv_expiry_delta: Option<u16>,
480550
) -> Result<LdkBolt11Invoice, Error> {
481551
let invoice = {
482552
let invoice_params = Bolt11InvoiceParameters {
483553
amount_msats: amount_msat,
484554
description: invoice_description.clone(),
485555
invoice_expiry_delta_secs: Some(expiry_secs),
556+
min_final_cltv_expiry_delta: min_cltv_expiry_delta,
486557
payment_hash: manual_claim_payment_hash,
487558
..Default::default()
488559
};

src/payment/unified_qr.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ impl UnifiedQrPayment {
112112
&invoice_description,
113113
expiry_sec,
114114
None,
115+
None,
115116
) {
116117
Ok(invoice) => Some(invoice),
117118
Err(e) => {

tests/common/mod.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,41 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
966966
manual_payment_hash,
967967
)
968968
.unwrap();
969+
let min_cltv_expiry_delta = 100;
970+
let manual_preimage_with_min_cltv = PaymentPreimage([44u8; 32]);
971+
let manual_payment_hash_with_min_cltv =
972+
PaymentHash(Sha256::hash(&manual_preimage_with_min_cltv.0).to_byte_array());
973+
let manual_invoice_with_min_cltv = node_b
974+
.bolt11_payment()
975+
.receive_for_hash_with_min_cltv_expiry_delta(
976+
invoice_amount_3_msat,
977+
&invoice_description.clone().into(),
978+
9217,
979+
manual_payment_hash_with_min_cltv,
980+
min_cltv_expiry_delta,
981+
)
982+
.unwrap();
983+
let expected_min_final_cltv_expiry_delta = u64::from(min_cltv_expiry_delta) + 3;
984+
assert_eq!(
985+
manual_invoice_with_min_cltv.min_final_cltv_expiry_delta(),
986+
expected_min_final_cltv_expiry_delta
987+
);
988+
let manual_variable_preimage_with_min_cltv = PaymentPreimage([45u8; 32]);
989+
let manual_variable_payment_hash_with_min_cltv =
990+
PaymentHash(Sha256::hash(&manual_variable_preimage_with_min_cltv.0).to_byte_array());
991+
let manual_variable_invoice_with_min_cltv = node_b
992+
.bolt11_payment()
993+
.receive_variable_amount_for_hash_with_min_cltv_expiry_delta(
994+
&invoice_description.clone().into(),
995+
9217,
996+
manual_variable_payment_hash_with_min_cltv,
997+
min_cltv_expiry_delta,
998+
)
999+
.unwrap();
1000+
assert_eq!(
1001+
manual_variable_invoice_with_min_cltv.min_final_cltv_expiry_delta(),
1002+
expected_min_final_cltv_expiry_delta
1003+
);
9691004
let manual_payment_id = node_a.bolt11_payment().send(&manual_invoice, None).unwrap();
9701005

9711006
let claimable_amount_msat = expect_payment_claimable_event!(

0 commit comments

Comments
 (0)