Move events.rs into its own top-level module
[rust-lightning] / lightning / src / offers / refund.rs
index f800274c973341d0ed33fd9c89dffc8fcb475b19..cc0388c0241b5f77a125ab0dabe4da8427504e8f 100644 (file)
@@ -18,7 +18,7 @@
 //! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
 //! [`Offer`]: crate::offers::offer::Offer
 //!
-//! ```ignore
+//! ```
 //! extern crate bitcoin;
 //! extern crate core;
 //! extern crate lightning;
@@ -118,9 +118,9 @@ impl RefundBuilder {
                }
 
                let refund = RefundContents {
-                       payer: PayerContents(metadata), metadata: None, description, absolute_expiry: None,
-                       issuer: None, paths: None, chain: None, amount_msats,
-                       features: InvoiceRequestFeatures::empty(), quantity: None, payer_id, payer_note: None,
+                       payer: PayerContents(metadata), description, absolute_expiry: None, issuer: None,
+                       paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
+                       quantity: None, payer_id, payer_note: None,
                };
 
                Ok(RefundBuilder { refund })
@@ -216,7 +216,7 @@ impl RefundBuilder {
 ///
 /// [`Invoice`]: crate::offers::invoice::Invoice
 /// [`Offer`]: crate::offers::offer::Offer
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
 pub struct Refund {
        pub(super) bytes: Vec<u8>,
        pub(super) contents: RefundContents,
@@ -225,11 +225,10 @@ pub struct Refund {
 /// The contents of a [`Refund`], which may be shared with an [`Invoice`].
 ///
 /// [`Invoice`]: crate::offers::invoice::Invoice
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
 pub(super) struct RefundContents {
        payer: PayerContents,
        // offer fields
-       metadata: Option<Vec<u8>>,
        description: String,
        absolute_expiry: Option<Duration>,
        issuer: Option<String>,
@@ -318,12 +317,31 @@ impl Refund {
                self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
        }
 
+       /// Creates an [`Invoice`] for the refund with the given required fields and using the
+       /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
+       ///
+       /// See [`Refund::respond_with_no_std`] for further details where the aforementioned creation
+       /// time is used for the `created_at` parameter.
+       ///
+       /// [`Invoice`]: crate::offers::invoice::Invoice
+       /// [`Duration`]: core::time::Duration
+       #[cfg(feature = "std")]
+       pub fn respond_with(
+               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               signing_pubkey: PublicKey,
+       ) -> Result<InvoiceBuilder, SemanticError> {
+               let created_at = std::time::SystemTime::now()
+                       .duration_since(std::time::SystemTime::UNIX_EPOCH)
+                       .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
+
+               self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at)
+       }
+
        /// Creates an [`Invoice`] for the refund with the given required fields.
        ///
        /// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
-       /// calling this method in `std` builds. For `no-std` builds, a final [`Duration`] parameter
-       /// must be given, which is used to set [`Invoice::created_at`] since [`std::time::SystemTime`]
-       /// is not available.
+       /// `created_at`, which is used to set [`Invoice::created_at`]. Useful for `no-std` builds where
+       /// [`std::time::SystemTime`] is not available.
        ///
        /// The caller is expected to remember the preimage of `payment_hash` in order to
        /// claim a payment for the invoice.
@@ -332,27 +350,22 @@ impl Refund {
        /// offer, which does have a `signing_pubkey`.
        ///
        /// The `payment_paths` parameter is useful for maintaining the payment recipient's privacy. It
-       /// must contain one or more elements.
+       /// must contain one or more elements ordered from most-preferred to least-preferred, if there's
+       /// a preference. Note, however, that any privacy is lost if a public node id is used for
+       /// `signing_pubkey`.
        ///
        /// Errors if the request contains unknown required features.
        ///
        /// [`Invoice`]: crate::offers::invoice::Invoice
        /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
-       pub fn respond_with(
+       pub fn respond_with_no_std(
                &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
-               signing_pubkey: PublicKey,
-               #[cfg(any(test, not(feature = "std")))]
-               created_at: Duration
+               signing_pubkey: PublicKey, created_at: Duration
        ) -> Result<InvoiceBuilder, SemanticError> {
                if self.features().requires_unknown_bits() {
                        return Err(SemanticError::UnknownRequiredFeatures);
                }
 
-               #[cfg(all(not(test), feature = "std"))]
-               let created_at = std::time::SystemTime::now()
-                       .duration_since(std::time::SystemTime::UNIX_EPOCH)
-                       .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
-
                InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey)
        }
 
@@ -395,7 +408,7 @@ impl RefundContents {
 
                let offer = OfferTlvStreamRef {
                        chains: None,
-                       metadata: self.metadata.as_ref(),
+                       metadata: None,
                        currency: None,
                        amount: None,
                        description: Some(&self.description),
@@ -497,6 +510,10 @@ impl TryFrom<RefundTlvStream> for RefundContents {
                        Some(metadata) => PayerContents(metadata),
                };
 
+               if metadata.is_some() {
+                       return Err(SemanticError::UnexpectedMetadata);
+               }
+
                if chains.is_some() {
                        return Err(SemanticError::UnexpectedChain);
                }
@@ -539,10 +556,9 @@ impl TryFrom<RefundTlvStream> for RefundContents {
                        Some(payer_id) => payer_id,
                };
 
-               // TODO: Should metadata be included?
                Ok(RefundContents {
-                       payer, metadata, description, absolute_expiry, issuer, paths, chain, amount_msats,
-                       features, quantity, payer_id, payer_note,
+                       payer, description, absolute_expiry, issuer, paths, chain, amount_msats, features,
+                       quantity, payer_id, payer_note,
                })
        }
 }
@@ -949,6 +965,17 @@ mod tests {
                        panic!("error parsing refund: {:?}", e);
                }
 
+               let metadata = vec![42; 32];
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.1.metadata = Some(&metadata);
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedMetadata));
+                       },
+               }
+
                let chains = vec![ChainHash::using_genesis_block(Network::Testnet)];
                let mut tlv_stream = refund.as_tlv_stream();
                tlv_stream.1.chains = Some(&chains);