From d9ab2fa58177fc390891e0229e9db269e8d54f6e Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 23 Feb 2024 16:33:23 -0600 Subject: [PATCH] Add c_bindings version of OfferBuilder Use the macros introduced in the previous commit to define two builders for each type parameterization of OfferBuilder - OfferWithExplicitMetadataBuilder - OfferWithDerivedMetadataBuilder The difference between these and OfferBuilder is that these have methods that take `self` by mutable reference instead of by value and don't return anything instead returning the modified builder. This is required because bindings don't support move semantics nor impl blocks specific to a certain type parameterization. Because of this, the builder's contents must be cloned when building an Offer. Keeps OfferBuilder defined so that it can be used internally in ChannelManager::create_offer_builder even when compiled for c_bindings. --- lightning/src/ln/channelmanager.rs | 47 +++++++-- lightning/src/offers/invoice.rs | 12 ++- lightning/src/offers/invoice_request.rs | 10 +- lightning/src/offers/offer.rs | 128 ++++++++++++++++++++---- 4 files changed, 165 insertions(+), 32 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ec3e8ef6..d41909be 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -60,7 +60,7 @@ use crate::ln::wire::Encode; use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder}; use crate::offers::invoice_error::InvoiceError; use crate::offers::merkle::SignError; -use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder}; +use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; use crate::onion_message::messenger::{Destination, MessageRouter, PendingOnionMessage, new_pending_onion_message}; @@ -76,11 +76,16 @@ use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; #[cfg(not(c_bindings))] use { + crate::offers::offer::DerivedMetadata, crate::routing::router::DefaultRouter, crate::routing::gossip::NetworkGraph, crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}, crate::sign::KeysManager, }; +#[cfg(c_bindings)] +use { + crate::offers::offer::OfferWithDerivedMetadataBuilder, +}; use alloc::collections::{btree_map, BTreeMap}; @@ -7520,7 +7525,9 @@ where self.finish_close_channel(failure); } } +} +macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will /// not have an expiration unless otherwise set on the builder. @@ -7549,22 +7556,40 @@ where /// [`Offer`]: crate::offers::offer::Offer /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest pub fn create_offer_builder( - &self, description: String - ) -> 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; - - let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; + &$self, description: String + ) -> 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)?; let builder = OfferBuilder::deriving_signing_pubkey( description, node_id, expanded_key, entropy, secp_ctx ) - .chain_hash(self.chain_hash) + .chain_hash($self.chain_hash) .path(path); - Ok(builder) + Ok(builder.into()) } +} } + +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); /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index bb29c761..ebb23fb2 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1306,7 +1306,15 @@ mod tests { use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::InvoiceRequestTlvStreamRef; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self}; - use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity}; + use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity}; + #[cfg(not(c_bindings))] + use { + crate::offers::offer::OfferBuilder, + }; + #[cfg(c_bindings)] + use { + crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder, + }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::refund::RefundBuilder; @@ -1644,6 +1652,8 @@ mod tests { ], }; + #[cfg(c_bindings)] + use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 4dd85b35..ecdde2b6 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -941,7 +941,15 @@ mod tests { use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG}; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self}; - use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity}; + use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity}; + #[cfg(not(c_bindings))] + use { + crate::offers::offer::OfferBuilder, + }; + #[cfg(c_bindings)] + use { + crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder, + }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::test_utils::*; diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 2033efee..5b5b66e3 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -118,6 +118,34 @@ pub struct OfferBuilder<'a, M: MetadataStrategy, T: secp256k1::Signing> { secp_ctx: Option<&'a Secp256k1>, } +/// Builds an [`Offer`] for the "offer to be paid" flow. +/// +/// 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(c_bindings)] +pub struct OfferWithExplicitMetadataBuilder<'a> { + offer: OfferContents, + metadata_strategy: core::marker::PhantomData, + secp_ctx: Option<&'a Secp256k1>, +} + +/// Builds an [`Offer`] for the "offer to be paid" flow. +/// +/// 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(c_bindings)] +pub struct OfferWithDerivedMetadataBuilder<'a> { + offer: OfferContents, + metadata_strategy: core::marker::PhantomData, + secp_ctx: Option<&'a Secp256k1>, +} + /// Indicates how [`Offer::metadata`] may be set. /// /// This is not exported to bindings users as builder patterns don't map outside of move semantics. @@ -174,7 +202,7 @@ macro_rules! offer_explicit_metadata_builder_methods { ( } } } -macro_rules! offer_derived_metadata_builder_methods { () => { +macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => { /// Similar to [`OfferBuilder::new`] except, if [`OfferBuilder::path`] is called, the signing /// pubkey is derived from the given [`ExpandedKey`] and [`EntropySource`]. This provides /// recipient privacy by using a different signing pubkey for each offer. Otherwise, the @@ -188,7 +216,7 @@ macro_rules! offer_derived_metadata_builder_methods { () => { /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey pub fn deriving_signing_pubkey( description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, - secp_ctx: &'a Secp256k1 + secp_ctx: &'a Secp256k1<$secp_context> ) -> Self where ES::Target: EntropySource { let nonce = Nonce::from_entropy_source(entropy_source); let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None); @@ -206,7 +234,7 @@ macro_rules! offer_derived_metadata_builder_methods { () => { } } macro_rules! offer_builder_methods { ( - $self: ident, $self_type: ty, $return_type: ty, $return_value: expr + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? ) => { /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called, /// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported. @@ -224,7 +252,7 @@ macro_rules! offer_builder_methods { ( /// See [`Offer::chains`] on how this relates to the payment currency. /// /// Successive calls to this method will add another chain hash. - pub(crate) fn chain_hash(mut $self: $self_type, chain: ChainHash) -> $return_type { + pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> $return_type { let chains = $self.offer.chains.get_or_insert_with(Vec::new); if !chains.contains(&chain) { chains.push(chain); @@ -243,7 +271,7 @@ macro_rules! offer_builder_methods { ( /// Sets the [`Offer::amount`]. /// /// Successive calls to this method will override the previous setting. - pub(super) fn amount(mut $self: $self_type, amount: Amount) -> $return_type { + pub(super) fn amount($($self_mut)* $self: $self_type, amount: Amount) -> $return_type { $self.offer.amount = Some(amount); $return_value } @@ -252,7 +280,7 @@ macro_rules! offer_builder_methods { ( /// already passed is valid and can be checked for using [`Offer::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn absolute_expiry(mut $self: $self_type, absolute_expiry: Duration) -> $return_type { + pub fn absolute_expiry($($self_mut)* $self: $self_type, absolute_expiry: Duration) -> $return_type { $self.offer.absolute_expiry = Some(absolute_expiry); $return_value } @@ -260,7 +288,7 @@ macro_rules! offer_builder_methods { ( /// Sets the [`Offer::issuer`]. /// /// Successive calls to this method will override the previous setting. - pub fn issuer(mut $self: $self_type, issuer: String) -> $return_type { + pub fn issuer($($self_mut)* $self: $self_type, issuer: String) -> $return_type { $self.offer.issuer = Some(issuer); $return_value } @@ -270,7 +298,7 @@ macro_rules! offer_builder_methods { ( /// /// Successive calls to this method will add another blinded path. Caller is responsible for not /// adding duplicate paths. - pub fn path(mut $self: $self_type, path: BlindedPath) -> $return_type { + pub fn path($($self_mut)* $self: $self_type, path: BlindedPath) -> $return_type { $self.offer.paths.get_or_insert_with(Vec::new).push(path); $return_value } @@ -279,13 +307,13 @@ macro_rules! offer_builder_methods { ( /// [`Quantity::One`]. /// /// Successive calls to this method will override the previous setting. - pub fn supported_quantity(mut $self: $self_type, quantity: Quantity) -> $return_type { + pub fn supported_quantity($($self_mut)* $self: $self_type, quantity: Quantity) -> $return_type { $self.offer.supported_quantity = quantity; $return_value } /// Builds an [`Offer`] from the builder's settings. - pub fn build(mut $self: $self_type) -> Result { + pub fn build($($self_mut)* $self: $self_type) -> Result { match $self.offer.amount { Some(Amount::Bitcoin { amount_msats }) => { if amount_msats > MAX_VALUE_MSAT { @@ -305,7 +333,7 @@ macro_rules! offer_builder_methods { ( Ok($self.build_without_checks()) } - fn build_without_checks(mut $self: $self_type) -> Offer { + fn build_without_checks($($self_mut)* $self: $self_type) -> Offer { // Create the metadata for stateless verification of an InvoiceRequest. if let Some(mut metadata) = $self.offer.metadata.take() { if metadata.has_derivation_material() { @@ -333,34 +361,43 @@ macro_rules! offer_builder_methods { ( let mut bytes = Vec::new(); $self.offer.write(&mut bytes).unwrap(); - Offer { bytes, contents: $self.offer } + Offer { + bytes, + #[cfg(not(c_bindings))] + contents: $self.offer, + #[cfg(c_bindings)] + contents: $self.offer.clone() + } } } } #[cfg(test)] macro_rules! offer_builder_test_methods { ( - $self: ident, $self_type: ty, $return_type: ty, $return_value: expr + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? ) => { - fn features_unchecked(mut $self: $self_type, features: OfferFeatures) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + fn features_unchecked($($self_mut)* $self: $self_type, features: OfferFeatures) -> $return_type { $self.offer.features = features; $return_value } - pub(crate) fn clear_paths(mut $self: $self_type) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type { $self.offer.paths = None; $return_value } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn build_unchecked($self: $self_type) -> Offer { $self.build_without_checks() } } } impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { - offer_builder_methods!(self, Self, Self, self); + offer_builder_methods!(self, Self, Self, self, mut); #[cfg(test)] - offer_builder_test_methods!(self, Self, Self, self); + offer_builder_test_methods!(self, Self, Self, self, mut); } impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { @@ -368,7 +405,43 @@ impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { } impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { - offer_derived_metadata_builder_methods!(); + offer_derived_metadata_builder_methods!(T); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> OfferWithExplicitMetadataBuilder<'a> { + offer_explicit_metadata_builder_methods!(self, &mut Self, (), ()); + offer_builder_methods!(self, &mut Self, (), ()); +} + +#[cfg(all(c_bindings, test))] +impl<'a> OfferWithExplicitMetadataBuilder<'a> { + offer_explicit_metadata_builder_methods!(self, &mut Self, &mut Self, self); + offer_builder_methods!(self, &mut Self, &mut Self, self); + offer_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> OfferWithDerivedMetadataBuilder<'a> { + offer_derived_metadata_builder_methods!(secp256k1::All); + offer_builder_methods!(self, &mut Self, (), ()); +} + +#[cfg(all(c_bindings, test))] +impl<'a> OfferWithDerivedMetadataBuilder<'a> { + offer_derived_metadata_builder_methods!(secp256k1::All); + offer_builder_methods!(self, &mut Self, &mut Self, self); + offer_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(c_bindings)] +impl<'a> From> +for OfferWithDerivedMetadataBuilder<'a> { + fn from(builder: OfferBuilder<'a, DerivedMetadata, secp256k1::All>) -> Self { + let OfferBuilder { offer, metadata_strategy, secp_ctx } = builder; + + Self { offer, metadata_strategy, secp_ctx } + } } /// An `Offer` is a potentially long-lived proposal for payment of a good or service. @@ -936,7 +1009,15 @@ impl core::fmt::Display for Offer { #[cfg(test)] mod tests { - use super::{Amount, Offer, OfferBuilder, OfferTlvStreamRef, Quantity}; + use super::{Amount, Offer, OfferTlvStreamRef, Quantity}; + #[cfg(not(c_bindings))] + use { + super::OfferBuilder, + }; + #[cfg(c_bindings)] + use { + super::OfferWithExplicitMetadataBuilder as OfferBuilder, + }; use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; @@ -1065,6 +1146,8 @@ mod tests { let entropy = FixedEntropy {}; let secp_ctx = Secp256k1::new(); + #[cfg(c_bindings)] + use super::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) @@ -1121,6 +1204,8 @@ mod tests { ], }; + #[cfg(c_bindings)] + use super::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) @@ -1175,8 +1260,13 @@ mod tests { assert_eq!(tlv_stream.amount, Some(1000)); assert_eq!(tlv_stream.currency, None); + #[cfg(not(c_bindings))] let builder = OfferBuilder::new("foo".into(), pubkey(42)) .amount(currency_amount.clone()); + #[cfg(c_bindings)] + let mut builder = OfferBuilder::new("foo".into(), pubkey(42)); + #[cfg(c_bindings)] + builder.amount(currency_amount.clone()); let tlv_stream = builder.offer.as_tlv_stream(); assert_eq!(builder.offer.amount, Some(currency_amount.clone())); assert_eq!(tlv_stream.amount, Some(10)); -- 2.30.2