Skip to content

Commit

Permalink
RefundBuilder for c_bindings
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkczyz committed Feb 20, 2024
1 parent b11d6cf commit a24c79e
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 77 deletions.
84 changes: 53 additions & 31 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7586,23 +7586,7 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
}
} }

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<M, T, ES, NS, SP, F, R, L>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::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<DerivedMetadata, secp256k1::All>);

#[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.
///
Expand Down Expand Up @@ -7652,31 +7636,69 @@ 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<u64>
) -> Result<RefundBuilder<secp256k1::All>, 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,
)
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;

Ok(builder)
}
} }

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<M, T, ES, NS, SP, F, R, L>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::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<DerivedMetadata, secp256k1::All>);
#[cfg(not(c_bindings))]
create_refund_builder!(self, RefundBuilder<secp256k1::All>);

#[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
Expand Down
140 changes: 94 additions & 46 deletions lightning/src/offers/refund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>>,
}

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<secp256k1::All>>,
}

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.
///
Expand Down Expand Up @@ -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
Expand All @@ -173,7 +185,11 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn deriving_payer_id<ES: Deref>(
description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
secp_ctx: &'a Secp256k1<T>, amount_msats: u64, payment_id: PaymentId
#[cfg(not(c_bindings))]
secp_ctx: &'a Secp256k1<T>,
#[cfg(c_bindings)]
secp_ctx: &'a Secp256k1<secp256k1::All>,
amount_msats: u64, payment_id: PaymentId
) -> Result<Self, Bolt12SemanticError> where ES::Target: EntropySource {
if amount_msats > MAX_VALUE_MSAT {
return Err(Bolt12SemanticError::InvalidAmount);
Expand All @@ -197,44 +213,44 @@ 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
/// by private channels or if [`Refund::payer_id`] is not a public node id.
///
/// 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
Expand All @@ -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<Refund, Bolt12SemanticError> {
if self.refund.chain() == self.refund.implied_chain() {
self.refund.chain = None;
pub fn build(mut $self: $self_type) -> Result<Refund, Bolt12SemanticError> {
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`].
Expand Down

0 comments on commit a24c79e

Please sign in to comment.