From 3b9c31514464c1d11f564694b138352867c1c1b3 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 11 Mar 2024 13:33:55 +0100 Subject: [PATCH] WIP --- bindings/ldk_node.udl | 26 +++-- src/error.rs | 3 + src/event.rs | 52 +++++---- src/lib.rs | 14 +-- src/payment/bolt11.rs | 72 ++++++++---- src/payment/payment_store.rs | 209 ++++++++++++++++++++++++----------- src/payment/spontaneous.rs | 27 +++-- src/uniffi_types.rs | 19 ++++ tests/common/mod.rs | 79 ++++++------- 9 files changed, 323 insertions(+), 178 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 4026dcab0..aa8eeb1e1 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -68,9 +68,9 @@ interface Node { void update_channel_config([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config); [Throws=NodeError] void sync_wallets(); - PaymentDetails? payment([ByRef]PaymentHash payment_hash); + PaymentDetails? payment([ByRef]PaymentId payment_id); [Throws=NodeError] - void remove_payment([ByRef]PaymentHash payment_hash); + void remove_payment([ByRef]PaymentId payment_id); BalanceDetails list_balances(); sequence list_payments(); sequence list_peers(); @@ -82,9 +82,9 @@ interface Node { interface Bolt11Payment { [Throws=NodeError] - PaymentHash send([ByRef]Bolt11Invoice invoice); + PaymentId send([ByRef]Bolt11Invoice invoice); [Throws=NodeError] - PaymentHash send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); + PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); [Throws=NodeError] void send_probes([ByRef]Bolt11Invoice invoice); [Throws=NodeError] @@ -101,7 +101,7 @@ interface Bolt11Payment { interface SpontaneousPayment { [Throws=NodeError] - PaymentHash send(u64 amount_msat, PublicKey node_id); + PaymentId send(u64 amount_msat, PublicKey node_id); [Throws=NodeError] void send_probes(u64 amount_msat, PublicKey node_id); }; @@ -139,6 +139,7 @@ enum NodeError { "InvalidSocketAddress", "InvalidPublicKey", "InvalidSecretKey", + "InvalidPaymentId", "InvalidPaymentHash", "InvalidPaymentPreimage", "InvalidPaymentSecret", @@ -221,9 +222,9 @@ interface ClosureReason { [Enum] interface PaymentKind { Onchain(); - Bolt11(); - Bolt11Jit(LSPFeeLimits lsp_fee_limits); - Spontaneous(); + Bolt11(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret); + Bolt11Jit(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, LSPFeeLimits lsp_fee_limits); + Spontaneous(PaymentHash hash, PaymentPreimage? preimage); }; enum PaymentDirection { @@ -243,10 +244,8 @@ dictionary LSPFeeLimits { }; dictionary PaymentDetails { - PaymentKind? kind; - PaymentHash hash; - PaymentPreimage? preimage; - PaymentSecret? secret; + PaymentId id; + PaymentKind kind; u64? amount_msat; PaymentDirection direction; PaymentStatus status; @@ -371,6 +370,9 @@ typedef string Address; [Custom] typedef string Bolt11Invoice; +[Custom] +typedef string PaymentId; + [Custom] typedef string PaymentHash; diff --git a/src/error.rs b/src/error.rs index c5234a6d4..5acc75af8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -47,6 +47,8 @@ pub enum Error { InvalidPublicKey, /// The given secret key is invalid. InvalidSecretKey, + /// The given payment id is invalid. + InvalidPaymentId, /// The given payment hash is invalid. InvalidPaymentHash, /// The given payment pre-image is invalid. @@ -100,6 +102,7 @@ impl fmt::Display for Error { Self::InvalidSocketAddress => write!(f, "The given network address is invalid."), Self::InvalidPublicKey => write!(f, "The given public key is invalid."), Self::InvalidSecretKey => write!(f, "The given secret key is invalid."), + Self::InvalidPaymentId => write!(f, "The given payment id is invalid."), Self::InvalidPaymentHash => write!(f, "The given payment hash is invalid."), Self::InvalidPaymentPreimage => write!(f, "The given payment preimage is invalid."), Self::InvalidPaymentSecret => write!(f, "The given payment secret is invalid."), diff --git a/src/event.rs b/src/event.rs index d5b5fd1b5..752342a93 100644 --- a/src/event.rs +++ b/src/event.rs @@ -18,6 +18,7 @@ use lightning::chain::chaininterface::ConfirmationTarget; use lightning::events::{ClosureReason, PaymentPurpose}; use lightning::events::{Event as LdkEvent, PaymentFailureReason}; use lightning::impl_writeable_tlv_based_enum; +use lightning::ln::channelmanager::PaymentId; use lightning::ln::{ChannelId, PaymentHash}; use lightning::routing::gossip::NodeId; use lightning::util::errors::APIError; @@ -411,7 +412,8 @@ where onion_fields: _, counterparty_skimmed_fee_msat, } => { - if let Some(info) = self.payment_store.get(&payment_hash) { + let payment_id = PaymentId(payment_hash.0); + if let Some(info) = self.payment_store.get(&payment_id) { if info.status == PaymentStatus::Succeeded { log_info!( self.logger, @@ -423,7 +425,7 @@ where let update = PaymentDetailsUpdate { status: Some(PaymentStatus::Failed), - ..PaymentDetailsUpdate::new(payment_hash) + ..PaymentDetailsUpdate::new(payment_id) }; self.payment_store.update(&update).unwrap_or_else(|e| { log_error!(self.logger, "Failed to access payment store: {}", e); @@ -433,7 +435,7 @@ where } let max_total_opening_fee_msat = match info.kind { - Some(PaymentKind::Bolt11Jit { lsp_fee_limits }) => { + PaymentKind::Bolt11Jit { lsp_fee_limits, .. } => { lsp_fee_limits .max_total_opening_fee_msat .or_else(|| { @@ -461,7 +463,7 @@ where let update = PaymentDetailsUpdate { status: Some(PaymentStatus::Failed), - ..PaymentDetailsUpdate::new(payment_hash) + ..PaymentDetailsUpdate::new(payment_id) }; self.payment_store.update(&update).unwrap_or_else(|e| { log_error!(self.logger, "Failed to access payment store: {}", e); @@ -502,7 +504,7 @@ where let update = PaymentDetailsUpdate { status: Some(PaymentStatus::Failed), - ..PaymentDetailsUpdate::new(payment_hash) + ..PaymentDetailsUpdate::new(payment_id) }; self.payment_store.update(&update).unwrap_or_else(|e| { log_error!(self.logger, "Failed to access payment store: {}", e); @@ -524,6 +526,7 @@ where hex_utils::to_string(&payment_hash.0), amount_msat, ); + let payment_id = PaymentId(payment_hash.0); match purpose { PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => { let update = PaymentDetailsUpdate { @@ -531,7 +534,7 @@ where secret: Some(Some(payment_secret)), amount_msat: Some(Some(amount_msat)), status: Some(PaymentStatus::Succeeded), - ..PaymentDetailsUpdate::new(payment_hash) + ..PaymentDetailsUpdate::new(payment_id) }; match self.payment_store.update(&update) { Ok(true) => (), @@ -555,11 +558,14 @@ where } }, PaymentPurpose::SpontaneousPayment(preimage) => { - let payment = PaymentDetails { - kind: Some(PaymentKind::Spontaneous), - preimage: Some(preimage), + let id = PaymentId(payment_hash.0); + let kind = PaymentKind::Spontaneous { hash: payment_hash, - secret: None, + preimage: Some(preimage), + }; + let payment = PaymentDetails { + id, + kind, amount_msat: Some(amount_msat), direction: PaymentDirection::Inbound, status: PaymentStatus::Succeeded, @@ -596,13 +602,19 @@ where }); }, LdkEvent::PaymentSent { payment_preimage, payment_hash, fee_paid_msat, .. } => { - if let Some(mut payment) = self.payment_store.get(&payment_hash) { - payment.preimage = Some(payment_preimage); - payment.status = PaymentStatus::Succeeded; - self.payment_store.insert(payment.clone()).unwrap_or_else(|e| { - log_error!(self.logger, "Failed to access payment store: {}", e); - panic!("Failed to access payment store"); - }); + let payment_id = PaymentId(payment_hash.0); + let update = PaymentDetailsUpdate { + preimage: Some(Some(payment_preimage)), + status: Some(PaymentStatus::Succeeded), + ..PaymentDetailsUpdate::new(payment_id) + }; + + self.payment_store.update(&update).unwrap_or_else(|e| { + log_error!(self.logger, "Failed to access payment store: {}", e); + panic!("Failed to access payment store"); + }); + + self.payment_store.get(&payment_id).map(|payment| { log_info!( self.logger, "Successfully sent payment of {}msat{} from \ @@ -616,7 +628,8 @@ where hex_utils::to_string(&payment_hash.0), hex_utils::to_string(&payment_preimage.0) ); - } + }); + self.event_queue .add_event(Event::PaymentSuccessful { payment_hash, fee_paid_msat }) .unwrap_or_else(|e| { @@ -632,9 +645,10 @@ where reason ); + let payment_id = PaymentId(payment_hash.0); let update = PaymentDetailsUpdate { status: Some(PaymentStatus::Failed), - ..PaymentDetailsUpdate::new(payment_hash) + ..PaymentDetailsUpdate::new(payment_id) }; self.payment_store.update(&update).unwrap_or_else(|e| { log_error!(self.logger, "Failed to access payment store: {}", e); diff --git a/src/lib.rs b/src/lib.rs index e431c1a45..5031638f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,8 +141,8 @@ pub use types::{ChannelDetails, PeerDetails, UserChannelId}; use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger}; use lightning::chain::{BestBlock, Confirm}; +use lightning::ln::channelmanager::PaymentId; use lightning::ln::msgs::SocketAddress; -use lightning::ln::PaymentHash; use lightning::util::config::{ChannelHandshakeConfig, UserConfig}; pub use lightning::util::logger::Level as LogLevel; @@ -1143,16 +1143,16 @@ impl Node { } } - /// Retrieve the details of a specific payment with the given hash. + /// Retrieve the details of a specific payment with the given id. /// /// Returns `Some` if the payment was known and `None` otherwise. - pub fn payment(&self, payment_hash: &PaymentHash) -> Option { - self.payment_store.get(payment_hash) + pub fn payment(&self, payment_id: &PaymentId) -> Option { + self.payment_store.get(payment_id) } - /// Remove the payment with the given hash from the store. - pub fn remove_payment(&self, payment_hash: &PaymentHash) -> Result<(), Error> { - self.payment_store.remove(&payment_hash) + /// Remove the payment with the given id from the store. + pub fn remove_payment(&self, payment_id: &PaymentId) -> Result<(), Error> { + self.payment_store.remove(&payment_id) } /// Retrieves an overview of all known balances. diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index e6a790224..60c271292 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -66,7 +66,7 @@ impl Bolt11Payment { } /// Send a payment given an invoice. - pub fn send(&self, invoice: &Bolt11Invoice) -> Result { + pub fn send(&self, invoice: &Bolt11Invoice) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { return Err(Error::NotRunning); @@ -77,7 +77,8 @@ impl Bolt11Payment { Error::InvalidInvoice })?; - if let Some(payment) = self.payment_store.get(&payment_hash) { + let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); + if let Some(payment) = self.payment_store.get(&payment_id) { if payment.status == PaymentStatus::Pending || payment.status == PaymentStatus::Succeeded { @@ -87,7 +88,6 @@ impl Bolt11Payment { } let payment_secret = Some(*invoice.payment_secret()); - let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); match self.channel_manager.send_payment( @@ -102,29 +102,36 @@ impl Bolt11Payment { let amt_msat = invoice.amount_milli_satoshis().unwrap(); log_info!(self.logger, "Initiated sending {}msat to {}", amt_msat, payee_pubkey); - let payment = PaymentDetails { - kind: Some(PaymentKind::Bolt11), - preimage: None, + let kind = PaymentKind::Bolt11 { hash: payment_hash, + preimage: None, secret: payment_secret, + }; + + let payment = PaymentDetails { + id: payment_id, + kind, amount_msat: invoice.amount_milli_satoshis(), direction: PaymentDirection::Outbound, status: PaymentStatus::Pending, }; self.payment_store.insert(payment)?; - Ok(payment_hash) + Ok(payment_id) }, Err(e) => { log_error!(self.logger, "Failed to send payment: {:?}", e); match e { RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment), _ => { - let payment = PaymentDetails { - kind: Some(PaymentKind::Bolt11), - preimage: None, + let kind = PaymentKind::Bolt11 { hash: payment_hash, + preimage: None, secret: payment_secret, + }; + let payment = PaymentDetails { + id: payment_id, + kind, amount_msat: invoice.amount_milli_satoshis(), direction: PaymentDirection::Outbound, status: PaymentStatus::Failed, @@ -146,7 +153,7 @@ impl Bolt11Payment { /// amount paid to be determined by the user. pub fn send_using_amount( &self, invoice: &Bolt11Invoice, amount_msat: u64, - ) -> Result { + ) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { return Err(Error::NotRunning); @@ -162,7 +169,8 @@ impl Bolt11Payment { } let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); - if let Some(payment) = self.payment_store.get(&payment_hash) { + let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); + if let Some(payment) = self.payment_store.get(&payment_id) { if payment.status == PaymentStatus::Pending || payment.status == PaymentStatus::Succeeded { @@ -171,7 +179,6 @@ impl Bolt11Payment { } } - let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); let payment_secret = invoice.payment_secret(); let expiry_time = invoice.duration_since_epoch().saturating_add(invoice.expiry_time()); let mut payment_params = PaymentParameters::from_node_id( @@ -199,7 +206,7 @@ impl Bolt11Payment { route_params, retry_strategy, ) { - Ok(_payment_id) => { + Ok(()) => { let payee_pubkey = invoice.recover_payee_pub_key(); log_info!( self.logger, @@ -208,18 +215,22 @@ impl Bolt11Payment { payee_pubkey ); - let payment = PaymentDetails { - kind: Some(PaymentKind::Bolt11), + let kind = PaymentKind::Bolt11 { hash: payment_hash, preimage: None, secret: Some(*payment_secret), + }; + + let payment = PaymentDetails { + id: payment_id, + kind, amount_msat: Some(amount_msat), direction: PaymentDirection::Outbound, status: PaymentStatus::Pending, }; self.payment_store.insert(payment)?; - Ok(payment_hash) + Ok(payment_id) }, Err(e) => { log_error!(self.logger, "Failed to send payment: {:?}", e); @@ -227,11 +238,15 @@ impl Bolt11Payment { match e { RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment), _ => { - let payment = PaymentDetails { - kind: Some(PaymentKind::Bolt11), + let kind = PaymentKind::Bolt11 { hash: payment_hash, preimage: None, secret: Some(*payment_secret), + }; + + let payment = PaymentDetails { + id: payment_id, + kind, amount_msat: Some(amount_msat), direction: PaymentDirection::Outbound, status: PaymentStatus::Failed, @@ -287,11 +302,16 @@ impl Bolt11Payment { }; let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); - let payment = PaymentDetails { - kind: Some(PaymentKind::Bolt11), + let id = PaymentId(payment_hash.0); + let kind = PaymentKind::Bolt11 { hash: payment_hash, preimage: None, secret: Some(invoice.payment_secret().clone()), + }; + + let payment = PaymentDetails { + id, + kind, amount_msat, direction: PaymentDirection::Inbound, status: PaymentStatus::Pending, @@ -413,12 +433,16 @@ impl Bolt11Payment { max_total_opening_fee_msat: lsp_total_opening_fee, max_proportional_opening_fee_ppm_msat: lsp_prop_opening_fee, }; - let kind = Some(PaymentKind::Bolt11Jit { lsp_fee_limits }); - let payment = PaymentDetails { - kind, + let id = PaymentId(payment_hash.0); + let kind = PaymentKind::Bolt11Jit { hash: payment_hash, preimage: None, secret: Some(invoice.payment_secret().clone()), + lsp_fee_limits, + }; + let payment = PaymentDetails { + id, + kind, amount_msat, direction: PaymentDirection::Inbound, status: PaymentStatus::Pending, diff --git a/src/payment/payment_store.rs b/src/payment/payment_store.rs index 5fb0abb66..9b0bf0069 100644 --- a/src/payment/payment_store.rs +++ b/src/payment/payment_store.rs @@ -6,9 +6,14 @@ use crate::logger::{log_error, Logger}; use crate::types::DynStore; use crate::Error; +use lightning::ln::channelmanager::PaymentId; +use lightning::ln::msgs::DecodeError; use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; -use lightning::util::ser::Writeable; -use lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum}; +use lightning::util::ser::{Readable, Writeable}; +use lightning::{ + _init_and_read_len_prefixed_tlv_fields, impl_writeable_tlv_based, + impl_writeable_tlv_based_enum, write_tlv_fields, +}; use std::collections::HashMap; use std::iter::FromIterator; @@ -18,10 +23,10 @@ use std::sync::{Arc, Mutex}; /// Represents a payment. #[derive(Clone, Debug, PartialEq, Eq)] pub struct PaymentDetails { + /// The identifier of this payment. + pub id: PaymentId, /// The kind of the payment. - /// - /// This will be `None` for objects created with LDK Node v0.2.1 and earlier. - pub kind: Option, + pub kind: PaymentKind, /// The amount transferred. pub amount_msat: Option, /// The direction of the payment. @@ -30,16 +35,75 @@ pub struct PaymentDetails { pub status: PaymentStatus, } -impl_writeable_tlv_based!(PaymentDetails, { - (0, hash, required), - // 1 briefly used to be lsp_fee_limits, could probably be reused at some point in the future. - (2, preimage, required), - (3, kind, option), - (4, secret, required), - (6, amount_msat, required), - (8, direction, required), - (10, status, required) -}); +impl Writeable for PaymentDetails { + fn write( + &self, writer: &mut W, + ) -> Result<(), lightning::io::Error> { + write_tlv_fields!(writer, { + (0, self.id, required), + // 1 briefly used to be lsp_fee_limits, could probably be reused at some point in the future. + // 2 used to be the preimage before it was moved to `kind` + (3, self.kind, required), + // 4 used to be the secret before it was moved to `kind` + (6, self.amount_msat, required), + (8, self.direction, required), + (10, self.status, required) + }); + Ok(()) + } +} + +impl Readable for PaymentDetails { + fn read(reader: &mut R) -> Result { + _init_and_read_len_prefixed_tlv_fields!(reader, { + (0, id, required), + (1, lsp_fee_limits_opt, option), + (2, preimage, option), + (3, kind_opt, option), + (4, secret_opt, option), + (6, amount_msat, required), + (8, direction, required), + (10, status, required) + }); + + let id: PaymentId = id.0.ok_or(DecodeError::InvalidValue)?; + let hash = PaymentHash(id.0); + let direction = direction.0.ok_or(DecodeError::InvalidValue)?; + let status = status.0.ok_or(DecodeError::InvalidValue)?; + + let kind = if let Some(kind) = kind_opt { + // If we serialized the payment kind, use it. + // This will always be the case for any version after v0.2.1. + kind + } else { + // Otherwise we persisted with v0.2.1 or before, and puzzle together the kind from the + // provided fields. + + if let Some(secret) = secret_opt { + if let Some(lsp_fee_limits) = lsp_fee_limits_opt { + PaymentKind::Bolt11Jit { hash, preimage, secret, lsp_fee_limits } + } else { + PaymentKind::Bolt11 { hash, preimage, secret } + } + } else { + PaymentKind::Spontaneous { hash, preimage } + } + }; + + Ok(PaymentDetails { id, kind, amount_msat: amount_msat.0, direction, status }) + } +} + +//impl_writeable_tlv_based!(PaymentDetails, { +// (0, id, required), +// // 1 briefly used to be lsp_fee_limits, could probably be reused at some point in the future. +// // 2 used to be the preimage before it was moved to `kind` +// (3, kind, required), +// // 4 used to be the secret before it was moved to `kind` +// (6, amount_msat, required), +// (8, direction, required), +// (10, status, required) +//}); /// Represents the direction of a payment. #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -81,20 +145,26 @@ pub enum PaymentKind { /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md // TODO: Bolt11 { invoice: Option }, - Bolt11 { - /// The payment hash, i.e., the hash of the `preimage`. - hash: PaymentHash, - /// The pre-image used by the payment. - preimage: Option, - /// The secret used by the payment. - secret: Option, - }, + Bolt11 { + /// The payment hash, i.e., the hash of the `preimage`. + hash: PaymentHash, + /// The pre-image used by the payment. + preimage: Option, + /// The secret used by the payment. + secret: Option, + }, /// A [BOLT 11] payment intended to open an [LSPS 2] just-in-time channel. /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md /// [LSPS 2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md // TODO: Bolt11Jit { invoice: Option }, Bolt11Jit { + /// The payment hash, i.e., the hash of the `preimage`. + hash: PaymentHash, + /// The pre-image used by the payment. + preimage: Option, + /// The secret used by the payment. + secret: Option, /// Limits applying to how much fee we allow an LSP to deduct from the payment amount. /// /// Allowing them to deduct this fee from the first inbound payment will pay for the LSP's @@ -106,18 +176,31 @@ pub enum PaymentKind { lsp_fee_limits: LSPFeeLimits, }, /// A spontaneous ("keysend") payment. - Spontaneous, + Spontaneous { + /// The payment hash, i.e., the hash of the `preimage`. + hash: PaymentHash, + /// The pre-image used by the payment. + preimage: Option, + }, } impl_writeable_tlv_based_enum!(PaymentKind, (0, Onchain) => {}, (2, Bolt11) => { - // TODO: (0, invoice, option), + (0, hash, required), + (2, preimage, required), + (4, secret, required), }, (4, Bolt11Jit) => { - (0, lsp_fee_limits, required), + (0, hash, required), + (2, preimage, required), + (4, secret, required), + (6, lsp_fee_limits, required), }, - (8, Spontaneous) => {}; + (8, Spontaneous) => { + (0, hash, required), + (2, preimage, required), + }; ); /// Limits applying to how much fee we allow an LSP to deduct from the payment amount. @@ -142,7 +225,7 @@ impl_writeable_tlv_based!(LSPFeeLimits, { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct PaymentDetailsUpdate { - pub hash: PaymentHash, + pub id: PaymentId, pub preimage: Option>, pub secret: Option>, pub amount_msat: Option>, @@ -151,15 +234,8 @@ pub(crate) struct PaymentDetailsUpdate { } impl PaymentDetailsUpdate { - pub fn new(hash: PaymentHash) -> Self { - Self { - hash, - preimage: None, - secret: None, - amount_msat: None, - direction: None, - status: None, - } + pub fn new(id: PaymentId) -> Self { + Self { id, preimage: None, secret: None, amount_msat: None, direction: None, status: None } } } @@ -167,7 +243,7 @@ pub(crate) struct PaymentStore where L::Target: Logger, { - payments: Mutex>, + payments: Mutex>, kv_store: Arc, logger: L, } @@ -178,7 +254,7 @@ where { pub(crate) fn new(payments: Vec, kv_store: Arc, logger: L) -> Self { let payments = Mutex::new(HashMap::from_iter( - payments.into_iter().map(|payment| (payment.hash, payment)), + payments.into_iter().map(|payment| (payment.id, payment)), )); Self { payments, kv_store, logger } } @@ -186,14 +262,14 @@ where pub(crate) fn insert(&self, payment: PaymentDetails) -> Result { let mut locked_payments = self.payments.lock().unwrap(); - let hash = payment.hash.clone(); - let updated = locked_payments.insert(hash.clone(), payment.clone()).is_some(); - self.persist_info(&hash, &payment)?; + let id = payment.id.clone(); + let updated = locked_payments.insert(id.clone(), payment.clone()).is_some(); + self.persist_info(&id, &payment)?; Ok(updated) } - pub(crate) fn remove(&self, hash: &PaymentHash) -> Result<(), Error> { - let store_key = hex_utils::to_string(&hash.0); + pub(crate) fn remove(&self, id: &PaymentId) -> Result<(), Error> { + let store_key = hex_utils::to_string(&id.0); self.kv_store .remove( PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, @@ -214,21 +290,30 @@ where }) } - pub(crate) fn get(&self, hash: &PaymentHash) -> Option { - self.payments.lock().unwrap().get(hash).cloned() + pub(crate) fn get(&self, id: &PaymentId) -> Option { + self.payments.lock().unwrap().get(id).cloned() } pub(crate) fn update(&self, update: &PaymentDetailsUpdate) -> Result { let mut updated = false; let mut locked_payments = self.payments.lock().unwrap(); - if let Some(payment) = locked_payments.get_mut(&update.hash) { + if let Some(payment) = locked_payments.get_mut(&update.id) { if let Some(preimage_opt) = update.preimage { - payment.preimage = preimage_opt; + match payment.kind { + PaymentKind::Bolt11 { ref mut preimage, .. } => *preimage = preimage_opt, + PaymentKind::Bolt11Jit { ref mut preimage, .. } => *preimage = preimage_opt, + PaymentKind::Spontaneous { ref mut preimage, .. } => *preimage = preimage_opt, + _ => {}, + } } if let Some(secret_opt) = update.secret { - payment.secret = secret_opt; + match payment.kind { + PaymentKind::Bolt11 { ref mut secret, .. } => *secret = secret_opt, + PaymentKind::Bolt11Jit { ref mut secret, .. } => *secret = secret_opt, + _ => {}, + } } if let Some(amount_opt) = update.amount_msat { @@ -239,7 +324,7 @@ where payment.status = status; } - self.persist_info(&update.hash, payment)?; + self.persist_info(&update.id, payment)?; updated = true; } @@ -259,8 +344,8 @@ where .collect::>() } - fn persist_info(&self, hash: &PaymentHash, payment: &PaymentDetails) -> Result<(), Error> { - let store_key = hex_utils::to_string(&hash.0); + fn persist_info(&self, id: &PaymentId, payment: &PaymentDetails) -> Result<(), Error> { + let store_key = hex_utils::to_string(&id.0); let data = payment.encode(); self.kv_store .write( @@ -324,7 +409,8 @@ mod tests { let payment_store = PaymentStore::new(Vec::new(), Arc::clone(&store), logger); let hash = PaymentHash([42u8; 32]); - assert!(!payment_store.get(&hash).is_some()); + let id = PaymentId([42u8; 32]); + assert!(!payment_store.get(&id).is_some()); let store_key = hex_utils::to_string(&hash.0); assert!(store @@ -335,18 +421,17 @@ mod tests { ) .is_err()); + let kind = PaymentKind::Bolt11 { hash, preimage: None, secret: None }; let payment = PaymentDetails { - hash, - preimage: None, - secret: None, + id, + kind, amount_msat: None, direction: PaymentDirection::Inbound, status: PaymentStatus::Pending, - lsp_fee_limits: None, }; assert_eq!(Ok(false), payment_store.insert(payment.clone())); - assert!(payment_store.get(&hash).is_some()); + assert!(payment_store.get(&id).is_some()); assert!(store .read( PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, @@ -356,14 +441,14 @@ mod tests { .is_ok()); assert_eq!(Ok(true), payment_store.insert(payment)); - assert!(payment_store.get(&hash).is_some()); + assert!(payment_store.get(&id).is_some()); - let mut update = PaymentDetailsUpdate::new(hash); + let mut update = PaymentDetailsUpdate::new(id); update.status = Some(PaymentStatus::Succeeded); assert_eq!(Ok(true), payment_store.update(&update)); - assert!(payment_store.get(&hash).is_some()); + assert!(payment_store.get(&id).is_some()); - assert_eq!(PaymentStatus::Succeeded, payment_store.get(&hash).unwrap().status); + assert_eq!(PaymentStatus::Succeeded, payment_store.get(&id).unwrap().status); } #[test] diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 46d5d0db8..59aa15022 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -42,7 +42,7 @@ impl SpontaneousPayment { } /// Send a spontaneous, aka. "keysend", payment - pub fn send(&self, amount_msat: u64, node_id: PublicKey) -> Result { + pub fn send(&self, amount_msat: u64, node_id: PublicKey) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { return Err(Error::NotRunning); @@ -50,8 +50,9 @@ impl SpontaneousPayment { let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes()); let payment_hash = PaymentHash::from(payment_preimage); + let payment_id = PaymentId(payment_hash.0); - if let Some(payment) = self.payment_store.get(&payment_hash) { + if let Some(payment) = self.payment_store.get(&payment_id) { if payment.status == PaymentStatus::Pending || payment.status == PaymentStatus::Succeeded { @@ -73,21 +74,24 @@ impl SpontaneousPayment { route_params, Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT), ) { - Ok(_payment_id) => { + Ok(_hash) => { log_info!(self.logger, "Initiated sending {}msat to {}.", amount_msat, node_id); - let payment = PaymentDetails { - kind: Some(PaymentKind::Spontaneous), + let kind = PaymentKind::Spontaneous { hash: payment_hash, preimage: Some(payment_preimage), - secret: None, + }; + + let payment = PaymentDetails { + id: payment_id, + kind, status: PaymentStatus::Pending, direction: PaymentDirection::Outbound, amount_msat: Some(amount_msat), }; self.payment_store.insert(payment)?; - Ok(payment_hash) + Ok(payment_id) }, Err(e) => { log_error!(self.logger, "Failed to send payment: {:?}", e); @@ -95,11 +99,14 @@ impl SpontaneousPayment { match e { RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment), _ => { - let payment = PaymentDetails { - kind: Some(PaymentKind::Spontaneous), + let kind = PaymentKind::Spontaneous { hash: payment_hash, preimage: Some(payment_preimage), - secret: None, + }; + + let payment = PaymentDetails { + id: payment_id, + kind, status: PaymentStatus::Failed, direction: PaymentDirection::Outbound, amount_msat: Some(amount_msat), diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index e0c18fe36..43dd869d7 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -21,6 +21,7 @@ use crate::{SocketAddress, UserChannelId}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; +use lightning::ln::channelmanager::PaymentId; use lightning_invoice::SignedRawBolt11Invoice; use std::convert::TryInto; @@ -76,6 +77,24 @@ impl UniffiCustomTypeConverter for Bolt11Invoice { } } +impl UniffiCustomTypeConverter for PaymentId { + type Builtin = String; + + 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(PaymentId(bytes)); + } + } + Err(Error::InvalidPaymentId.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + hex_utils::to_string(&obj.0) + } +} + impl UniffiCustomTypeConverter for PaymentHash { type Builtin = String; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index a9acb63a0..f8022639a 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -419,10 +419,10 @@ pub(crate) fn do_channel_full_cycle( let invoice = node_b.bolt11_payment().receive(invoice_amount_1_msat, &"asdf", 9217).unwrap(); println!("\nA send"); - let payment_hash = node_a.bolt11_payment().send(&invoice).unwrap(); + let payment_id = node_a.bolt11_payment().send(&invoice).unwrap(); assert_eq!(node_a.bolt11_payment().send(&invoice), Err(NodeError::DuplicatePayment)); - assert_eq!(node_a.list_payments().first().unwrap().hash, payment_hash); + assert_eq!(node_a.list_payments().first().unwrap().id, payment_id); let outbound_payments_a = node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound); @@ -442,21 +442,21 @@ pub(crate) fn do_channel_full_cycle( expect_event!(node_a, PaymentSuccessful); expect_event!(node_b, PaymentReceived); - assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound); - assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); - assert_eq!(node_b.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound); - assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); // Assert we fail duplicate outbound payments and check the status hasn't changed. assert_eq!(Err(NodeError::DuplicatePayment), node_a.bolt11_payment().send(&invoice)); - assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound); - assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); - assert_eq!(node_b.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound); - assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); // Test under-/overpayment let invoice_amount_2_msat = 2500_000; @@ -473,7 +473,7 @@ pub(crate) fn do_channel_full_cycle( let overpaid_amount_msat = invoice_amount_2_msat + 100; println!("\nA overpaid send"); - let payment_hash = + let payment_id = node_a.bolt11_payment().send_using_amount(&invoice, overpaid_amount_msat).unwrap(); expect_event!(node_a, PaymentSuccessful); let received_amount = match node_b.wait_next_event() { @@ -487,12 +487,12 @@ pub(crate) fn do_channel_full_cycle( }, }; assert_eq!(received_amount, overpaid_amount_msat); - assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound); - assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(overpaid_amount_msat)); - assert_eq!(node_b.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound); - assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(overpaid_amount_msat)); + assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(overpaid_amount_msat)); + assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(overpaid_amount_msat)); // Test "zero-amount" invoice payment println!("\nB receive_variable_amount_payment"); @@ -504,7 +504,7 @@ pub(crate) fn do_channel_full_cycle( node_a.bolt11_payment().send(&variable_amount_invoice) ); println!("\nA send_using_amount"); - let payment_hash = node_a + let payment_id = node_a .bolt11_payment() .send_using_amount(&variable_amount_invoice, determined_amount_msat) .unwrap(); @@ -521,17 +521,17 @@ pub(crate) fn do_channel_full_cycle( }, }; assert_eq!(received_amount, determined_amount_msat); - assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound); - assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(determined_amount_msat)); - assert_eq!(node_b.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound); - assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(determined_amount_msat)); + assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(determined_amount_msat)); + assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(determined_amount_msat)); // Test spontaneous/keysend payments println!("\nA send_spontaneous_payment"); let keysend_amount_msat = 2500_000; - let keysend_payment_hash = + let keysend_payment_id = node_a.spontaneous_payment().send(keysend_amount_msat, node_b.node_id()).unwrap(); expect_event!(node_a, PaymentSuccessful); let received_keysend_amount = match node_b.wait_next_event() { @@ -545,21 +545,12 @@ pub(crate) fn do_channel_full_cycle( }, }; assert_eq!(received_keysend_amount, keysend_amount_msat); - assert_eq!(node_a.payment(&keysend_payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!( - node_a.payment(&keysend_payment_hash).unwrap().direction, - PaymentDirection::Outbound - ); - assert_eq!( - node_a.payment(&keysend_payment_hash).unwrap().amount_msat, - Some(keysend_amount_msat) - ); - assert_eq!(node_b.payment(&keysend_payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&keysend_payment_hash).unwrap().direction, PaymentDirection::Inbound); - assert_eq!( - node_b.payment(&keysend_payment_hash).unwrap().amount_msat, - Some(keysend_amount_msat) - ); + assert_eq!(node_a.payment(&keysend_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&keysend_payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&keysend_payment_id).unwrap().amount_msat, Some(keysend_amount_msat)); + assert_eq!(node_b.payment(&keysend_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&keysend_payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&keysend_payment_id).unwrap().amount_msat, Some(keysend_amount_msat)); println!("\nB close_channel"); node_b.close_channel(&user_channel_id, node_a.node_id()).unwrap();