diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 5f0a70c2c6..3edf2da956 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -102,12 +102,14 @@ impl Bolt11Payment { pub(crate) fn receive_inner( &self, amount_msat: Option, invoice_description: &LdkBolt11InvoiceDescription, expiry_secs: u32, manual_claim_payment_hash: Option, + min_final_cltv_expiry_delta: Option, ) -> Result { let invoice_params = Bolt11InvoiceParameters { amount_msats: amount_msat, description: invoice_description.clone(), invoice_expiry_delta_secs: Some(expiry_secs), payment_hash: manual_claim_payment_hash, + min_final_cltv_expiry_delta, ..Default::default() }; @@ -549,7 +551,8 @@ impl Bolt11Payment { &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_inner(Some(amount_msat), &description, expiry_secs, None)?; + let invoice = + self.receive_inner(Some(amount_msat), &description, expiry_secs, None, None)?; Ok(maybe_wrap(invoice)) } @@ -578,8 +581,39 @@ impl Bolt11Payment { payment_hash: PaymentHash, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = - self.receive_inner(Some(amount_msat), &description, expiry_secs, Some(payment_hash))?; + let invoice = self.receive_inner( + Some(amount_msat), + &description, + expiry_secs, + Some(payment_hash), + None, + )?; + Ok(maybe_wrap(invoice)) + } + + /// Same as [`receive_for_hash`], but requires the payment's final HTLC to leave at least + /// `min_final_cltv_expiry_delta` blocks until it expires, giving the user that much more time + /// to obtain the preimage. + /// + /// The invoice requests three blocks more than the given value, to allow for blocks being + /// connected while the payment is in flight, and an HTLC arriving with fewer than + /// `min_final_cltv_expiry_delta` blocks left is failed back without a [`PaymentClaimable`] + /// event. + /// + /// [`receive_for_hash`]: Self::receive_for_hash + /// [`PaymentClaimable`]: crate::Event::PaymentClaimable + pub fn receive_for_hash_with_min_final_cltv_expiry_delta( + &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, + payment_hash: PaymentHash, min_final_cltv_expiry_delta: u16, + ) -> Result { + let description = maybe_try_convert_enum(description)?; + let invoice = self.receive_inner( + Some(amount_msat), + &description, + expiry_secs, + Some(payment_hash), + Some(min_final_cltv_expiry_delta), + )?; Ok(maybe_wrap(invoice)) } @@ -591,7 +625,7 @@ impl Bolt11Payment { &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_inner(None, &description, expiry_secs, None)?; + let invoice = self.receive_inner(None, &description, expiry_secs, None, None)?; Ok(maybe_wrap(invoice)) } @@ -619,7 +653,8 @@ impl Bolt11Payment { &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_inner(None, &description, expiry_secs, Some(payment_hash))?; + let invoice = + self.receive_inner(None, &description, expiry_secs, Some(payment_hash), None)?; Ok(maybe_wrap(invoice)) } diff --git a/src/payment/unified.rs b/src/payment/unified.rs index 0eb4f41a28..0515241e56 100644 --- a/src/payment/unified.rs +++ b/src/payment/unified.rs @@ -154,6 +154,7 @@ impl UnifiedPayment { &invoice_description, expiry_sec, None, + None, ) { Ok(invoice) => Some(invoice), Err(e) => { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 8814980711..7f06369253 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1561,6 +1561,39 @@ pub(crate) async fn do_channel_full_cycle( assert_eq!(manual_payment.amount_msat, Some(invoice_amount_3_msat)); assert!(matches!(&manual_payment.kind, PaymentKind::Bolt11 { .. })); + // Test claiming manually registered payments that require a longer final CLTV expiry. + let manual_cltv_amount_msat = 5_532_000; + let min_final_cltv_expiry_delta = 81; + let manual_cltv_preimage = PaymentPreimage([44u8; 32]); + let manual_cltv_payment_hash = + PaymentHash(Sha256::hash(&manual_cltv_preimage.0).to_byte_array()); + let manual_cltv_invoice = node_b + .bolt11_payment() + .receive_for_hash_with_min_final_cltv_expiry_delta( + manual_cltv_amount_msat, + &invoice_description.clone().into(), + 9217, + manual_cltv_payment_hash, + min_final_cltv_expiry_delta, + ) + .unwrap(); + assert_eq!( + manual_cltv_invoice.min_final_cltv_expiry_delta(), + u64::from(min_final_cltv_expiry_delta) + 3 + ); + let outbound_manual_cltv_payment_id = + node_a.bolt11_payment().send(&manual_cltv_invoice, None).unwrap(); + + let (manual_cltv_payment_id, claimable_amount_msat) = + expect_payment_claimable_event!(node_b, manual_cltv_payment_hash, manual_cltv_amount_msat); + node_b + .bolt11_payment() + .claim_for_id(manual_cltv_payment_id, claimable_amount_msat, manual_cltv_preimage) + .unwrap(); + let received_payment_id = expect_payment_received_event!(node_b, claimable_amount_msat); + assert_eq!(received_payment_id, manual_cltv_payment_id); + expect_payment_successful_event!(node_a, outbound_manual_cltv_payment_id, None); + // Test failing manually registered payments. let invoice_amount_4_msat = 5_532_000; let manual_fail_preimage = PaymentPreimage([43u8; 32]); @@ -1632,11 +1665,11 @@ pub(crate) async fn do_channel_full_cycle( assert!(matches!(&received_keysend_payment.kind, PaymentKind::Spontaneous { .. })); assert_eq!( node_a.list_payments_matching(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(), - 5 + 6 ); assert_eq!( node_b.list_payments_matching(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(), - 5 + 6 ); assert_eq!( node_a.list_payments_matching(|p| matches!(p.kind, PaymentKind::Spontaneous { .. })).len(), @@ -1849,6 +1882,7 @@ pub(crate) async fn do_channel_full_cycle( + invoice_amount_1_msat + overpaid_amount_msat + invoice_amount_3_msat + + manual_cltv_amount_msat + determined_amount_msat + keysend_amount_msat) / 1000 - splice_out_sat;