From a24c79ed25634e377c2f3645fd65f7390fe850f4 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 20 Feb 2024 17:42:44 -0600 Subject: [PATCH] RefundBuilder for c_bindings RefundBuilder is currently not exported to bindings because it uses move semantics and has impl blocks for specific type parameterizations. Define a macro that defines RefundBuilder methods such that c_bindings versions can be defined. Methods for the c_bindings version use the unit return type, except in tests where they use &mut Self. --- lightning/src/ln/channelmanager.rs | 84 ++++++++++------- lightning/src/offers/refund.rs | 140 +++++++++++++++++++---------- 2 files changed, 147 insertions(+), 77 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ed080da8138..9a36fe055d9 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -7586,23 +7586,7 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { } } } -impl ChannelManager -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - L::Target: Logger, -{ - #[cfg(not(c_bindings))] - create_offer_builder!(self, OfferBuilder); - - #[cfg(c_bindings)] - create_offer_builder!(self, OfferWithDerivedMetadataBuilder); - +macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. /// @@ -7652,24 +7636,40 @@ where /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments pub fn create_refund_builder( - &self, description: String, amount_msats: u64, absolute_expiry: Duration, + &$self, description: String, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option - ) -> Result, Bolt12SemanticError> { - let node_id = self.get_our_node_id(); - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; + ) -> Result<$builder, Bolt12SemanticError> { + let node_id = $self.get_our_node_id(); + let expanded_key = &$self.inbound_payment_key; + let entropy = &*$self.entropy_source; + let secp_ctx = &$self.secp_ctx; + + let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; + + #[cfg(not(c_bindings))] + let builder; + #[cfg(not(c_bindings))] { + builder = <$builder>::deriving_payer_id( + description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id + )? + .chain_hash($self.chain_hash) + .absolute_expiry(absolute_expiry) + .path(path); + } - let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; - let builder = RefundBuilder::deriving_payer_id( - description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id - )? - .chain_hash(self.chain_hash) - .absolute_expiry(absolute_expiry) - .path(path); + #[cfg(c_bindings)] + let mut builder; + #[cfg(c_bindings)] { + builder = <$builder>::deriving_payer_id( + description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id + )?; + builder.chain_hash($self.chain_hash); + builder.absolute_expiry(absolute_expiry); + builder.path(path); + } let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); - self.pending_outbound_payments + $self.pending_outbound_payments .add_new_awaiting_invoice( payment_id, expiration, retry_strategy, max_total_routing_fee_msat, ) @@ -7677,6 +7677,28 @@ where Ok(builder) } +} } + +impl ChannelManager +where + M::Target: chain::Watch<::EcdsaSigner>, + T::Target: BroadcasterInterface, + ES::Target: EntropySource, + NS::Target: NodeSigner, + SP::Target: SignerProvider, + F::Target: FeeEstimator, + R::Target: Router, + L::Target: Logger, +{ + #[cfg(not(c_bindings))] + create_offer_builder!(self, OfferBuilder); + #[cfg(not(c_bindings))] + create_refund_builder!(self, RefundBuilder); + + #[cfg(c_bindings)] + create_offer_builder!(self, OfferWithDerivedMetadataBuilder); + #[cfg(c_bindings)] + create_refund_builder!(self, RefundBuilder); /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and /// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index ba3ab1d1ef3..40745ffcad0 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -116,15 +116,25 @@ pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Refund ~~~~~"; /// /// See [module-level documentation] for usage. /// -/// This is not exported to bindings users as builder patterns don't map outside of move semantics. -/// /// [module-level documentation]: self +#[cfg(not(c_bindings))] pub struct RefundBuilder<'a, T: secp256k1::Signing> { refund: RefundContents, secp_ctx: Option<&'a Secp256k1>, } -impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { +/// Builds a [`Refund`] for the "offer for money" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct RefundBuilder<'a> { + refund: RefundContents, + secp_ctx: Option<&'a Secp256k1>, +} + +macro_rules! refund_without_secp256k1_builder_methods { () => { /// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to /// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey. /// @@ -155,9 +165,11 @@ impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { secp_ctx: None, }) } -} +} } -impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { +macro_rules! refund_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { /// Similar to [`RefundBuilder::new`] except, if [`RefundBuilder::path`] is called, the payer id /// is derived from the given [`ExpandedKey`] and nonce. This provides sender privacy by using a /// different payer id for each refund, assuming a different nonce is used. Otherwise, the @@ -173,7 +185,11 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey pub fn deriving_payer_id( description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, - secp_ctx: &'a Secp256k1, amount_msats: u64, payment_id: PaymentId + #[cfg(not(c_bindings))] + secp_ctx: &'a Secp256k1, + #[cfg(c_bindings)] + secp_ctx: &'a Secp256k1, + amount_msats: u64, payment_id: PaymentId ) -> Result where ES::Target: EntropySource { if amount_msats > MAX_VALUE_MSAT { return Err(Bolt12SemanticError::InvalidAmount); @@ -197,17 +213,17 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// already passed is valid and can be checked for using [`Refund::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self { - self.refund.absolute_expiry = Some(absolute_expiry); - self + pub fn absolute_expiry(mut $self: $self_type, absolute_expiry: Duration) -> $return_type { + $self.refund.absolute_expiry = Some(absolute_expiry); + $return_value } /// Sets the [`Refund::issuer`]. /// /// Successive calls to this method will override the previous setting. - pub fn issuer(mut self, issuer: String) -> Self { - self.refund.issuer = Some(issuer); - self + pub fn issuer(mut $self: $self_type, issuer: String) -> $return_type { + $self.refund.issuer = Some(issuer); + $return_value } /// Adds a blinded path to [`Refund::paths`]. Must include at least one path if only connected @@ -215,26 +231,26 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// /// Successive calls to this method will add another blinded path. Caller is responsible for not /// adding duplicate paths. - pub fn path(mut self, path: BlindedPath) -> Self { - self.refund.paths.get_or_insert_with(Vec::new).push(path); - self + pub fn path(mut $self: $self_type, path: BlindedPath) -> $return_type { + $self.refund.paths.get_or_insert_with(Vec::new).push(path); + $return_value } /// Sets the [`Refund::chain`] of the given [`Network`] for paying an invoice. If not /// called, [`Network::Bitcoin`] is assumed. /// /// Successive calls to this method will override the previous setting. - pub fn chain(self, network: Network) -> Self { - self.chain_hash(ChainHash::using_genesis_block(network)) + pub fn chain($self: $self_type, network: Network) -> $return_type { + $self.chain_hash(ChainHash::using_genesis_block(network)) } /// Sets the [`Refund::chain`] of the given [`ChainHash`] for paying an invoice. If not called, /// [`Network::Bitcoin`] is assumed. /// /// Successive calls to this method will override the previous setting. - pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self { - self.refund.chain = Some(chain); - self + pub(crate) fn chain_hash(mut $self: $self_type, chain: ChainHash) -> $return_type { + $self.refund.chain = Some(chain); + $return_value } /// Sets [`Refund::quantity`] of items. This is purely for informational purposes. It is useful @@ -246,66 +262,98 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity /// [`Offer`]: crate::offers::offer::Offer - pub fn quantity(mut self, quantity: u64) -> Self { - self.refund.quantity = Some(quantity); - self + pub fn quantity(mut $self: $self_type, quantity: u64) -> $return_type { + $self.refund.quantity = Some(quantity); + $return_value } /// Sets the [`Refund::payer_note`]. /// /// Successive calls to this method will override the previous setting. - pub fn payer_note(mut self, payer_note: String) -> Self { - self.refund.payer_note = Some(payer_note); - self + pub fn payer_note(mut $self: $self_type, payer_note: String) -> $return_type { + $self.refund.payer_note = Some(payer_note); + $return_value } /// Builds a [`Refund`] after checking for valid semantics. - pub fn build(mut self) -> Result { - if self.refund.chain() == self.refund.implied_chain() { - self.refund.chain = None; + pub fn build(mut $self: $self_type) -> Result { + if $self.refund.chain() == $self.refund.implied_chain() { + $self.refund.chain = None; } // Create the metadata for stateless verification of a Bolt12Invoice. - if self.refund.payer.0.has_derivation_material() { - let mut metadata = core::mem::take(&mut self.refund.payer.0); + if $self.refund.payer.0.has_derivation_material() { + let mut metadata = core::mem::take(&mut $self.refund.payer.0); - if self.refund.paths.is_none() { + if $self.refund.paths.is_none() { metadata = metadata.without_keys(); } - let mut tlv_stream = self.refund.as_tlv_stream(); + let mut tlv_stream = $self.refund.as_tlv_stream(); tlv_stream.0.metadata = None; if metadata.derives_payer_keys() { tlv_stream.2.payer_id = None; } - let (derived_metadata, keys) = metadata.derive_from(tlv_stream, self.secp_ctx); + let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); metadata = derived_metadata; if let Some(keys) = keys { - self.refund.payer_id = keys.public_key(); + $self.refund.payer_id = keys.public_key(); } - self.refund.payer.0 = metadata; + $self.refund.payer.0 = metadata; } let mut bytes = Vec::new(); - self.refund.write(&mut bytes).unwrap(); + $self.refund.write(&mut bytes).unwrap(); - Ok(Refund { bytes, contents: self.refund }) + #[cfg(not(c_bindings))] { + Ok(Refund { bytes, contents: $self.refund }) + } + #[cfg(c_bindings)] { + Ok(Refund { bytes, contents: $self.refund.clone() }) + } } -} +} } #[cfg(test)] -impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { - pub(crate) fn clear_paths(mut self) -> Self { - self.refund.paths = None; - self +macro_rules! refund_builder_test_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { + pub(crate) fn clear_paths(mut $self: $self_type) -> $return_type { + $self.refund.paths = None; + $return_value } - fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self { - self.refund.features = features; - self + fn features_unchecked(mut $self: $self_type, features: InvoiceRequestFeatures) -> $return_type { + $self.refund.features = features; + $return_value } +} } + +#[cfg(not(c_bindings))] +impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { + refund_without_secp256k1_builder_methods!(); +} + +#[cfg(not(c_bindings))] +impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { + refund_builder_methods!(self, Self, Self, self); + + #[cfg(test)] + refund_builder_test_methods!(self, Self, Self, self); +} + +#[cfg(c_bindings)] +impl<'a> RefundBuilder<'a> { + refund_without_secp256k1_builder_methods!(); + #[cfg(not(test))] + refund_builder_methods!(self, &mut Self, (), ()); + + #[cfg(test)] + refund_builder_methods!(self, &mut Self, &mut Self, self); + #[cfg(test)] + refund_builder_test_methods!(self, &mut Self, &mut Self, self); } /// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`].