From 0e1723dc74b5261604f43e19eaf89590ab8539d4 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 28 Oct 2024 14:49:21 -0500 Subject: [PATCH 01/10] Owned and ref versions of Bolt11InvoiceDescription Split Bolt11InvoiceDescription into a version used with references to the description or description hash in the invoice and an owned version of these for when constructing an invoice. The latter is useful as it removes an unnecessary clone and can be used in a future change specifying either a description or description hash in larger set of invoice parameters. Since it doesn't use a reference, it can be exposed in bindings as well. --- lightning-invoice/src/lib.rs | 38 +++++++++++++++++++++++-------- lightning/src/ln/invoice_utils.rs | 28 +++++++++++------------ 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 8baedbe26cd..9f049deb78f 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -233,13 +233,33 @@ pub struct Bolt11Invoice { signed_invoice: SignedRawBolt11Invoice, } +/// Represents the description of an invoice which has to be either a directly included string or +/// a hash of a description provided out of band. +#[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)] +pub enum Bolt11InvoiceDescription { + /// Description of what the invoice is for + Direct(Description), + + /// Hash of the description of what the invoice is for + Hash(Sha256), +} + +impl Display for Bolt11InvoiceDescription { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Bolt11InvoiceDescription::Direct(desc) => write!(f, "{}", desc.0), + Bolt11InvoiceDescription::Hash(hash) => write!(f, "{}", hash.0), + } + } +} + /// Represents the description of an invoice which has to be either a directly included string or /// a hash of a description provided out of band. /// /// This is not exported to bindings users as we don't have a good way to map the reference lifetimes making this /// practically impossible to use safely in languages like C. #[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)] -pub enum Bolt11InvoiceDescription<'f> { +pub enum Bolt11InvoiceDescriptionRef<'f> { /// Reference to the directly supplied description in the invoice Direct(&'f Description), @@ -247,11 +267,11 @@ pub enum Bolt11InvoiceDescription<'f> { Hash(&'f Sha256), } -impl<'f> Display for Bolt11InvoiceDescription<'f> { +impl<'f> Display for Bolt11InvoiceDescriptionRef<'f> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Bolt11InvoiceDescription::Direct(desc) => write!(f, "{}", desc.0), - Bolt11InvoiceDescription::Hash(hash) => write!(f, "{}", hash.0), + Bolt11InvoiceDescriptionRef::Direct(desc) => write!(f, "{}", desc.0), + Bolt11InvoiceDescriptionRef::Hash(hash) => write!(f, "{}", hash.0), } } } @@ -708,7 +728,7 @@ impl InvoiceBui pub fn invoice_description(self, description: Bolt11InvoiceDescription) -> InvoiceBuilder { match description { Bolt11InvoiceDescription::Direct(desc) => { - self.description(desc.clone().into_inner().0) + self.description(desc.0.0) } Bolt11InvoiceDescription::Hash(hash) => { self.description_hash(hash.0) @@ -1374,11 +1394,11 @@ impl Bolt11Invoice { /// Return the description or a hash of it for longer ones /// /// This is not exported to bindings users because we don't yet export Bolt11InvoiceDescription - pub fn description(&self) -> Bolt11InvoiceDescription { + pub fn description(&self) -> Bolt11InvoiceDescriptionRef { if let Some(direct) = self.signed_invoice.description() { - return Bolt11InvoiceDescription::Direct(direct); + return Bolt11InvoiceDescriptionRef::Direct(direct); } else if let Some(hash) = self.signed_invoice.description_hash() { - return Bolt11InvoiceDescription::Hash(hash); + return Bolt11InvoiceDescriptionRef::Hash(hash); } unreachable!("ensured by constructor"); } @@ -2211,7 +2231,7 @@ mod test { assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]); assert_eq!( invoice.description(), - Bolt11InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap())) + Bolt11InvoiceDescriptionRef::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap())) ); assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap()); assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32])); diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 611e3c0531d..06f8a76e045 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -75,7 +75,7 @@ where L::Target: Logger, { let description = Description::new(description).map_err(SignOrCreationError::CreationError)?; - let description = Bolt11InvoiceDescription::Direct(&description,); + let description = Bolt11InvoiceDescription::Direct(description); _create_phantom_invoice::( amt_msat, payment_hash, description, invoice_expiry_delta_secs, phantom_route_hints, entropy_source, node_signer, logger, network, min_final_cltv_expiry_delta, duration_since_epoch, @@ -130,7 +130,7 @@ where L::Target: Logger, { _create_phantom_invoice::( - amt_msat, payment_hash, Bolt11InvoiceDescription::Hash(&description_hash), + amt_msat, payment_hash, Bolt11InvoiceDescription::Hash(description_hash), invoice_expiry_delta_secs, phantom_route_hints, entropy_source, node_signer, logger, network, min_final_cltv_expiry_delta, duration_since_epoch, ) @@ -161,7 +161,7 @@ where let invoice = match description { Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.as_inner().0.clone()) + InvoiceBuilder::new(network).description(description.into_inner().0) } Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; @@ -424,7 +424,7 @@ where { _create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager, node_signer, logger, network, amt_msat, - Bolt11InvoiceDescription::Hash(&description_hash), + Bolt11InvoiceDescription::Hash(description_hash), duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, ) } @@ -454,7 +454,7 @@ where _create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager, node_signer, logger, network, amt_msat, Bolt11InvoiceDescription::Direct( - &Description::new(description).map_err(SignOrCreationError::CreationError)?, + Description::new(description).map_err(SignOrCreationError::CreationError)?, ), duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, ) @@ -518,7 +518,7 @@ where .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( channelmanager, node_signer, logger, network, amt_msat, - Bolt11InvoiceDescription::Hash(&description_hash), + Bolt11InvoiceDescription::Hash(description_hash), duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, min_final_cltv_expiry_delta, ) @@ -551,7 +551,7 @@ where _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( channelmanager, node_signer, logger, network, amt_msat, Bolt11InvoiceDescription::Direct( - &Description::new(description).map_err(SignOrCreationError::CreationError)?, + Description::new(description).map_err(SignOrCreationError::CreationError)?, ), duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, min_final_cltv_expiry_delta, @@ -586,7 +586,7 @@ where let invoice = match description { Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.as_inner().0.clone()) + InvoiceBuilder::new(network).description(description.into_inner().0) } Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; @@ -864,7 +864,7 @@ impl<'a, 'b, L: Deref> WithChannelDetails<'a, 'b, L> where L::Target: Logger { mod test { use super::*; use core::time::Duration; - use lightning_invoice::{Currency, Description, Bolt11InvoiceDescription, SignOrCreationError, CreationError}; + use lightning_invoice::{Currency, Description, Bolt11InvoiceDescriptionRef, SignOrCreationError, CreationError}; use bitcoin::hashes::{Hash, sha256}; use bitcoin::hashes::sha256::Hash as Sha256; use crate::sign::PhantomKeysManager; @@ -921,7 +921,7 @@ mod test { assert_eq!(invoice.amount_milli_satoshis(), Some(10_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(), Bolt11InvoiceDescription::Direct(&Description::new("test".to_string()).unwrap())); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Direct(&Description::new("test".to_string()).unwrap())); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is @@ -1009,7 +1009,7 @@ mod test { ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&Sha256(Sha256::hash("Testing description_hash".as_bytes())))); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Hash(&Sha256(Sha256::hash("Testing description_hash".as_bytes())))); } #[test] @@ -1026,7 +1026,7 @@ mod test { ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description::new("test".to_string()).unwrap())); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Direct(&Description::new("test".to_string()).unwrap())); assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap()); } @@ -1379,7 +1379,7 @@ mod test { }; assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description::new("test".to_string()).unwrap())); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Direct(&Description::new("test".to_string()).unwrap())); assert_eq!(invoice.route_hints().len(), 2); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); assert!(!invoice.features().unwrap().supports_basic_mpp()); @@ -1498,7 +1498,7 @@ mod test { assert_eq!(invoice.amount_milli_satoshis(), Some(20_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&Sha256(Sha256::hash("Description hash phantom invoice".as_bytes())))); + assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Hash(&Sha256(Sha256::hash("Description hash phantom invoice".as_bytes())))); } #[test] From 65790b67189745471e60606c622cd97b7314cedc Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 6 Nov 2024 11:11:11 -0600 Subject: [PATCH 02/10] Add Description::empty constructor When creating a default Bolt11InvoiceParameters, having an infallible constructor avoids an unwrap. --- lightning-invoice/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 9f049deb78f..6f7ec225337 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -1600,6 +1600,11 @@ impl Description { } } + /// Creates an empty `Description`. + pub fn empty() -> Self { + Description(UntrustedString(String::new())) + } + /// Returns the underlying description [`UntrustedString`] pub fn into_inner(self) -> UntrustedString { self.0 From 2a954020c6855302d24c39f859dc68f49ad009c8 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 28 Oct 2024 15:54:01 -0500 Subject: [PATCH 03/10] Combine import statements --- lightning/src/ln/invoice_utils.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 06f8a76e045..1cccb69f48f 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -11,8 +11,7 @@ use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::sign::{Recipient, NodeSigner, SignerProvider, EntropySource}; use crate::types::payment::{PaymentHash, PaymentSecret}; use crate::ln::channel_state::ChannelDetails; -use crate::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY_DELTA}; -use crate::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA}; +use crate::ln::channelmanager::{ChannelManager, PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA, MIN_FINAL_CLTV_EXPIRY_DELTA}; use crate::ln::inbound_payment::{create, create_from_hash, ExpandedKey}; use crate::routing::gossip::RoutingFees; use crate::routing::router::{RouteHint, RouteHintHop, Router}; From 0ea225cfe7ea2d7e8e3692f6dcd3984232e78482 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 6 Nov 2024 18:53:18 -0600 Subject: [PATCH 04/10] Remove no-std support from invoice_utils functions The upcoming ChannelManager::create_bolt11_invoice will not support setting a specific creation time, so remove that functionality from the invoice_utils functions. This will avoid duplicate code when deprecating. --- lightning/src/ln/invoice_utils.rs | 135 ++++++++++-------------------- 1 file changed, 46 insertions(+), 89 deletions(-) diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 1cccb69f48f..8de460c8a06 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -346,11 +346,16 @@ where L::Target: Logger, { use std::time::SystemTime; - let duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + let duration_since_epoch = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) .expect("for the foreseeable future this shouldn't happen"); - create_invoice_from_channelmanager_and_duration_since_epoch( + + _create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager, node_signer, logger, network, amt_msat, - description, duration, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, + Bolt11InvoiceDescription::Direct( + Description::new(description).map_err(SignOrCreationError::CreationError)?, + ), + duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, ) } @@ -388,39 +393,10 @@ where L::Target: Logger, { use std::time::SystemTime; - - let duration = SystemTime::now() + let duration_since_epoch = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .expect("for the foreseeable future this shouldn't happen"); - 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, min_final_cltv_expiry_delta, - ) -} - -/// Utility to construct an invoice. Generally, unless you want to do something like a custom -/// `cltv_expiry`, this is what you should be using to create an invoice. -#[cfg_attr(feature = "std", doc = "")] -#[cfg_attr(feature = "std", doc = "See [`create_invoice_from_channelmanager_with_description_hash`] for more information.")] -#[cfg_attr(feature = "std", doc = "")] -#[cfg_attr(feature = "std", doc = "This can be used in a `no_std` environment, where [`std::time::SystemTime`] is not available and the current time is supplied by the caller.")] -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, min_final_cltv_expiry_delta: Option, -) -> Result> -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ _create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager, node_signer, logger, network, amt_msat, Bolt11InvoiceDescription::Hash(description_hash), @@ -428,37 +404,6 @@ where ) } -/// Utility to construct an invoice. Generally, unless you want to do something like a custom -/// `cltv_expiry`, this is what you should be using to create an invoice. -#[cfg_attr(feature = "std", doc = "")] -#[cfg_attr(feature = "std", doc = "See [`create_invoice_from_channelmanager`] for more information.")] -#[cfg_attr(feature = "std", doc = "")] -#[cfg_attr(feature = "std", doc = "This version can be used in a `no_std` environment, where [`std::time::SystemTime`] is not available and the current time is supplied by the caller.")] -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, min_final_cltv_expiry_delta: Option, -) -> Result> -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - _create_invoice_from_channelmanager_and_duration_since_epoch( - channelmanager, node_signer, logger, network, amt_msat, - Bolt11InvoiceDescription::Direct( - Description::new(description).map_err(SignOrCreationError::CreationError)?, - ), - duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, - ) -} - fn _create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager: &ChannelManager, node_signer: NS, logger: L, network: Currency, amt_msat: Option, description: Bolt11InvoiceDescription, @@ -489,15 +434,17 @@ where invoice_expiry_delta_secs, payment_hash, payment_secret, min_final_cltv_expiry_delta) } -/// See [`create_invoice_from_channelmanager_and_duration_since_epoch`] +#[cfg(feature = "std")] +/// See [`create_invoice_from_channelmanager`]. +/// /// This version allows for providing custom [`PaymentHash`] and description hash for the invoice. /// /// This may be useful if you're building an on-chain swap or involving another protocol where /// the payment hash is also involved outside the scope of lightning and want to set the /// description hash. -pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch_with_payment_hash( +pub fn create_invoice_from_channelmanager_with_description_hash_and_payment_hash( channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description_hash: Sha256, duration_since_epoch: Duration, + network: Currency, amt_msat: Option, description_hash: Sha256, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, ) -> Result> where @@ -511,6 +458,11 @@ where MR::Target: MessageRouter, L::Target: Logger, { + use std::time::SystemTime; + let duration_since_epoch = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("for the foreseeable future this shouldn't happen"); + let payment_secret = channelmanager .create_inbound_payment_for_hash(payment_hash, amt_msat, invoice_expiry_delta_secs, min_final_cltv_expiry_delta) @@ -523,14 +475,16 @@ where ) } -/// See [`create_invoice_from_channelmanager_and_duration_since_epoch`] +#[cfg(feature = "std")] +/// See [`create_invoice_from_channelmanager`]. +/// /// This version allows for providing a custom [`PaymentHash`] for the invoice. /// This may be useful if you're building an on-chain swap or involving another protocol where /// the payment hash is also involved outside the scope of lightning. -pub fn create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( +pub fn create_invoice_from_channelmanager_with_payment_hash( 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, min_final_cltv_expiry_delta: Option, + network: Currency, amt_msat: Option, description: String, invoice_expiry_delta_secs: u32, + payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::EcdsaSigner>, @@ -543,6 +497,11 @@ where MR::Target: MessageRouter, L::Target: Logger, { + use std::time::SystemTime; + let duration_since_epoch = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("for the foreseeable future this shouldn't happen"); + let payment_secret = channelmanager .create_inbound_payment_for_hash(payment_hash, amt_msat, invoice_expiry_delta_secs, min_final_cltv_expiry_delta) @@ -913,10 +872,10 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); let non_default_invoice_expiry_secs = 4200; - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( + let invoice = create_invoice_from_channelmanager( 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, None).unwrap(); + Some(10_000), "test".to_string(), non_default_invoice_expiry_secs, None, + ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_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); @@ -964,9 +923,9 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let custom_min_final_cltv_expiry_delta = Some(50); - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( + let invoice = create_invoice_from_channelmanager( nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, - Some(10_000), "".into(), Duration::from_secs(1234567), 3600, + Some(10_000), "".into(), 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 { @@ -987,10 +946,9 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let custom_min_final_cltv_expiry_delta = Some(21); - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( + let invoice = create_invoice_from_channelmanager( 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, + Some(10_000), "".into(), 3600, custom_min_final_cltv_expiry_delta, ).unwrap(); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); } @@ -1002,9 +960,9 @@ mod test { let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let description_hash = Sha256(Hash::hash("Testing description_hash".as_bytes())); - let invoice = create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( + let invoice = create_invoice_from_channelmanager_with_description_hash( nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, - Some(10_000), description_hash, Duration::from_secs(1234567), 3600, None, + Some(10_000), description_hash, 3600, None, ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); @@ -1018,10 +976,9 @@ mod test { let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let payment_hash = PaymentHash([0; 32]); - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( + let invoice = create_invoice_from_channelmanager_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, None, + Some(10_000), "test".to_string(), 3600, payment_hash, None, ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); @@ -1310,10 +1267,10 @@ mod test { invoice_node: &Node<'a, 'b, 'c>, mut chan_ids_to_match: HashSet ) { - let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( + let invoice = create_invoice_from_channelmanager( invoice_node.node, invoice_node.keys_manager, invoice_node.logger, - Currency::BitcoinTestnet, invoice_amt, "test".to_string(), Duration::from_secs(1234567), - 3600, None).unwrap(); + Currency::BitcoinTestnet, invoice_amt, "test".to_string(), 3600, None, + ).unwrap(); let hints = invoice.private_routes(); for hint in hints { @@ -1948,9 +1905,9 @@ mod test { 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 = create_invoice_from_channelmanager_and_duration_since_epoch( + let result = create_invoice_from_channelmanager( 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), + Some(10_000), "Some description".into(), 3600, Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), ); match result { Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)) => {}, From 284cb28af291b94e9351160c22cf881680b7d1b2 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 8 Nov 2024 11:44:00 -0600 Subject: [PATCH 05/10] Remove currency support from invoice_utils When creating an invoice using a ChannelManager, payments for a specific ChainHash / Network are only valid. Use the one from the ChannelManager instead of allowing arbitrary ones in the form of a Currency. --- lightning/src/ln/channelmanager.rs | 2 +- lightning/src/ln/invoice_utils.rs | 41 +++++++++++++++++------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 3914384ca82..5c39e901772 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2199,7 +2199,7 @@ where L::Target: Logger, { default_configuration: UserConfig, - chain_hash: ChainHash, + pub(super) chain_hash: ChainHash, fee_estimator: LowerBoundedFeeEstimator, chain_monitor: M, tx_broadcaster: T, diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 8de460c8a06..1df7bfa08a8 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -6,6 +6,7 @@ use lightning_invoice::{Description, Bolt11InvoiceDescription, Sha256}; use crate::prelude::*; use bitcoin::hashes::Hash; +use bitcoin::network::Network; use crate::chain; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::sign::{Recipient, NodeSigner, SignerProvider, EntropySource}; @@ -331,7 +332,7 @@ fn rotate_through_iterators>(mut vecs: Vec) -> impl /// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::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, + amt_msat: Option, description: String, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, ) -> Result> where @@ -351,7 +352,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, + channelmanager, node_signer, logger, amt_msat, Bolt11InvoiceDescription::Direct( Description::new(description).map_err(SignOrCreationError::CreationError)?, ), @@ -378,7 +379,7 @@ where /// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::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, + amt_msat: Option, description_hash: Sha256, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, ) -> Result> where @@ -398,7 +399,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, + channelmanager, node_signer, logger, amt_msat, Bolt11InvoiceDescription::Hash(description_hash), duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, ) @@ -406,7 +407,7 @@ where fn _create_invoice_from_channelmanager_and_duration_since_epoch( channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description: Bolt11InvoiceDescription, + amt_msat: Option, description: Bolt11InvoiceDescription, duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, ) -> Result> where @@ -430,7 +431,7 @@ where .create_inbound_payment(amt_msat, invoice_expiry_delta_secs, min_final_cltv_expiry_delta) .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager, node_signer, logger, network, amt_msat, description, duration_since_epoch, + channelmanager, node_signer, logger, amt_msat, description, duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, min_final_cltv_expiry_delta) } @@ -444,7 +445,7 @@ where /// description hash. pub fn create_invoice_from_channelmanager_with_description_hash_and_payment_hash( channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description_hash: Sha256, + amt_msat: Option, description_hash: Sha256, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, ) -> Result> where @@ -468,7 +469,7 @@ where min_final_cltv_expiry_delta) .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager, node_signer, logger, network, amt_msat, + channelmanager, node_signer, logger, amt_msat, Bolt11InvoiceDescription::Hash(description_hash), duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, min_final_cltv_expiry_delta, @@ -483,7 +484,7 @@ where /// the payment hash is also involved outside the scope of lightning. pub fn create_invoice_from_channelmanager_with_payment_hash( channelmanager: &ChannelManager, node_signer: NS, logger: L, - network: Currency, amt_msat: Option, description: String, invoice_expiry_delta_secs: u32, + amt_msat: Option, description: String, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, ) -> Result> where @@ -507,7 +508,7 @@ where min_final_cltv_expiry_delta) .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager, node_signer, logger, network, amt_msat, + channelmanager, node_signer, logger, amt_msat, Bolt11InvoiceDescription::Direct( Description::new(description).map_err(SignOrCreationError::CreationError)?, ), @@ -518,7 +519,7 @@ where 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: Bolt11InvoiceDescription, + amt_msat: Option, description: Bolt11InvoiceDescription, duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret, min_final_cltv_expiry_delta: Option, ) -> Result> @@ -536,6 +537,10 @@ where let our_node_pubkey = channelmanager.get_our_node_id(); let channels = channelmanager.list_channels(); + let network = Network::from_chain_hash(channelmanager.chain_hash) + .map(Into::into) + .unwrap_or(Currency::Bitcoin); + 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)); } @@ -873,7 +878,7 @@ mod test { create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); let non_default_invoice_expiry_secs = 4200; let invoice = create_invoice_from_channelmanager( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, + nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Some(10_000), "test".to_string(), non_default_invoice_expiry_secs, None, ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); @@ -924,7 +929,7 @@ mod test { let custom_min_final_cltv_expiry_delta = Some(50); let invoice = create_invoice_from_channelmanager( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, + nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Some(10_000), "".into(), 3600, if with_custom_delta { custom_min_final_cltv_expiry_delta } else { None }, ).unwrap(); @@ -947,7 +952,7 @@ mod test { let custom_min_final_cltv_expiry_delta = Some(21); let invoice = create_invoice_from_channelmanager( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, + nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Some(10_000), "".into(), 3600, custom_min_final_cltv_expiry_delta, ).unwrap(); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); @@ -961,7 +966,7 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let description_hash = Sha256(Hash::hash("Testing description_hash".as_bytes())); let invoice = create_invoice_from_channelmanager_with_description_hash( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, + nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Some(10_000), description_hash, 3600, None, ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); @@ -977,7 +982,7 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let payment_hash = PaymentHash([0; 32]); let invoice = create_invoice_from_channelmanager_with_payment_hash( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, + nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Some(10_000), "test".to_string(), 3600, payment_hash, None, ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); @@ -1269,7 +1274,7 @@ mod test { ) { let invoice = create_invoice_from_channelmanager( invoice_node.node, invoice_node.keys_manager, invoice_node.logger, - Currency::BitcoinTestnet, invoice_amt, "test".to_string(), 3600, None, + invoice_amt, "test".to_string(), 3600, None, ).unwrap(); let hints = invoice.private_routes(); @@ -1906,7 +1911,7 @@ mod test { let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let result = create_invoice_from_channelmanager( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, + nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Some(10_000), "Some description".into(), 3600, Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), ); match result { From 3c6896c9fa9538dfccce3fd3b72257fa29dd960c Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 28 Oct 2024 17:28:10 -0500 Subject: [PATCH 06/10] Add a BOLT11 invoice utility to ChannelManager Now that the lightning crate depends on the lightning_invoice crate, the utility functions previously living in the latter can be implemented on ChannelManager. Additionally, the parameters are now moved to a struct in order to remove the increasingly combinatorial blow-up of methods. The new Bolt11InvoiceParameters is used to determine what values to set in the invoice. Using None for any given parameter results in a reasonable the default or a behavior determined by the ChannelManager as detailed in the documentation. --- lightning/src/ln/channelmanager.rs | 143 ++++++++++++++++- lightning/src/ln/invoice_utils.rs | 236 +++++++---------------------- 2 files changed, 195 insertions(+), 184 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 5c39e901772..9631851eb73 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -102,6 +102,8 @@ use { crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder, }; +use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, CreationError, Currency, Description, InvoiceBuilder as Bolt11InvoiceBuilder, SignOrCreationError, DEFAULT_EXPIRY_TIME}; + use alloc::collections::{btree_map, BTreeMap}; use crate::io; @@ -2199,7 +2201,7 @@ where L::Target: Logger, { default_configuration: UserConfig, - pub(super) chain_hash: ChainHash, + chain_hash: ChainHash, fee_estimator: LowerBoundedFeeEstimator, chain_monitor: M, tx_broadcaster: T, @@ -9093,6 +9095,145 @@ where self.finish_close_channel(failure); } } + + /// Utility for creating a BOLT11 invoice that can be verified by [`ChannelManager`] without + /// storing any additional state. It achieves this by including a [`PaymentSecret`] in the + /// invoice which it uses to verify that the invoice has not expired and the payment amount is + /// sufficient, reproducing the [`PaymentPreimage`] if applicable. + pub fn create_bolt11_invoice( + &self, params: Bolt11InvoiceParameters, + ) -> Result> { + let Bolt11InvoiceParameters { + amount_msats, description, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, + payment_hash, + } = params; + + let currency = + Network::from_chain_hash(self.chain_hash).map(Into::into).unwrap_or(Currency::Bitcoin); + + #[cfg(feature = "std")] + let duration_since_epoch = { + use std::time::SystemTime; + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + .expect("for the foreseeable future this shouldn't happen") + }; + #[cfg(not(feature = "std"))] + let duration_since_epoch = + Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); + + if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta { + if min_final_cltv_expiry_delta.saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA { + return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)); + } + } + + let (payment_hash, payment_secret) = match payment_hash { + Some(payment_hash) => { + let payment_secret = self + .create_inbound_payment_for_hash( + payment_hash, amount_msats, + invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32), + min_final_cltv_expiry_delta, + ) + .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; + (payment_hash, payment_secret) + }, + None => { + self + .create_inbound_payment( + amount_msats, invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32), + min_final_cltv_expiry_delta, + ) + .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))? + }, + }; + + log_trace!(self.logger, "Creating invoice with payment hash {}", &payment_hash); + + let invoice = Bolt11InvoiceBuilder::new(currency); + let invoice = match description { + Bolt11InvoiceDescription::Direct(description) => invoice.description(description.into_inner().0), + Bolt11InvoiceDescription::Hash(hash) => invoice.description_hash(hash.0), + }; + + let mut invoice = invoice + .duration_since_epoch(duration_since_epoch) + .payee_pub_key(self.get_our_node_id()) + .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) + .payment_secret(payment_secret) + .basic_mpp() + .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() + ); + + if let Some(invoice_expiry_delta_secs) = invoice_expiry_delta_secs{ + invoice = invoice.expiry_time(Duration::from_secs(invoice_expiry_delta_secs.into())); + } + + if let Some(amount_msats) = amount_msats { + invoice = invoice.amount_milli_satoshis(amount_msats); + } + + let channels = self.list_channels(); + let route_hints = super::invoice_utils::sort_and_filter_channels(channels, amount_msats, &self.logger); + for hint in route_hints { + invoice = invoice.private_route(hint); + } + + let raw_invoice = invoice.build_raw().map_err(|e| SignOrCreationError::CreationError(e))?; + let signature = self.node_signer.sign_invoice(&raw_invoice, Recipient::Node); + + raw_invoice + .sign(|_| signature) + .map(|invoice| Bolt11Invoice::from_signed(invoice).unwrap()) + .map_err(|e| SignOrCreationError::SignError(e)) + } +} + +/// Parameters used with [`create_bolt11_invoice`]. +/// +/// [`create_bolt11_invoice`]: ChannelManager::create_bolt11_invoice +pub struct Bolt11InvoiceParameters { + /// The amount for the invoice, if any. + pub amount_msats: Option, + + /// The description for what the invoice is for, or hash of such description. + pub description: Bolt11InvoiceDescription, + + /// The invoice expiration relative to its creation time. If not set, the invoice will expire in + /// [`DEFAULT_EXPIRY_TIME`] by default. + /// + /// The creation time used is the duration since the Unix epoch for `std` builds. For non-`std` + /// builds, the highest block timestamp seen is used instead. + pub invoice_expiry_delta_secs: Option, + + /// The minimum `cltv_expiry` for the last HTLC in the route. If not set, will use + /// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. + /// + /// If set, must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`], and a three-block buffer will be + /// added as well to allow for up to a few new block confirmations during routing. + pub min_final_cltv_expiry_delta: Option, + + /// The payment hash used in the invoice. If not set, a payment hash will be generated using a + /// preimage that can be reproduced by [`ChannelManager`] without storing any state. + /// + /// Uses the payment hash if set. This may be useful if you're building an on-chain swap or + /// involving another protocol where the payment hash is also involved outside the scope of + /// lightning. + pub payment_hash: Option, +} + +impl Default for Bolt11InvoiceParameters { + fn default() -> Self { + Self { + amount_msats: None, + description: Bolt11InvoiceDescription::Direct(Description::empty()), + invoice_expiry_delta_secs: None, + min_final_cltv_expiry_delta: None, + payment_hash: None, + } + } } macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 1df7bfa08a8..01767ab05a3 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -6,13 +6,12 @@ use lightning_invoice::{Description, Bolt11InvoiceDescription, Sha256}; use crate::prelude::*; use bitcoin::hashes::Hash; -use bitcoin::network::Network; use crate::chain; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::sign::{Recipient, NodeSigner, SignerProvider, EntropySource}; -use crate::types::payment::{PaymentHash, PaymentSecret}; +use crate::types::payment::PaymentHash; use crate::ln::channel_state::ChannelDetails; -use crate::ln::channelmanager::{ChannelManager, PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA, MIN_FINAL_CLTV_EXPIRY_DELTA}; +use crate::ln::channelmanager::{Bolt11InvoiceParameters, ChannelManager, PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA, MIN_FINAL_CLTV_EXPIRY_DELTA}; use crate::ln::inbound_payment::{create, create_from_hash, ExpandedKey}; use crate::routing::gossip::RoutingFees; use crate::routing::router::{RouteHint, RouteHintHop, Router}; @@ -314,7 +313,6 @@ fn rotate_through_iterators>(mut vecs: Vec) -> impl }) } -#[cfg(feature = "std")] /// Utility to construct an invoice. Generally, unless you want to do something like a custom /// cltv_expiry, this is what you should be using to create an invoice. The reason being, this /// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user @@ -331,9 +329,8 @@ fn rotate_through_iterators>(mut vecs: Vec) -> impl /// /// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub fn create_invoice_from_channelmanager( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - amt_msat: Option, description: String, invoice_expiry_delta_secs: u32, - min_final_cltv_expiry_delta: Option, + channelmanager: &ChannelManager, amt_msat: Option, + description: String, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::EcdsaSigner>, @@ -346,21 +343,17 @@ where MR::Target: MessageRouter, L::Target: Logger, { - use std::time::SystemTime; - let duration_since_epoch = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("for the foreseeable future this shouldn't happen"); - - _create_invoice_from_channelmanager_and_duration_since_epoch( - channelmanager, node_signer, logger, amt_msat, - Bolt11InvoiceDescription::Direct( - Description::new(description).map_err(SignOrCreationError::CreationError)?, - ), - duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, - ) + let description = Description::new(description).map_err(SignOrCreationError::CreationError)?; + let params = Bolt11InvoiceParameters { + amount_msats: amt_msat, + description: Bolt11InvoiceDescription::Direct(description), + invoice_expiry_delta_secs: Some(invoice_expiry_delta_secs), + min_final_cltv_expiry_delta, + payment_hash: None, + }; + channelmanager.create_bolt11_invoice(params) } -#[cfg(feature = "std")] /// Utility to construct an invoice. Generally, unless you want to do something like a custom /// cltv_expiry, this is what you should be using to create an invoice. The reason being, this /// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user @@ -378,37 +371,9 @@ where /// /// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub fn create_invoice_from_channelmanager_with_description_hash( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - amt_msat: Option, description_hash: Sha256, - invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, -) -> Result> -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - use std::time::SystemTime; - let duration_since_epoch = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("for the foreseeable future this shouldn't happen"); - - _create_invoice_from_channelmanager_and_duration_since_epoch( - channelmanager, node_signer, logger, amt_msat, - Bolt11InvoiceDescription::Hash(description_hash), - duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, - ) -} - -fn _create_invoice_from_channelmanager_and_duration_since_epoch( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - amt_msat: Option, description: Bolt11InvoiceDescription, - duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option, + channelmanager: &ChannelManager, amt_msat: Option, + description_hash: Sha256, invoice_expiry_delta_secs: u32, + min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::EcdsaSigner>, @@ -421,21 +386,16 @@ where MR::Target: MessageRouter, L::Target: Logger, { - 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)); - } - - // `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin - // supply. - let (payment_hash, payment_secret) = channelmanager - .create_inbound_payment(amt_msat, invoice_expiry_delta_secs, min_final_cltv_expiry_delta) - .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; - _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager, node_signer, logger, amt_msat, description, duration_since_epoch, - invoice_expiry_delta_secs, payment_hash, payment_secret, min_final_cltv_expiry_delta) + let params = Bolt11InvoiceParameters { + amount_msats: amt_msat, + description: Bolt11InvoiceDescription::Hash(description_hash), + invoice_expiry_delta_secs: Some(invoice_expiry_delta_secs), + min_final_cltv_expiry_delta, + payment_hash: None, + }; + channelmanager.create_bolt11_invoice(params) } -#[cfg(feature = "std")] /// See [`create_invoice_from_channelmanager`]. /// /// This version allows for providing custom [`PaymentHash`] and description hash for the invoice. @@ -444,9 +404,9 @@ where /// the payment hash is also involved outside the scope of lightning and want to set the /// description hash. pub fn create_invoice_from_channelmanager_with_description_hash_and_payment_hash( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - amt_msat: Option, description_hash: Sha256, - invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, + channelmanager: &ChannelManager, amt_msat: Option, + description_hash: Sha256, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, + min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::EcdsaSigner>, @@ -459,33 +419,25 @@ where MR::Target: MessageRouter, L::Target: Logger, { - use std::time::SystemTime; - let duration_since_epoch = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("for the foreseeable future this shouldn't happen"); - - let payment_secret = channelmanager - .create_inbound_payment_for_hash(payment_hash, amt_msat, invoice_expiry_delta_secs, - min_final_cltv_expiry_delta) - .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; - _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager, node_signer, logger, amt_msat, - Bolt11InvoiceDescription::Hash(description_hash), - duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, + let params = Bolt11InvoiceParameters { + amount_msats: amt_msat, + description: Bolt11InvoiceDescription::Hash(description_hash), + invoice_expiry_delta_secs: Some(invoice_expiry_delta_secs), min_final_cltv_expiry_delta, - ) + payment_hash: Some(payment_hash), + }; + channelmanager.create_bolt11_invoice(params) } -#[cfg(feature = "std")] /// See [`create_invoice_from_channelmanager`]. /// /// This version allows for providing a custom [`PaymentHash`] for the invoice. /// This may be useful if you're building an on-chain swap or involving another protocol where /// the payment hash is also involved outside the scope of lightning. pub fn create_invoice_from_channelmanager_with_payment_hash( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - amt_msat: Option, description: String, invoice_expiry_delta_secs: u32, - payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option, + channelmanager: &ChannelManager, amt_msat: Option, + description: String, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, + min_final_cltv_expiry_delta: Option, ) -> Result> where M::Target: chain::Watch<::EcdsaSigner>, @@ -498,91 +450,15 @@ where MR::Target: MessageRouter, L::Target: Logger, { - use std::time::SystemTime; - let duration_since_epoch = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("for the foreseeable future this shouldn't happen"); - - let payment_secret = channelmanager - .create_inbound_payment_for_hash(payment_hash, amt_msat, invoice_expiry_delta_secs, - min_final_cltv_expiry_delta) - .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; - _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager, node_signer, logger, amt_msat, - Bolt11InvoiceDescription::Direct( - Description::new(description).map_err(SignOrCreationError::CreationError)?, - ), - duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, + let description = Description::new(description).map_err(SignOrCreationError::CreationError)?; + let params = Bolt11InvoiceParameters { + amount_msats: amt_msat, + description: Bolt11InvoiceDescription::Direct(description), + invoice_expiry_delta_secs: Some(invoice_expiry_delta_secs), min_final_cltv_expiry_delta, - ) -} - -fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( - channelmanager: &ChannelManager, node_signer: NS, logger: L, - amt_msat: Option, description: Bolt11InvoiceDescription, - duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, - payment_secret: PaymentSecret, min_final_cltv_expiry_delta: Option, -) -> Result> -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - let our_node_pubkey = channelmanager.get_our_node_id(); - let channels = channelmanager.list_channels(); - - let network = Network::from_chain_hash(channelmanager.chain_hash) - .map(Into::into) - .unwrap_or(Currency::Bitcoin); - - 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 {}", &payment_hash); - - let invoice = match description { - Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.into_inner().0) - } - Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), + payment_hash: Some(payment_hash), }; - - let mut invoice = invoice - .duration_since_epoch(duration_since_epoch) - .payee_pub_key(our_node_pubkey) - .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) - .payment_secret(payment_secret) - .basic_mpp() - .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); - } - - let route_hints = sort_and_filter_channels(channels, amt_msat, &logger); - for hint in route_hints { - invoice = invoice.private_route(hint); - } - - let raw_invoice = match invoice.build_raw() { - Ok(inv) => inv, - Err(e) => return Err(SignOrCreationError::CreationError(e)) - }; - let signature = node_signer.sign_invoice(&raw_invoice, Recipient::Node); - let signed_raw_invoice = raw_invoice.sign(|_| signature); - match signed_raw_invoice { - Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()), - Err(e) => Err(SignOrCreationError::SignError(e)) - } + channelmanager.create_bolt11_invoice(params) } /// Sorts and filters the `channels` for an invoice, and returns the corresponding `RouteHint`s to include @@ -604,7 +480,7 @@ where /// * Limited to a total of 3 channels. /// * Sorted by lowest inbound capacity if an online channel with the minimum amount requested exists, /// otherwise sort by highest inbound capacity to give the payment the best chance of succeeding. -fn sort_and_filter_channels( +pub(super) fn sort_and_filter_channels( channels: Vec, min_inbound_capacity_msat: Option, logger: &L, @@ -878,8 +754,7 @@ mod test { create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); let non_default_invoice_expiry_secs = 4200; let invoice = create_invoice_from_channelmanager( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, - Some(10_000), "test".to_string(), non_default_invoice_expiry_secs, None, + nodes[1].node, Some(10_000), "test".to_string(), non_default_invoice_expiry_secs, None, ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); // If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`. @@ -929,8 +804,7 @@ mod test { let custom_min_final_cltv_expiry_delta = Some(50); let invoice = create_invoice_from_channelmanager( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, - Some(10_000), "".into(), 3600, + nodes[1].node, Some(10_000), "".into(), 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 { @@ -952,8 +826,7 @@ mod test { let custom_min_final_cltv_expiry_delta = Some(21); let invoice = create_invoice_from_channelmanager( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, - Some(10_000), "".into(), 3600, custom_min_final_cltv_expiry_delta, + nodes[1].node, Some(10_000), "".into(), 3600, custom_min_final_cltv_expiry_delta, ).unwrap(); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); } @@ -966,8 +839,7 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let description_hash = Sha256(Hash::hash("Testing description_hash".as_bytes())); let invoice = create_invoice_from_channelmanager_with_description_hash( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, - Some(10_000), description_hash, 3600, None, + nodes[1].node, Some(10_000), description_hash, 3600, None, ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); @@ -982,8 +854,7 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let payment_hash = PaymentHash([0; 32]); let invoice = create_invoice_from_channelmanager_with_payment_hash( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, - Some(10_000), "test".to_string(), 3600, payment_hash, None, + nodes[1].node, Some(10_000), "test".to_string(), 3600, payment_hash, None, ).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); @@ -1273,8 +1144,7 @@ mod test { mut chan_ids_to_match: HashSet ) { let invoice = create_invoice_from_channelmanager( - invoice_node.node, invoice_node.keys_manager, invoice_node.logger, - invoice_amt, "test".to_string(), 3600, None, + invoice_node.node, invoice_amt, "test".to_string(), 3600, None, ).unwrap(); let hints = invoice.private_routes(); @@ -1911,8 +1781,8 @@ mod test { let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let result = create_invoice_from_channelmanager( - nodes[1].node, nodes[1].keys_manager, nodes[1].logger, - Some(10_000), "Some description".into(), 3600, Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), + nodes[1].node, Some(10_000), "Some description".into(), 3600, + Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), ); match result { Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)) => {}, From ce0e182ea5062ca2d10a8191288797585bdd9e12 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 6 Nov 2024 16:49:02 -0600 Subject: [PATCH 07/10] Use ChannelManager::create_bolt11_invoice in tests The utility methods in in invoice_utils will be removed or deprecated in an upcoming commit. --- lightning/src/ln/invoice_utils.rs | 103 ++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 01767ab05a3..be1b430dd67 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -709,7 +709,7 @@ mod test { use crate::sign::PhantomKeysManager; use crate::events::{MessageSendEvent, MessageSendEventsProvider}; use crate::types::payment::{PaymentHash, PaymentPreimage}; - use crate::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry}; + use crate::ln::channelmanager::{Bolt11InvoiceParameters, PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::ChannelMessageHandler; use crate::routing::router::{PaymentParameters, RouteParameters}; @@ -752,10 +752,18 @@ mod test { let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); + + let description = Bolt11InvoiceDescription::Direct( + Description::new("test".to_string()).unwrap() + ); let non_default_invoice_expiry_secs = 4200; - let invoice = create_invoice_from_channelmanager( - nodes[1].node, Some(10_000), "test".to_string(), non_default_invoice_expiry_secs, None, - ).unwrap(); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(non_default_invoice_expiry_secs), + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_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); @@ -803,10 +811,17 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let custom_min_final_cltv_expiry_delta = Some(50); - let invoice = create_invoice_from_channelmanager( - nodes[1].node, Some(10_000), "".into(), 3600, - if with_custom_delta { custom_min_final_cltv_expiry_delta } else { None }, - ).unwrap(); + let description = Bolt11InvoiceDescription::Direct(Description::empty()); + let min_final_cltv_expiry_delta = + if with_custom_delta { custom_min_final_cltv_expiry_delta } else { None }; + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + min_final_cltv_expiry_delta, + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).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); } @@ -823,11 +838,17 @@ mod test { 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 = create_invoice_from_channelmanager( - nodes[1].node, Some(10_000), "".into(), 3600, custom_min_final_cltv_expiry_delta, - ).unwrap(); + let custom_min_final_cltv_expiry_delta = Some(21); + let description = Bolt11InvoiceDescription::Direct(Description::empty()); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + min_final_cltv_expiry_delta: custom_min_final_cltv_expiry_delta, + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); } @@ -837,10 +858,17 @@ mod test { 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 description_hash = Sha256(Hash::hash("Testing description_hash".as_bytes())); - let invoice = create_invoice_from_channelmanager_with_description_hash( - nodes[1].node, Some(10_000), description_hash, 3600, None, - ).unwrap(); + + let description = Bolt11InvoiceDescription::Hash( + Sha256(Hash::hash("Testing description_hash".as_bytes())) + ); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Hash(&Sha256(Sha256::hash("Testing description_hash".as_bytes())))); @@ -852,10 +880,19 @@ mod test { 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 payment_hash = PaymentHash([0; 32]); - let invoice = create_invoice_from_channelmanager_with_payment_hash( - nodes[1].node, Some(10_000), "test".to_string(), 3600, payment_hash, None, - ).unwrap(); + let description = Bolt11InvoiceDescription::Direct( + Description::new("test".to_string()).unwrap() + ); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + payment_hash: Some(payment_hash), + ..Default::default() + }; + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); assert_eq!(invoice.description(), Bolt11InvoiceDescriptionRef::Direct(&Description::new("test".to_string()).unwrap())); @@ -1143,9 +1180,16 @@ mod test { invoice_node: &Node<'a, 'b, 'c>, mut chan_ids_to_match: HashSet ) { - let invoice = create_invoice_from_channelmanager( - invoice_node.node, invoice_amt, "test".to_string(), 3600, None, - ).unwrap(); + let description = Bolt11InvoiceDescription::Direct( + Description::new("test".to_string()).unwrap() + ); + let invoice_params = Bolt11InvoiceParameters { + amount_msats: invoice_amt, + description, + invoice_expiry_delta_secs: Some(3600), + ..Default::default() + }; + let invoice = invoice_node.node.create_bolt11_invoice(invoice_params).unwrap(); let hints = invoice.private_routes(); for hint in hints { @@ -1780,11 +1824,18 @@ mod test { 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 = create_invoice_from_channelmanager( - nodes[1].node, Some(10_000), "Some description".into(), 3600, - Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), + + let description = Bolt11InvoiceDescription::Direct( + Description::new("Some description".to_string()).unwrap() ); - match result { + let invoice_params = Bolt11InvoiceParameters { + amount_msats: Some(10_000), + description, + invoice_expiry_delta_secs: Some(3600), + min_final_cltv_expiry_delta: Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), + ..Default::default() + }; + match nodes[1].node.create_bolt11_invoice(invoice_params) { Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)) => {}, _ => panic!(), } From b6ceafb4f6f8a1957d27d788e9f811e39cd4e5b1 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 6 Nov 2024 16:53:07 -0600 Subject: [PATCH 08/10] Deprecate functions in invoice_utils module ChannelManager::create_bolt11_invoice is a simpler and more flexible way of creating a BOLT11 invoice, so deprecate the corresponding functions in the invoice_utils module. --- lightning/src/ln/invoice_utils.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index be1b430dd67..a39222c3fcb 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -313,6 +313,7 @@ fn rotate_through_iterators>(mut vecs: Vec) -> impl }) } +#[deprecated(note = "Use ChannelManager::create_bolt11_invoice instead.")] /// Utility to construct an invoice. Generally, unless you want to do something like a custom /// cltv_expiry, this is what you should be using to create an invoice. The reason being, this /// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user @@ -354,6 +355,7 @@ where channelmanager.create_bolt11_invoice(params) } +#[deprecated(note = "Use ChannelManager::create_bolt11_invoice instead.")] /// Utility to construct an invoice. Generally, unless you want to do something like a custom /// cltv_expiry, this is what you should be using to create an invoice. The reason being, this /// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user @@ -396,6 +398,7 @@ where channelmanager.create_bolt11_invoice(params) } +#[deprecated(note = "Use ChannelManager::create_bolt11_invoice instead.")] /// See [`create_invoice_from_channelmanager`]. /// /// This version allows for providing custom [`PaymentHash`] and description hash for the invoice. @@ -429,6 +432,7 @@ where channelmanager.create_bolt11_invoice(params) } +#[deprecated(note = "Use ChannelManager::create_bolt11_invoice instead.")] /// See [`create_invoice_from_channelmanager`]. /// /// This version allows for providing a custom [`PaymentHash`] for the invoice. From 2a4a470c988146c1f7a8f3aad7d7e4029d736984 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 29 Oct 2024 09:58:37 -0500 Subject: [PATCH 09/10] Test new behavior in create_bolt11_invoice Bolt11InvoiceParameters allows for setting currency and duration_since_epoch. If currency is not set, test that the one corresponding to ChannelManager's chain hash is usd. If duration_since_epoch, is not set then highest seen timestamp is used in non-std compilations. --- lightning/src/ln/invoice_utils.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index a39222c3fcb..07e108dab06 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -710,6 +710,7 @@ mod test { use lightning_invoice::{Currency, Description, Bolt11InvoiceDescriptionRef, SignOrCreationError, CreationError}; use bitcoin::hashes::{Hash, sha256}; use bitcoin::hashes::sha256::Hash as Sha256; + use bitcoin::network::Network; use crate::sign::PhantomKeysManager; use crate::events::{MessageSendEvent, MessageSendEventsProvider}; use crate::types::payment::{PaymentHash, PaymentPreimage}; @@ -903,6 +904,36 @@ mod test { assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap()); } + #[cfg(not(feature = "std"))] + #[test] + fn creates_invoice_using_highest_seen_timestamp() { + 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 invoice_params = Bolt11InvoiceParameters::default(); + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); + let best_block = bitcoin::constants::genesis_block(Network::Testnet); + assert_eq!( + invoice.duration_since_epoch(), + Duration::from_secs(best_block.header.time.into()), + ); + } + + #[test] + fn creates_invoice_using_currency_inferred_from_chain_hash() { + 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 invoice_params = Bolt11InvoiceParameters::default(); + let invoice = nodes[1].node.create_bolt11_invoice(invoice_params).unwrap(); + assert_eq!(invoice.currency(), Currency::BitcoinTestnet); + assert_eq!(invoice.network(), Network::Testnet); + } + #[test] fn test_hints_has_only_public_confd_channels() { let chanmon_cfgs = create_chanmon_cfgs(2); From 7878801be79928e0309c079ea117d33a2521b557 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 6 Nov 2024 11:18:21 -0600 Subject: [PATCH 10/10] Update BOLT11 docs in ChannelManager Update ChannelManager docs to use create_bolt11_invoice and correct references to modules in the lightning-invoice crate that no longer exist. --- lightning/src/ln/channelmanager.rs | 42 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9631851eb73..f2c092286ea 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -1732,12 +1732,12 @@ where /// /// ## BOLT 11 Invoices /// -/// The [`lightning-invoice`] crate is useful for creating BOLT 11 invoices. Specifically, use the -/// functions in its `utils` module for constructing invoices that are compatible with -/// [`ChannelManager`]. These functions serve as a convenience for building invoices with the +/// The [`lightning-invoice`] crate is useful for creating BOLT 11 invoices. However, in order to +/// construct a [`Bolt11Invoice`] that is compatible with [`ChannelManager`], use +/// [`create_bolt11_invoice`]. This method serves as a convenience for building invoices with the /// [`PaymentHash`] and [`PaymentSecret`] returned from [`create_inbound_payment`]. To provide your -/// own [`PaymentHash`], use [`create_inbound_payment_for_hash`] or the corresponding functions in -/// the [`lightning-invoice`] `utils` module. +/// own [`PaymentHash`], override the appropriate [`Bolt11InvoiceParameters`], which is equivalent +/// to using [`create_inbound_payment_for_hash`]. /// /// [`ChannelManager`] generates an [`Event::PaymentClaimable`] once the full payment has been /// received. Call [`claim_funds`] to release the [`PaymentPreimage`], which in turn will result in @@ -1745,19 +1745,21 @@ where /// /// ``` /// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; -/// # use lightning::ln::channelmanager::AChannelManager; +/// # use lightning::ln::channelmanager::{AChannelManager, Bolt11InvoiceParameters}; /// # /// # fn example(channel_manager: T) { /// # let channel_manager = channel_manager.get_cm(); -/// // Or use utils::create_invoice_from_channelmanager -/// let known_payment_hash = match channel_manager.create_inbound_payment( -/// Some(10_000_000), 3600, None -/// ) { -/// Ok((payment_hash, _payment_secret)) => { -/// println!("Creating inbound payment {}", payment_hash); -/// payment_hash +/// let params = Bolt11InvoiceParameters { +/// amount_msats: Some(10_000_000), +/// invoice_expiry_delta_secs: Some(3600), +/// ..Default::default() +/// }; +/// let invoice = match channel_manager.create_bolt11_invoice(params) { +/// Ok(invoice) => { +/// println!("Creating invoice with payment hash {}", invoice.payment_hash()); +/// invoice /// }, -/// Err(()) => panic!("Error creating inbound payment"), +/// Err(e) => panic!("Error creating invoice: {}", e), /// }; /// /// // On the event processing thread @@ -1765,7 +1767,7 @@ where /// match event { /// Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose { /// PaymentPurpose::Bolt11InvoicePayment { payment_preimage: Some(payment_preimage), .. } => { -/// assert_eq!(payment_hash, known_payment_hash); +/// assert_eq!(payment_hash.0, invoice.payment_hash().as_ref()); /// println!("Claiming payment {}", payment_hash); /// channel_manager.claim_funds(payment_preimage); /// }, @@ -1773,7 +1775,7 @@ where /// println!("Unknown payment hash: {}", payment_hash); /// }, /// PaymentPurpose::SpontaneousPayment(payment_preimage) => { -/// assert_ne!(payment_hash, known_payment_hash); +/// assert_ne!(payment_hash.0, invoice.payment_hash().as_ref()); /// println!("Claiming spontaneous payment {}", payment_hash); /// channel_manager.claim_funds(payment_preimage); /// }, @@ -1781,7 +1783,7 @@ where /// # _ => {}, /// }, /// Event::PaymentClaimed { payment_hash, amount_msat, .. } => { -/// assert_eq!(payment_hash, known_payment_hash); +/// assert_eq!(payment_hash.0, invoice.payment_hash().as_ref()); /// println!("Claimed {} msats", amount_msat); /// }, /// // ... @@ -1792,8 +1794,8 @@ where /// # } /// ``` /// -/// For paying an invoice, [`lightning-invoice`] provides a `payment` module with convenience -/// functions for use with [`send_payment`]. +/// For paying an invoice, see the [`bolt11_payment`] module with convenience functions for use with +/// [`send_payment`]. /// /// ``` /// # use lightning::events::{Event, EventsProvider}; @@ -2127,8 +2129,10 @@ where /// [`list_recent_payments`]: Self::list_recent_payments /// [`abandon_payment`]: Self::abandon_payment /// [`lightning-invoice`]: https://docs.rs/lightning_invoice/latest/lightning_invoice +/// [`create_bolt11_invoice`]: Self::create_bolt11_invoice /// [`create_inbound_payment`]: Self::create_inbound_payment /// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash +/// [`bolt11_payment`]: crate::ln::bolt11_payment /// [`claim_funds`]: Self::claim_funds /// [`send_payment`]: Self::send_payment /// [`offers`]: crate::offers