From 1d72e87e7829d86214f92936c8b4fdcd9fac8d50 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Mon, 21 Nov 2022 14:53:52 +0200 Subject: [PATCH] Add `min_final_cltv_expiry` parameter to invoice utils All utility functions for invoice construction will now also accept an Option<>al `min_final_cltv_expiry_delta` which is useful for things like swaps etc. The `min_final_cltv_expiry_delta` will default back to `MIN_FINAL_CLTV_EXPIRY_DELTA` if `None` is provided. --- lightning-invoice/src/lib.rs | 7 ++ lightning-invoice/src/payment.rs | 6 +- lightning-invoice/src/utils.rs | 186 ++++++++++++++++++++++++----- lightning/src/ln/channelmanager.rs | 2 +- 4 files changed, 169 insertions(+), 32 deletions(-) diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index c36aafd8b..17b6e2de2 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -1448,6 +1448,11 @@ pub enum CreationError { /// /// [phantom invoices]: crate::utils::create_phantom_invoice MissingRouteHints, + + /// The provided `min_final_cltv_expiry_delta` was less than [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. + /// + /// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA + MinFinalCltvExpiryDeltaTooShort, } impl Display for CreationError { @@ -1458,6 +1463,8 @@ impl Display for CreationError { CreationError::TimestampOutOfBounds => f.write_str("The Unix timestamp of the supplied date is less than zero or greater than 35-bits"), CreationError::InvalidAmount => f.write_str("The supplied millisatoshi amount was greater than the total bitcoin supply"), CreationError::MissingRouteHints => f.write_str("The invoice required route hints and they weren't provided"), + CreationError::MinFinalCltvExpiryDeltaTooShort => f.write_str( + "The supplied final CLTV expiry delta was less than LDK's `MIN_FINAL_CLTV_EXPIRY_DELTA`"), } } } diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index 79bfcbeaa..a3517264b 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -2085,7 +2085,7 @@ mod tests { assert!(invoice_payer.pay_invoice(&create_invoice_from_channelmanager_and_duration_since_epoch( &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::Bitcoin, - Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600).unwrap()) + Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600, None).unwrap()) .is_ok()); let htlc_msgs = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(htlc_msgs.len(), 2); @@ -2130,7 +2130,7 @@ mod tests { assert!(invoice_payer.pay_invoice(&create_invoice_from_channelmanager_and_duration_since_epoch( &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::Bitcoin, - Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600).unwrap()) + Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600, None).unwrap()) .is_ok()); let htlc_msgs = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(htlc_msgs.len(), 2); @@ -2211,7 +2211,7 @@ mod tests { assert!(invoice_payer.pay_invoice(&create_invoice_from_channelmanager_and_duration_since_epoch( &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::Bitcoin, - Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600).unwrap()) + Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600, None).unwrap()) .is_ok()); let htlc_updates = SendEvent::from_node(&nodes[0]); check_added_monitors!(nodes[0], 1); diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index 3bf11b6ff..5d25516b9 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -42,6 +42,11 @@ use core::time::Duration; /// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for /// in excess of the current time. /// +/// You can specify a custom `min_final_cltv_expiry_delta`, or let LDK default it to +/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. The provided expiry must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`] - 3. +/// Note that LDK will add a buffer of 3 blocks to the delta to allow for up to a few new block +/// confirmations during routing. +/// /// Note that the provided `keys_manager`'s `NodeSigner` implementation must support phantom /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this /// requirement). @@ -51,10 +56,11 @@ use core::time::Duration; /// [`ChannelManager::create_inbound_payment`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment /// [`ChannelManager::create_inbound_payment_for_hash`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash /// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels +/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub fn create_phantom_invoice( amt_msat: Option, payment_hash: Option, description: String, invoice_expiry_delta_secs: u32, phantom_route_hints: Vec, entropy_source: ES, - node_signer: NS, logger: L, network: Currency, + node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option, ) -> Result> where ES::Target: EntropySource, @@ -65,7 +71,7 @@ where let description = InvoiceDescription::Direct(&description,); _create_phantom_invoice::( amt_msat, payment_hash, description, invoice_expiry_delta_secs, phantom_route_hints, - entropy_source, node_signer, logger, network, + entropy_source, node_signer, logger, network, min_final_cltv_expiry_delta, ) } @@ -104,7 +110,7 @@ where pub fn create_phantom_invoice_with_description_hash( amt_msat: Option, payment_hash: Option, invoice_expiry_delta_secs: u32, description_hash: Sha256, phantom_route_hints: Vec, entropy_source: ES, - node_signer: NS, logger: L, network: Currency + node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option, ) -> Result> where ES::Target: EntropySource, @@ -114,6 +120,7 @@ where _create_phantom_invoice::( amt_msat, payment_hash, InvoiceDescription::Hash(&description_hash), invoice_expiry_delta_secs, phantom_route_hints, entropy_source, node_signer, logger, network, + min_final_cltv_expiry_delta, ) } @@ -121,7 +128,7 @@ where fn _create_phantom_invoice( amt_msat: Option, payment_hash: Option, description: InvoiceDescription, invoice_expiry_delta_secs: u32, phantom_route_hints: Vec, entropy_source: ES, - node_signer: NS, logger: L, network: Currency, + node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option, ) -> Result> where ES::Target: EntropySource, @@ -136,6 +143,10 @@ where )); } + if min_final_cltv_expiry_delta.is_some() && min_final_cltv_expiry_delta.unwrap().saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA { + return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)); + } + let invoice = match description { InvoiceDescription::Direct(description) => { InvoiceBuilder::new(network).description(description.0.clone()) @@ -179,7 +190,9 @@ where .current_timestamp() .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) .payment_secret(payment_secret) - .min_final_cltv_expiry_delta(MIN_FINAL_CLTV_EXPIRY_DELTA.into()) + .min_final_cltv_expiry_delta( + // Add a buffer of 3 to the delta if present, otherwise use LDK's minimum. + min_final_cltv_expiry_delta.map(|x| x.saturating_add(3)).unwrap_or(MIN_FINAL_CLTV_EXPIRY_DELTA).into()) .expiry_time(Duration::from_secs(invoice_expiry_delta_secs.into())); if let Some(amt) = amt_msat { invoice = invoice.amount_milli_satoshis(amt); @@ -235,9 +248,17 @@ where /// /// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for /// in excess of the current time. +/// +/// You can specify a custom `min_final_cltv_expiry_delta`, or let LDK default it to +/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. The provided expiry must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. +/// Note that LDK will add a buffer of 3 blocks to the delta to allow for up to a few new block +/// confirmations during routing. +/// +/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub fn create_invoice_from_channelmanager( channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description: String, invoice_expiry_delta_secs: u32 + network: Currency, amt_msat: Option, description: String, invoice_expiry_delta_secs: u32, + min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::Signer>, @@ -254,7 +275,7 @@ where .expect("for the foreseeable future this shouldn't happen"); create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager, node_signer, logger, network, amt_msat, - description, duration, invoice_expiry_delta_secs + description, duration, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, ) } @@ -268,10 +289,17 @@ where /// /// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for /// in excess of the current time. +/// +/// You can specify a custom `min_final_cltv_expiry_delta`, or let LDK default it to +/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. The provided expiry must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. +/// Note that LDK will add a buffer of 3 blocks to the delta to allow for up to a few new block +/// confirmations during routing. +/// +/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub fn create_invoice_from_channelmanager_with_description_hash( channelmanager: &ChannelManager, node_signer: NS, logger: L, network: Currency, amt_msat: Option, description_hash: Sha256, - invoice_expiry_delta_secs: u32 + invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::Signer>, @@ -291,7 +319,7 @@ where create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( channelmanager, node_signer, logger, network, amt_msat, - description_hash, duration, invoice_expiry_delta_secs + description_hash, duration, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, ) } @@ -301,7 +329,7 @@ where pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( channelmanager: &ChannelManager, node_signer: NS, logger: L, network: Currency, amt_msat: Option, description_hash: Sha256, - duration_since_epoch: Duration, invoice_expiry_delta_secs: u32 + duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::Signer>, @@ -316,7 +344,7 @@ pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_sin _create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager, node_signer, logger, network, amt_msat, InvoiceDescription::Hash(&description_hash), - duration_since_epoch, invoice_expiry_delta_secs + duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, ) } @@ -326,7 +354,7 @@ pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_sin pub fn create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager: &ChannelManager, node_signer: NS, logger: L, network: Currency, amt_msat: Option, description: String, duration_since_epoch: Duration, - invoice_expiry_delta_secs: u32 + invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::Signer>, @@ -343,14 +371,14 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager: &ChannelManager, node_signer: NS, logger: L, network: Currency, amt_msat: Option, description: InvoiceDescription, - duration_since_epoch: Duration, invoice_expiry_delta_secs: u32 + duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::Signer>, @@ -362,13 +390,18 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager: &ChannelManager, node_signer: NS, logger: L, network: Currency, amt_msat: Option, description: String, duration_since_epoch: Duration, - invoice_expiry_delta_secs: u32, payment_hash: PaymentHash + invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::Signer>, @@ -398,14 +431,16 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_ InvoiceDescription::Direct( &Description::new(description).map_err(SignOrCreationError::CreationError)?, ), - duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret + duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, + min_final_cltv_expiry_delta, ) } fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( channelmanager: &ChannelManager, node_signer: NS, logger: L, network: Currency, amt_msat: Option, description: InvoiceDescription, duration_since_epoch: Duration, - invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret + invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret, + min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::Signer>, @@ -420,6 +455,10 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has let our_node_pubkey = channelmanager.get_our_node_id(); let channels = channelmanager.list_channels(); + if min_final_cltv_expiry_delta.is_some() && min_final_cltv_expiry_delta.unwrap().saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA { + return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)); + } + log_trace!(logger, "Creating invoice with payment hash {}", log_bytes!(payment_hash.0)); let invoice = match description { @@ -435,7 +474,9 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) .payment_secret(payment_secret) .basic_mpp() - .min_final_cltv_expiry_delta(MIN_FINAL_CLTV_EXPIRY_DELTA.into()) + .min_final_cltv_expiry_delta( + // Add a buffer of 3 to the delta if present, otherwise use LDK's minimum. + min_final_cltv_expiry_delta.map(|x| x.saturating_add(3)).unwrap_or(MIN_FINAL_CLTV_EXPIRY_DELTA).into()) .expiry_time(Duration::from_secs(invoice_expiry_delta_secs.into())); if let Some(amt) = amt_msat { invoice = invoice.amount_milli_satoshis(amt); @@ -637,7 +678,7 @@ where #[cfg(test)] mod test { use core::time::Duration; - use crate::{Currency, Description, InvoiceDescription}; + use crate::{Currency, Description, InvoiceDescription, SignOrCreationError, CreationError}; use bitcoin_hashes::{Hash, sha256}; use bitcoin_hashes::sha256::Hash as Sha256; use lightning::chain::keysinterface::{EntropySource, PhantomKeysManager}; @@ -663,8 +704,9 @@ mod test { let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, Some(10_000), "test".to_string(), Duration::from_secs(1234567), - non_default_invoice_expiry_secs).unwrap(); + non_default_invoice_expiry_secs, None).unwrap(); assert_eq!(invoice.amount_pico_btc(), Some(100_000)); + // If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`. assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string()))); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); @@ -719,6 +761,44 @@ mod test { assert_eq!(events.len(), 2); } + fn do_create_invoice_min_final_cltv_delta(with_custom_delta: bool) { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let custom_min_final_cltv_expiry_delta = Some(50); + + let invoice = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch( + &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, + Some(10_000), "".into(), Duration::from_secs(1234567), 3600, + if with_custom_delta { custom_min_final_cltv_expiry_delta } else { None }, + ).unwrap(); + assert_eq!(invoice.min_final_cltv_expiry_delta(), if with_custom_delta { + custom_min_final_cltv_expiry_delta.unwrap() + 3 /* Buffer */} else { MIN_FINAL_CLTV_EXPIRY_DELTA } as u64); + } + + #[test] + fn test_create_invoice_custom_min_final_cltv_delta() { + do_create_invoice_min_final_cltv_delta(true); + do_create_invoice_min_final_cltv_delta(false); + } + + #[test] + fn create_invoice_min_final_cltv_delta_equals_htlc_fail_buffer() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let custom_min_final_cltv_expiry_delta = Some(21); + + let invoice = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch( + &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, + Some(10_000), "".into(), Duration::from_secs(1234567), 3600, + custom_min_final_cltv_expiry_delta, + ).unwrap(); + assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); + } + #[test] fn test_create_invoice_with_description_hash() { let chanmon_cfgs = create_chanmon_cfgs(2); @@ -728,7 +808,7 @@ mod test { let description_hash = crate::Sha256(Hash::hash("Testing description_hash".as_bytes())); let invoice = crate::utils::create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, - Some(10_000), description_hash, Duration::from_secs(1234567), 3600 + Some(10_000), description_hash, Duration::from_secs(1234567), 3600, None, ).unwrap(); assert_eq!(invoice.amount_pico_btc(), Some(100_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); @@ -745,7 +825,7 @@ mod test { let invoice = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, Some(10_000), "test".to_string(), Duration::from_secs(1234567), 3600, - payment_hash + payment_hash, None, ).unwrap(); assert_eq!(invoice.amount_pico_btc(), Some(100_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); @@ -936,7 +1016,7 @@ mod test { let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( &invoice_node.node, invoice_node.keys_manager, invoice_node.logger, Currency::BitcoinTestnet, invoice_amt, "test".to_string(), Duration::from_secs(1234567), - 3600).unwrap(); + 3600, None).unwrap(); let hints = invoice.private_routes(); for hint in hints { @@ -988,7 +1068,8 @@ mod test { let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>( Some(payment_amt), payment_hash, "test".to_string(), non_default_invoice_expiry_secs, - route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger, Currency::BitcoinTestnet + route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger, + Currency::BitcoinTestnet, None, ).unwrap(); let (payment_hash, payment_secret) = (PaymentHash(invoice.payment_hash().into_inner()), *invoice.payment_secret()); let payment_preimage = if user_generated_pmt_hash { @@ -1095,7 +1176,10 @@ mod test { nodes[2].node.get_phantom_route_hints(), ]; - let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(Some(payment_amt), Some(payment_hash), "test".to_string(), 3600, route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger, Currency::BitcoinTestnet).unwrap(); + let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, + &test_utils::TestKeysInterface, &test_utils::TestLogger>(Some(payment_amt), Some(payment_hash), + "test".to_string(), 3600, route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, + &nodes[1].logger, Currency::BitcoinTestnet, None).unwrap(); let chan_0_1 = &nodes[1].node.list_usable_channels()[0]; assert_eq!(invoice.route_hints()[0].0[0].htlc_minimum_msat, chan_0_1.inbound_htlc_minimum_msat); @@ -1126,7 +1210,8 @@ mod test { &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger, >( Some(payment_amt), None, non_default_invoice_expiry_secs, description_hash, - route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger, Currency::BitcoinTestnet + route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger, + Currency::BitcoinTestnet, None, ) .unwrap(); assert_eq!(invoice.amount_pico_btc(), Some(200_000)); @@ -1135,6 +1220,32 @@ mod test { assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Description hash phantom invoice".as_bytes())))); } + #[test] + #[cfg(feature = "std")] + fn create_phantom_invoice_with_custom_payment_hash_and_custom_min_final_cltv_delta() { + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let payment_amt = 20_000; + let route_hints = vec![ + nodes[1].node.get_phantom_route_hints(), + nodes[2].node.get_phantom_route_hints(), + ]; + let user_payment_preimage = PaymentPreimage([1; 32]); + let payment_hash = Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).into_inner())); + let non_default_invoice_expiry_secs = 4200; + let min_final_cltv_expiry_delta = Some(100); + let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, + &test_utils::TestKeysInterface, &test_utils::TestLogger>(Some(payment_amt), payment_hash, + "".to_string(), non_default_invoice_expiry_secs, route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, + &nodes[1].logger, Currency::BitcoinTestnet, min_final_cltv_expiry_delta).unwrap(); + assert_eq!(invoice.amount_pico_btc(), Some(200_000)); + assert_eq!(invoice.min_final_cltv_expiry_delta(), (min_final_cltv_expiry_delta.unwrap() + 3) as u64); + assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); + } + #[test] #[cfg(feature = "std")] fn test_multi_node_hints_includes_single_channels_to_participating_nodes() { @@ -1440,7 +1551,10 @@ mod test { .map(|route_hint| route_hint.phantom_scid) .collect::>(); - let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(invoice_amt, None, "test".to_string(), 3600, phantom_route_hints, &invoice_node.keys_manager, &invoice_node.keys_manager, &invoice_node.logger, Currency::BitcoinTestnet).unwrap(); + let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, + &test_utils::TestKeysInterface, &test_utils::TestLogger>(invoice_amt, None, "test".to_string(), + 3600, phantom_route_hints, &invoice_node.keys_manager, &invoice_node.keys_manager, + &invoice_node.logger, Currency::BitcoinTestnet, None).unwrap(); let invoice_hints = invoice.private_routes(); @@ -1463,4 +1577,20 @@ mod test { } assert!(chan_ids_to_match.is_empty(), "Unmatched short channel ids: {:?}", chan_ids_to_match); } + + #[test] + fn test_create_invoice_fails_with_invalid_custom_min_final_cltv_expiry_delta() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let result = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch( + &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, + Some(10_000), "Some description".into(), Duration::from_secs(1234567), 3600, Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), + ); + match result { + Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)) => {}, + _ => panic!(), + } + } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 846a1ecc9..0d2da5d5d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -879,7 +879,7 @@ pub(super) const CLTV_FAR_FAR_AWAY: u32 = 14 * 24 * 6; // Note that we fail if exactly HTLC_FAIL_BACK_BUFFER + 1 was used, so we need to add one for // any payments to succeed. Further, we don't want payments to fail if a block was found while // a payment was being routed, so we add an extra block to be safe. -pub const MIN_FINAL_CLTV_EXPIRY_DELTA: u32 = HTLC_FAIL_BACK_BUFFER + 3; +pub const MIN_FINAL_CLTV_EXPIRY_DELTA: u16 = HTLC_FAIL_BACK_BUFFER as u16 + 3; // Check that our CLTV_EXPIRY is at least CLTV_CLAIM_BUFFER + ANTI_REORG_DELAY + LATENCY_GRACE_PERIOD_BLOCKS, // ie that if the next-hop peer fails the HTLC within -- 2.39.5