diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 36767b790..3c1f74b77 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -212,6 +212,10 @@ interface SpontaneousPayment { [Throws=NodeError] PaymentId send_with_custom_tlvs(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters, sequence custom_tlvs); [Throws=NodeError] + PaymentId send_with_preimage(u64 amount_msat, PublicKey node_id, PaymentPreimage preimage, SendingParameters? sending_parameters); + [Throws=NodeError] + PaymentId send_with_preimage_and_custom_tlvs(u64 amount_msat, PublicKey node_id, sequence custom_tlvs, PaymentPreimage preimage, SendingParameters? sending_parameters); + [Throws=NodeError] void send_probes(u64 amount_msat, PublicKey node_id); }; @@ -446,6 +450,10 @@ dictionary PaymentDetails { u64 latest_update_timestamp; }; +dictionary PaymentPreimage { + bytes inner; +}; + dictionary SendingParameters { MaxTotalRoutingFeeLimit? max_total_routing_fee_msat; u32? max_total_cltv_expiry_delta; @@ -829,9 +837,6 @@ typedef string PaymentId; [Custom] typedef string PaymentHash; -[Custom] -typedef string PaymentPreimage; - [Custom] typedef string PaymentSecret; diff --git a/src/balance.rs b/src/balance.rs index b5e2f5eb7..f373669ec 100644 --- a/src/balance.rs +++ b/src/balance.rs @@ -5,6 +5,8 @@ // http://opensource.org/licenses/MIT>, at your option. You may not use this file except in // accordance with one or both of these licenses. +use crate::ffi::maybe_wrap; +use crate::payment::PaymentPreimage; use crate::sweep::value_from_descriptor; use lightning::chain::channelmonitor::Balance as LdkBalance; @@ -12,7 +14,7 @@ use lightning::chain::channelmonitor::BalanceSource; use lightning::ln::types::ChannelId; use lightning::util::sweep::{OutputSpendStatus, TrackedSpendableOutput}; -use lightning_types::payment::{PaymentHash, PaymentPreimage}; +use lightning_types::payment::PaymentHash; use bitcoin::secp256k1::PublicKey; use bitcoin::{BlockHash, Txid}; @@ -263,7 +265,7 @@ impl LightningBalance { amount_satoshis, timeout_height, payment_hash, - payment_preimage, + payment_preimage: maybe_wrap(payment_preimage), }, LdkBalance::MaybeTimeoutClaimableHTLC { amount_satoshis, diff --git a/src/event.rs b/src/event.rs index 22848bec1..589f5a19e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -5,6 +5,7 @@ // http://opensource.org/licenses/MIT>, at your option. You may not use this file except in // accordance with one or both of these licenses. +use crate::ffi::maybe_wrap; use crate::types::{CustomTlvRecord, DynStore, PaymentStore, Sweeper, Wallet}; use crate::{ @@ -22,6 +23,7 @@ use crate::logger::Logger; use crate::payment::store::{ PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus, }; +use crate::payment::PaymentPreimage; use crate::io::{ EVENT_QUEUE_PERSISTENCE_KEY, EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE, @@ -39,7 +41,7 @@ use lightning::routing::gossip::NodeId; use lightning::util::errors::APIError; use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer}; -use lightning_types::payment::{PaymentHash, PaymentPreimage}; +use lightning_types::payment::PaymentHash; use lightning_liquidity::lsps2::utils::compute_opening_fee; @@ -740,7 +742,7 @@ where let quantity = payment_context.invoice_request.quantity; let kind = PaymentKind::Bolt12Offer { hash: Some(payment_hash), - preimage: payment_preimage, + preimage: payment_preimage.map(|preimage| maybe_wrap(preimage)), secret: Some(payment_secret), offer_id, payer_note, @@ -785,7 +787,7 @@ where // Since it's spontaneous, we insert it now into our store. let kind = PaymentKind::Spontaneous { hash: payment_hash, - preimage: Some(preimage), + preimage: Some(maybe_wrap(preimage)), }; let payment = PaymentDetails::new( @@ -871,7 +873,7 @@ where payment_secret, .. } => PaymentDetailsUpdate { - preimage: Some(payment_preimage), + preimage: Some(payment_preimage.map(|preimage| maybe_wrap(preimage))), secret: Some(Some(payment_secret)), amount_msat: Some(Some(amount_msat)), status: Some(PaymentStatus::Succeeded), @@ -880,7 +882,7 @@ where PaymentPurpose::Bolt12OfferPayment { payment_preimage, payment_secret, .. } => PaymentDetailsUpdate { - preimage: Some(payment_preimage), + preimage: Some(payment_preimage.map(|preimage| maybe_wrap(preimage))), secret: Some(Some(payment_secret)), amount_msat: Some(Some(amount_msat)), status: Some(PaymentStatus::Succeeded), @@ -891,14 +893,14 @@ where payment_secret, .. } => PaymentDetailsUpdate { - preimage: Some(payment_preimage), + preimage: Some(payment_preimage.map(|preimage| maybe_wrap(preimage))), secret: Some(Some(payment_secret)), amount_msat: Some(Some(amount_msat)), status: Some(PaymentStatus::Succeeded), ..PaymentDetailsUpdate::new(payment_id) }, PaymentPurpose::SpontaneousPayment(preimage) => PaymentDetailsUpdate { - preimage: Some(Some(preimage)), + preimage: Some(Some(maybe_wrap(preimage))), amount_msat: Some(Some(amount_msat)), status: Some(PaymentStatus::Succeeded), ..PaymentDetailsUpdate::new(payment_id) @@ -960,7 +962,7 @@ where let update = PaymentDetailsUpdate { hash: Some(Some(payment_hash)), - preimage: Some(Some(payment_preimage)), + preimage: Some(Some(maybe_wrap(payment_preimage))), fee_paid_msat: Some(fee_paid_msat), status: Some(PaymentStatus::Succeeded), ..PaymentDetailsUpdate::new(payment_id) @@ -992,7 +994,7 @@ where let event = Event::PaymentSuccessful { payment_id: Some(payment_id), payment_hash, - payment_preimage: Some(payment_preimage), + payment_preimage: Some(maybe_wrap(payment_preimage)), fee_paid_msat, }; diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 32464d044..db5b458d2 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -18,6 +18,14 @@ where wrapped_type.as_ref().as_ref() } +#[cfg(feature = "uniffi")] +pub fn maybe_extract(wrapped_type: T) -> Result +where + R: TryFrom, +{ + R::try_from(wrapped_type) +} + #[cfg(feature = "uniffi")] pub fn maybe_try_convert_enum(wrapped_type: &T) -> Result where @@ -27,10 +35,15 @@ where } #[cfg(feature = "uniffi")] -pub fn maybe_wrap(ldk_type: impl Into) -> std::sync::Arc { +pub fn maybe_wrap_arc(ldk_type: impl Into) -> std::sync::Arc { std::sync::Arc::new(ldk_type.into()) } +#[cfg(feature = "uniffi")] +pub fn maybe_wrap(ldk_type: impl Into) -> T { + ldk_type.into() +} + #[cfg(not(feature = "uniffi"))] pub fn maybe_deref(value: &T) -> &T { value @@ -41,7 +54,19 @@ pub fn maybe_try_convert_enum(value: &T) -> Result<&T, crate::error::Error> { Ok(value) } +#[cfg(not(feature = "uniffi"))] +pub fn maybe_wrap_arc(value: T) -> T { + value +} + #[cfg(not(feature = "uniffi"))] pub fn maybe_wrap(value: T) -> T { value } + +#[cfg(not(feature = "uniffi"))] +pub fn maybe_extract(wrapped_type: T) -> Result +where +{ + Ok(wrapped_type) +} diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 984e4da8f..c796618f5 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -22,14 +22,18 @@ pub use crate::payment::store::{ }; pub use crate::payment::{MaxTotalRoutingFeeLimit, QrPaymentResult, SendingParameters}; +use bitcoin::io::Read; pub use lightning::chain::channelmonitor::BalanceSource; pub use lightning::events::{ClosureReason, PaymentFailureReason}; +use lightning::ln::msgs::DecodeError; pub use lightning::ln::types::ChannelId; pub use lightning::offers::offer::OfferId; pub use lightning::routing::gossip::{NodeAlias, NodeId, RoutingFees}; pub use lightning::util::string::UntrustedString; -pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; +pub use lightning_types::payment::{ + PaymentHash, PaymentPreimage as LdkPaymentPreimage, PaymentSecret, +}; pub use lightning_invoice::{Description, SignedRawBolt11Invoice}; @@ -58,7 +62,7 @@ use lightning::ln::channelmanager::PaymentId; use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice; use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer}; use lightning::offers::refund::Refund as LdkRefund; -use lightning::util::ser::Writeable; +use lightning::util::ser::{Readable, Writeable}; use lightning_invoice::{Bolt11Invoice as LdkBolt11Invoice, Bolt11InvoiceDescriptionRef}; use std::convert::TryInto; @@ -665,21 +669,64 @@ impl UniffiCustomTypeConverter for PaymentHash { } } -impl UniffiCustomTypeConverter for PaymentPreimage { - type Builtin = String; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PaymentPreimage { + pub(crate) inner: Vec, +} - fn into_custom(val: Self::Builtin) -> uniffi::Result { - if let Some(bytes_vec) = hex_utils::to_vec(&val) { - let bytes_res = bytes_vec.try_into(); - if let Ok(bytes) = bytes_res { - return Ok(PaymentPreimage(bytes)); +impl From for PaymentPreimage { + fn from(ldk_value: LdkPaymentPreimage) -> Self { + PaymentPreimage { inner: ldk_value.0.to_vec() } + } +} + +impl TryFrom for LdkPaymentPreimage { + type Error = Error; + + fn try_from(preimage: PaymentPreimage) -> Result { + if preimage.inner.len() != 32 { + return Err(Error::InvalidPaymentPreimage); + } + + let mut array = [0u8; 32]; + array.copy_from_slice(&preimage.inner); + Ok(LdkPaymentPreimage(array)) + } +} + +impl FromStr for PaymentPreimage { + type Err = Error; + + fn from_str(s: &str) -> Result { + if let Some(bytes) = hex_utils::to_vec(s) { + if let Ok(array) = bytes.try_into() { + return Ok(Self::from(LdkPaymentPreimage(array))); } } - Err(Error::InvalidPaymentPreimage.into()) + + Err(Error::InvalidPaymentPreimage) } +} - fn from_custom(obj: Self) -> Self::Builtin { - hex_utils::to_string(&obj.0) +impl Writeable for PaymentPreimage { + fn write( + &self, writer: &mut W, + ) -> Result<(), lightning::io::Error> { + let ldk_preimage = LdkPaymentPreimage::try_from(self.clone()).map_err(|_| { + lightning::io::Error::new( + lightning::io::ErrorKind::InvalidData, + "Invalid payment preimage", + ) + })?; + + ldk_preimage.0.write(writer) + } +} + +impl Readable for PaymentPreimage { + fn read(r: &mut R) -> Result { + let buf: [u8; 32] = Readable::read(r)?; + Ok(PaymentPreimage { inner: buf.to_vec() }) } } diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 817a428bd..3063f6064 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -13,13 +13,14 @@ use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; use crate::connection::ConnectionManager; use crate::data_store::DataStoreUpdateResult; use crate::error::Error; -use crate::ffi::{maybe_deref, maybe_try_convert_enum, maybe_wrap}; +use crate::ffi::{maybe_deref, maybe_extract, maybe_try_convert_enum, maybe_wrap, maybe_wrap_arc}; use crate::liquidity::LiquiditySource; use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{ LSPFeeLimits, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus, }; +use crate::payment::PaymentPreimage; use crate::payment::SendingParameters; use crate::peer_store::{PeerInfo, PeerStore}; use crate::types::{ChannelManager, PaymentStore}; @@ -30,7 +31,7 @@ use lightning::ln::channelmanager::{ }; use lightning::routing::router::{PaymentParameters, RouteParameters}; -use lightning_types::payment::{PaymentHash, PaymentPreimage}; +use lightning_types::payment::{PaymentHash, PaymentPreimage as LdkPaymentPreimage}; use lightning_invoice::Bolt11Invoice as LdkBolt11Invoice; use lightning_invoice::Bolt11InvoiceDescription as LdkBolt11InvoiceDescription; @@ -348,7 +349,8 @@ impl Bolt11Payment { ) -> Result<(), Error> { let payment_id = PaymentId(payment_hash.0); - let expected_payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array()); + let inner: LdkPaymentPreimage = maybe_extract(preimage.clone())?; + let expected_payment_hash = PaymentHash(Sha256::hash(&inner.0).to_byte_array()); if expected_payment_hash != payment_hash { log_error!( @@ -379,7 +381,8 @@ impl Bolt11Payment { return Err(Error::InvalidPaymentHash); } - self.channel_manager.claim_funds(preimage); + let inner: LdkPaymentPreimage = maybe_extract(preimage)?; + self.channel_manager.claim_funds(inner); Ok(()) } @@ -438,7 +441,7 @@ impl Bolt11Payment { ) -> Result { let description = maybe_try_convert_enum(description)?; let invoice = self.receive_inner(Some(amount_msat), &description, expiry_secs, None)?; - Ok(maybe_wrap(invoice)) + Ok(maybe_wrap_arc(invoice)) } /// Returns a payable invoice that can be used to request a payment of the amount @@ -462,7 +465,7 @@ impl Bolt11Payment { let description = maybe_try_convert_enum(description)?; let invoice = self.receive_inner(Some(amount_msat), &description, expiry_secs, Some(payment_hash))?; - Ok(maybe_wrap(invoice)) + Ok(maybe_wrap_arc(invoice)) } /// Returns a payable invoice that can be used to request and receive a payment for which the @@ -474,7 +477,7 @@ impl Bolt11Payment { ) -> Result { let description = maybe_try_convert_enum(description)?; let invoice = self.receive_inner(None, &description, expiry_secs, None)?; - Ok(maybe_wrap(invoice)) + Ok(maybe_wrap_arc(invoice)) } /// Returns a payable invoice that can be used to request a payment for the given payment hash @@ -496,7 +499,7 @@ impl Bolt11Payment { ) -> Result { let description = maybe_try_convert_enum(description)?; let invoice = self.receive_inner(None, &description, expiry_secs, Some(payment_hash))?; - Ok(maybe_wrap(invoice)) + Ok(maybe_wrap_arc(invoice)) } pub(crate) fn receive_inner( @@ -539,9 +542,10 @@ impl Bolt11Payment { } else { None }; + let kind = PaymentKind::Bolt11 { hash: payment_hash, - preimage, + preimage: preimage.map(|preimage| maybe_wrap(preimage)), secret: Some(payment_secret.clone()), }; let payment = PaymentDetails::new( @@ -579,7 +583,7 @@ impl Bolt11Payment { max_total_lsp_fee_limit_msat, None, )?; - Ok(maybe_wrap(invoice)) + Ok(maybe_wrap_arc(invoice)) } /// Returns a payable invoice that can be used to request a variable amount payment (also known @@ -605,7 +609,7 @@ impl Bolt11Payment { None, max_proportional_lsp_fee_limit_ppm_msat, )?; - Ok(maybe_wrap(invoice)) + Ok(maybe_wrap_arc(invoice)) } fn receive_via_jit_channel_inner( @@ -677,7 +681,7 @@ impl Bolt11Payment { self.channel_manager.get_payment_preimage(payment_hash, payment_secret.clone()).ok(); let kind = PaymentKind::Bolt11Jit { hash: payment_hash, - preimage, + preimage: preimage.map(|preimage| maybe_wrap(preimage)), secret: Some(payment_secret.clone()), counterparty_skimmed_fee_msat: None, lsp_fee_limits, diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index b9efa3241..cecdcdec1 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -11,7 +11,7 @@ use crate::config::LDK_PAYMENT_RETRY_TIMEOUT; use crate::error::Error; -use crate::ffi::{maybe_deref, maybe_wrap}; +use crate::ffi::{maybe_deref, maybe_wrap_arc}; use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; use crate::types::{ChannelManager, PaymentStore}; @@ -311,7 +311,7 @@ impl Bolt12Payment { &self, amount_msat: u64, description: &str, expiry_secs: Option, quantity: Option, ) -> Result { let offer = self.receive_inner(amount_msat, description, expiry_secs, quantity)?; - Ok(maybe_wrap(offer)) + Ok(maybe_wrap_arc(offer)) } /// Returns a payable offer that can be used to request and receive a payment for which the @@ -335,7 +335,7 @@ impl Bolt12Payment { Error::OfferCreationFailed })?; - Ok(maybe_wrap(offer)) + Ok(maybe_wrap_arc(offer)) } /// Requests a refund payment for the given [`Refund`]. @@ -374,7 +374,7 @@ impl Bolt12Payment { self.payment_store.insert(payment)?; - Ok(maybe_wrap(invoice)) + Ok(maybe_wrap_arc(invoice)) } /// Returns a [`Refund`] object that can be used to offer a refund payment of the amount given. @@ -441,6 +441,6 @@ impl Bolt12Payment { self.payment_store.insert(payment)?; - Ok(maybe_wrap(refund)) + Ok(maybe_wrap_arc(refund)) } } diff --git a/src/payment/mod.rs b/src/payment/mod.rs index b031e37fd..7c5aa3858 100644 --- a/src/payment/mod.rs +++ b/src/payment/mod.rs @@ -23,6 +23,11 @@ pub use store::{ }; pub use unified_qr::{QrPaymentResult, UnifiedQrPayment}; +#[cfg(not(feature = "uniffi"))] +pub(crate) type PaymentPreimage = lightning::types::payment::PaymentPreimage; +#[cfg(feature = "uniffi")] +pub(crate) type PaymentPreimage = crate::ffi::PaymentPreimage; + /// Represents information used to send a payment. #[derive(Clone, Debug, PartialEq)] pub struct SendingParameters { diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 1508b6cd8..c08a5792d 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -9,16 +9,17 @@ use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; use crate::error::Error; +use crate::ffi::{maybe_extract, maybe_wrap}; use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; -use crate::payment::SendingParameters; +use crate::payment::{PaymentPreimage, SendingParameters}; use crate::types::{ChannelManager, CustomTlvRecord, KeysManager, PaymentStore}; use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure}; use lightning::routing::router::{PaymentParameters, RouteParameters}; use lightning::sign::EntropySource; -use lightning_types::payment::{PaymentHash, PaymentPreimage}; +use lightning_types::payment::{PaymentHash, PaymentPreimage as LdkPaymentPreimage}; use bitcoin::secp256k1::PublicKey; @@ -57,7 +58,7 @@ impl SpontaneousPayment { pub fn send( &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, ) -> Result { - self.send_inner(amount_msat, node_id, sending_parameters, None) + self.send_inner(amount_msat, node_id, sending_parameters, None, None) } /// Send a spontaneous payment including a list of custom TLVs. @@ -65,19 +66,39 @@ impl SpontaneousPayment { &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, custom_tlvs: Vec, ) -> Result { - self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs)) + self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs), None) + } + + /// Send a spontaneous payment with custom preimage + pub fn send_with_preimage( + &self, amount_msat: u64, node_id: PublicKey, preimage: PaymentPreimage, + sending_parameters: Option, + ) -> Result { + self.send_inner(amount_msat, node_id, sending_parameters, None, Some(preimage)) + } + + /// Send a spontaneous payment with custom preimage including a list of custom TLVs. + pub fn send_with_preimage_and_custom_tlvs( + &self, amount_msat: u64, node_id: PublicKey, custom_tlvs: Vec, + preimage: PaymentPreimage, sending_parameters: Option, + ) -> Result { + self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs), Some(preimage)) } fn send_inner( &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, - custom_tlvs: Option>, + custom_tlvs: Option>, preimage: Option, ) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { return Err(Error::NotRunning); } - let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes()); + let payment_preimage = if let Some(payment_preimage) = preimage { + maybe_extract(payment_preimage)? + } else { + LdkPaymentPreimage(self.keys_manager.get_secure_random_bytes()) + }; let payment_hash = PaymentHash::from(payment_preimage); let payment_id = PaymentId(payment_hash.0); @@ -132,7 +153,7 @@ impl SpontaneousPayment { let kind = PaymentKind::Spontaneous { hash: payment_hash, - preimage: Some(payment_preimage), + preimage: Some(maybe_wrap(payment_preimage)), }; let payment = PaymentDetails::new( payment_id, @@ -154,7 +175,7 @@ impl SpontaneousPayment { _ => { let kind = PaymentKind::Spontaneous { hash: payment_hash, - preimage: Some(payment_preimage), + preimage: Some(maybe_wrap(payment_preimage)), }; let payment = PaymentDetails::new( payment_id, diff --git a/src/payment/store.rs b/src/payment/store.rs index 75b2b1b2a..1e50a9591 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -15,7 +15,7 @@ use lightning::{ impl_writeable_tlv_based_enum, write_tlv_fields, }; -use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning_types::payment::{PaymentHash, PaymentSecret}; use bitcoin::{BlockHash, Txid}; @@ -23,6 +23,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::data_store::{StorableObject, StorableObjectId, StorableObjectUpdate}; use crate::hex_utils; +use crate::payment::PaymentPreimage; /// Represents a payment. #[derive(Clone, Debug, PartialEq, Eq)] @@ -221,40 +222,40 @@ impl StorableObject for PaymentDetails { }, } } - if let Some(preimage_opt) = update.preimage { + if let Some(preimage_opt) = &update.preimage { match self.kind { PaymentKind::Bolt11 { ref mut preimage, .. } => { - update_if_necessary!(*preimage, preimage_opt) + update_if_necessary!(*preimage, preimage_opt.clone()) }, PaymentKind::Bolt11Jit { ref mut preimage, .. } => { - update_if_necessary!(*preimage, preimage_opt) + update_if_necessary!(*preimage, preimage_opt.clone()) }, PaymentKind::Bolt12Offer { ref mut preimage, .. } => { - update_if_necessary!(*preimage, preimage_opt) + update_if_necessary!(*preimage, preimage_opt.clone()) }, PaymentKind::Bolt12Refund { ref mut preimage, .. } => { - update_if_necessary!(*preimage, preimage_opt) + update_if_necessary!(*preimage, preimage_opt.clone()) }, PaymentKind::Spontaneous { ref mut preimage, .. } => { - update_if_necessary!(*preimage, preimage_opt) + update_if_necessary!(*preimage, preimage_opt.clone()) }, _ => {}, } } - if let Some(secret_opt) = update.secret { + if let Some(secret_opt) = &update.secret { match self.kind { PaymentKind::Bolt11 { ref mut secret, .. } => { - update_if_necessary!(*secret, secret_opt) + update_if_necessary!(*secret, *secret_opt) }, PaymentKind::Bolt11Jit { ref mut secret, .. } => { - update_if_necessary!(*secret, secret_opt) + update_if_necessary!(*secret, *secret_opt) }, PaymentKind::Bolt12Offer { ref mut secret, .. } => { - update_if_necessary!(*secret, secret_opt) + update_if_necessary!(*secret, *secret_opt) }, PaymentKind::Bolt12Refund { ref mut secret, .. } => { - update_if_necessary!(*secret, secret_opt) + update_if_necessary!(*secret, *secret_opt) }, _ => {}, } @@ -563,13 +564,17 @@ impl PaymentDetailsUpdate { impl From<&PaymentDetails> for PaymentDetailsUpdate { fn from(value: &PaymentDetails) -> Self { - let (hash, preimage, secret) = match value.kind { + let (hash, preimage, secret) = match &value.kind { PaymentKind::Bolt11 { hash, preimage, secret, .. } => (Some(hash), preimage, secret), PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => (Some(hash), preimage, secret), - PaymentKind::Bolt12Offer { hash, preimage, secret, .. } => (hash, preimage, secret), - PaymentKind::Bolt12Refund { hash, preimage, secret, .. } => (hash, preimage, secret), - PaymentKind::Spontaneous { hash, preimage, .. } => (Some(hash), preimage, None), - _ => (None, None, None), + PaymentKind::Bolt12Offer { hash, preimage, secret, .. } => { + (hash.as_ref(), preimage, secret) + }, + PaymentKind::Bolt12Refund { hash, preimage, secret, .. } => { + (hash.as_ref(), preimage, secret) + }, + PaymentKind::Spontaneous { hash, preimage, .. } => (Some(hash), preimage, &None), + _ => (None, &None, &None), }; let confirmation_status = match value.kind { @@ -586,9 +591,9 @@ impl From<&PaymentDetails> for PaymentDetailsUpdate { Self { id: value.id, - hash: Some(hash), - preimage: Some(preimage), - secret: Some(secret), + hash: Some(hash.copied()), + preimage: Some(preimage.clone()), + secret: Some(*secret), amount_msat: Some(value.amount_msat), fee_paid_msat: Some(value.fee_paid_msat), counterparty_skimmed_fee_msat, @@ -608,8 +613,10 @@ impl StorableObjectUpdate for PaymentDetailsUpdate { #[cfg(test)] mod tests { use super::*; + use crate::ffi::maybe_wrap; use bitcoin::io::Cursor; use lightning::util::ser::Readable; + use lightning_types::payment::PaymentPreimage as LdkPaymentPreimage; /// We refactored `PaymentDetails` to hold a payment id and moved some required fields into /// `PaymentKind`. Here, we keep the old layout available in order test de/ser compatibility. @@ -639,7 +646,7 @@ mod tests { // We refactored `PaymentDetails` to hold a payment id and moved some required fields into // `PaymentKind`. Here, we test compatibility with the old layout. let hash = PaymentHash([42u8; 32]); - let preimage = Some(PaymentPreimage([43u8; 32])); + let preimage = Some(LdkPaymentPreimage([43u8; 32])); let secret = Some(PaymentSecret([44u8; 32])); let amount_msat = Some(45_000_000); @@ -647,7 +654,7 @@ mod tests { { let old_bolt11_payment = OldPaymentDetails { hash, - preimage, + preimage: preimage.map(|preimage| maybe_wrap(preimage)), secret, amount_msat, direction: PaymentDirection::Inbound, @@ -672,7 +679,7 @@ mod tests { match bolt11_decoded.kind { PaymentKind::Bolt11 { hash: h, preimage: p, secret: s } => { assert_eq!(hash, h); - assert_eq!(preimage, p); + assert_eq!(preimage.map(|preimage| maybe_wrap(preimage)), p); assert_eq!(secret, s); }, _ => { @@ -690,7 +697,7 @@ mod tests { let old_bolt11_jit_payment = OldPaymentDetails { hash, - preimage, + preimage: preimage.map(|preimage| maybe_wrap(preimage)), secret, amount_msat, direction: PaymentDirection::Inbound, @@ -721,7 +728,7 @@ mod tests { lsp_fee_limits: l, } => { assert_eq!(hash, h); - assert_eq!(preimage, p); + assert_eq!(preimage.map(|preimage| maybe_wrap(preimage)), p); assert_eq!(secret, s); assert_eq!(None, c); assert_eq!(lsp_fee_limits, Some(l)); @@ -736,7 +743,7 @@ mod tests { { let old_spontaneous_payment = OldPaymentDetails { hash, - preimage, + preimage: preimage.map(|preimage| maybe_wrap(preimage)), secret: None, amount_msat, direction: PaymentDirection::Inbound, @@ -761,7 +768,7 @@ mod tests { match spontaneous_decoded.kind { PaymentKind::Spontaneous { hash: h, preimage: p } => { assert_eq!(hash, h); - assert_eq!(preimage, p); + assert_eq!(preimage.map(|preimage| maybe_wrap(preimage)), p); }, _ => { panic!("Unexpected kind!"); diff --git a/src/payment/unified_qr.rs b/src/payment/unified_qr.rs index af5ee1c7b..2739fc5e3 100644 --- a/src/payment/unified_qr.rs +++ b/src/payment/unified_qr.rs @@ -12,7 +12,7 @@ //! [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md //! [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md use crate::error::Error; -use crate::ffi::maybe_wrap; +use crate::ffi::maybe_wrap_arc; use crate::logger::{log_error, LdkLogger, Logger}; use crate::payment::{Bolt11Payment, Bolt12Payment, OnchainPayment}; use crate::Config; @@ -147,7 +147,7 @@ impl UnifiedQrPayment { uri.clone().require_network(self.config.network).map_err(|_| Error::InvalidNetwork)?; if let Some(offer) = uri_network_checked.extras.bolt12_offer { - let offer = maybe_wrap(offer); + let offer = maybe_wrap_arc(offer); match self.bolt12_payment.send(&offer, None, None) { Ok(payment_id) => return Ok(QrPaymentResult::Bolt12 { payment_id }), Err(e) => log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified QR code payment. Falling back to the BOLT11 invoice.", e), @@ -155,7 +155,7 @@ impl UnifiedQrPayment { } if let Some(invoice) = uri_network_checked.extras.bolt11_invoice { - let invoice = maybe_wrap(invoice); + let invoice = maybe_wrap_arc(invoice); match self.bolt11_invoice.send(&invoice, None) { Ok(payment_id) => return Ok(QrPaymentResult::Bolt11 { payment_id }), Err(e) => log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified QR code payment. Falling back to the on-chain transaction.", e), diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 3258df791..7a7a92350 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -783,7 +783,7 @@ pub(crate) fn do_channel_full_cycle( ); node_b .bolt11_payment() - .claim_for_hash(manual_payment_hash, claimable_amount_msat, manual_preimage) + .claim_for_hash(manual_payment_hash, claimable_amount_msat, manual_preimage.into()) .unwrap(); expect_payment_received_event!(node_b, claimable_amount_msat); expect_payment_successful_event!(node_a, Some(manual_payment_id), None); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index db48eca23..484da8e00 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -19,8 +19,8 @@ use common::{ use ldk_node::config::EsploraSyncConfig; use ldk_node::liquidity::LSPS2ServiceConfig; use ldk_node::payment::{ - ConfirmationStatus, PaymentDirection, PaymentKind, PaymentStatus, QrPaymentResult, - SendingParameters, + ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, + QrPaymentResult, SendingParameters, }; use ldk_node::{Builder, Event, NodeError}; @@ -29,8 +29,10 @@ use lightning::routing::gossip::{NodeAlias, NodeId}; use lightning::util::persist::KVStore; use lightning_invoice::{Bolt11InvoiceDescription, Description}; +use lightning_types::payment::PaymentPreimage; use bitcoin::address::NetworkUnchecked; +use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::hashes::Hash; use bitcoin::Address; use bitcoin::Amount; @@ -809,7 +811,7 @@ fn simple_bolt12_send_receive() { let node_a_payments = node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. })); assert_eq!(node_a_payments.len(), 1); - match node_a_payments.first().unwrap().kind { + match &node_a_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { hash, preimage, @@ -820,7 +822,7 @@ fn simple_bolt12_send_receive() { } => { assert!(hash.is_some()); assert!(preimage.is_some()); - assert_eq!(offer_id, offer.id()); + assert_eq!(offer_id, &offer.id()); assert_eq!(&expected_quantity, qty); assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0); //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 @@ -836,12 +838,12 @@ fn simple_bolt12_send_receive() { let node_b_payments = node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. })); assert_eq!(node_b_payments.len(), 1); - match node_b_payments.first().unwrap().kind { + match &node_b_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { hash, preimage, secret, offer_id, .. } => { assert!(hash.is_some()); assert!(preimage.is_some()); assert!(secret.is_some()); - assert_eq!(offer_id, offer.id()); + assert_eq!(offer_id, &offer.id()); }, _ => { panic!("Unexpected payment kind"); @@ -875,7 +877,7 @@ fn simple_bolt12_send_receive() { matches!(p.kind, PaymentKind::Bolt12Offer { .. }) && p.id == payment_id }); assert_eq!(node_a_payments.len(), 1); - let payment_hash = match node_a_payments.first().unwrap().kind { + let payment_hash = match &node_a_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { hash, preimage, @@ -886,7 +888,7 @@ fn simple_bolt12_send_receive() { } => { assert!(hash.is_some()); assert!(preimage.is_some()); - assert_eq!(offer_id, offer.id()); + assert_eq!(offer_id, &offer.id()); assert_eq!(&expected_quantity, qty); assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0); //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 @@ -905,12 +907,12 @@ fn simple_bolt12_send_receive() { matches!(p.kind, PaymentKind::Bolt12Offer { .. }) && p.id == node_b_payment_id }); assert_eq!(node_b_payments.len(), 1); - match node_b_payments.first().unwrap().kind { + match &node_b_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { hash, preimage, secret, offer_id, .. } => { assert!(hash.is_some()); assert!(preimage.is_some()); assert!(secret.is_some()); - assert_eq!(offer_id, offer.id()); + assert_eq!(offer_id, &offer.id()); }, _ => { panic!("Unexpected payment kind"); @@ -943,7 +945,7 @@ fn simple_bolt12_send_receive() { matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_b_payment_id }); assert_eq!(node_b_payments.len(), 1); - match node_b_payments.first().unwrap().kind { + match &node_b_payments.first().unwrap().kind { PaymentKind::Bolt12Refund { hash, preimage, @@ -969,7 +971,7 @@ fn simple_bolt12_send_receive() { matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_a_payment_id }); assert_eq!(node_a_payments.len(), 1); - match node_a_payments.first().unwrap().kind { + match &node_a_payments.first().unwrap().kind { PaymentKind::Bolt12Refund { hash, preimage, secret, .. } => { assert!(hash.is_some()); assert!(preimage.is_some()); @@ -1381,3 +1383,69 @@ fn facade_logging() { validate_log_entry(entry); } } + +#[test] +fn spontaneous_send_with_custom_preimage() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = TestChainSource::Esplora(&electrsd); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + + let address_a = node_a.onchain_payment().new_address().unwrap(); + let premine_sat = 1_000_000; + premine_and_distribute_funds( + &bitcoind.client, + &electrsd.client, + vec![address_a], + Amount::from_sat(premine_sat), + ); + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + open_channel(&node_a, &node_b, 500_000, true, &electrsd); + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6); + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + let seed = b"test_payment_preimage"; + let bytes: Sha256Hash = Sha256Hash::hash(seed); + let custom_bytes = bytes.to_byte_array(); + let custom_preimage = PaymentPreimage(custom_bytes); + + let amount_msat = 100_000; + let payment_id = node_a + .spontaneous_payment() + .send_with_preimage(amount_msat, node_b.node_id(), custom_preimage.clone().into(), None) + .unwrap(); + + // check payment status and verify stored preimage + expect_payment_successful_event!(node_a, Some(payment_id), None); + let details: PaymentDetails = + node_a.list_payments_with_filter(|p| p.id == payment_id).first().unwrap().clone(); + assert_eq!(details.status, PaymentStatus::Succeeded); + if let PaymentKind::Spontaneous { preimage: Some(pi), .. } = details.kind { + assert_eq!(pi, custom_preimage.into()); + } else { + panic!("Expected a spontaneous PaymentKind with a preimage"); + } + + // Verify receiver side (node_b) + expect_payment_received_event!(node_b, amount_msat); + let receiver_payments: Vec = node_b.list_payments_with_filter(|p| { + p.direction == PaymentDirection::Inbound + && matches!(p.kind, PaymentKind::Spontaneous { .. }) + }); + + assert_eq!(receiver_payments.len(), 1); + let receiver_details = &receiver_payments[0]; + assert_eq!(receiver_details.status, PaymentStatus::Succeeded); + assert_eq!(receiver_details.amount_msat, Some(amount_msat)); + assert_eq!(receiver_details.direction, PaymentDirection::Inbound); + + // Verify receiver also has the same preimage + if let PaymentKind::Spontaneous { preimage: Some(pi), .. } = &receiver_details.kind { + assert_eq!(pi, &custom_preimage.into()); + } else { + panic!("Expected receiver to have spontaneous PaymentKind with preimage"); + } +}