diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f15e61f..3506daa6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ prior LSPS2 fee-limit state stored in `PaymentKind::Bolt11Jit` is not migrated. - Users of the VSS storage backend must upgrade their VSS server to at least version `v0.1.0-alpha.0` before upgrading LDK Node. +- The `payment_id` field on the `PaymentSuccessful`, `PaymentFailed`, and + `PaymentReceived` events is now a required (non-optional) `PaymentId`. Events + persisted by LDK Node v0.2.1 or earlier (which stored `payment_id` as + optional) will fail to deserialize on read; users upgrading from those + versions need to drain pending events before the upgrade. # 0.7.0 - Dec. 3, 2025 This seventh minor release introduces numerous new features, bug fixes, and API improvements. In particular, it adds support for channel Splicing, Async Payments, as well as sourcing chain data from a Bitcoin Core REST backend. diff --git a/src/builder.rs b/src/builder.rs index 3df594b7c..1df89f5c6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -2149,6 +2149,7 @@ fn build_with_store_internal( scorer, peer_store, payment_store, + pending_payment_store, lnurl_auth, is_running, node_metrics, diff --git a/src/event.rs b/src/event.rs index 80acd0690..8e9b36c42 100644 --- a/src/event.rs +++ b/src/event.rs @@ -10,6 +10,7 @@ use core::task::{Poll, Waker}; use std::collections::VecDeque; use std::ops::Deref; use std::sync::{Arc, Mutex}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::secp256k1::PublicKey; @@ -53,7 +54,8 @@ use crate::payment::store::{ use crate::payment::PaymentMetadata; use crate::runtime::Runtime; use crate::types::{ - CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, Sweeper, Wallet, + CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, PendingPaymentStore, + Sweeper, Wallet, }; use crate::{ hex_utils, BumpTransactionEventHandler, ChannelManager, Error, Graph, PeerInfo, PeerStore, @@ -103,9 +105,7 @@ pub enum Event { /// A sent payment was successful. PaymentSuccessful { /// A local identifier used to track the payment. - /// - /// Will only be `None` for events serialized with LDK Node v0.2.1 or prior. - payment_id: Option, + payment_id: PaymentId, /// The hash of the payment. payment_hash: PaymentHash, /// The preimage to the `payment_hash`. @@ -131,9 +131,7 @@ pub enum Event { /// A sent payment has failed. PaymentFailed { /// A local identifier used to track the payment. - /// - /// Will only be `None` for events serialized with LDK Node v0.2.1 or prior. - payment_id: Option, + payment_id: PaymentId, /// The hash of the payment. /// /// This will be `None` if the payment failed before receiving an invoice when paying a @@ -149,9 +147,7 @@ pub enum Event { /// A payment has been received. PaymentReceived { /// A local identifier used to track the payment. - /// - /// Will only be `None` for events serialized with LDK Node v0.2.1 or prior. - payment_id: Option, + payment_id: PaymentId, /// The hash of the payment. payment_hash: PaymentHash, /// The value, in thousandths of a satoshi, that has been received. @@ -200,15 +196,15 @@ pub enum Event { }, /// A payment for a previously-registered payment hash has been received. /// - /// This needs to be manually claimed by supplying the correct preimage to [`claim_for_hash`]. + /// This needs to be manually claimed by supplying the correct preimage to [`claim_for_id`]. /// /// If the provided parameters don't match the expectations or the preimage can't be - /// retrieved in time, should be failed-back via [`fail_for_hash`]. + /// retrieved in time, should be failed-back via [`fail_for_id`]. /// /// Note claiming will necessarily fail after the `claim_deadline` has been reached. /// - /// [`claim_for_hash`]: crate::payment::Bolt11Payment::claim_for_hash - /// [`fail_for_hash`]: crate::payment::Bolt11Payment::fail_for_hash + /// [`claim_for_id`]: crate::payment::Bolt11Payment::claim_for_id + /// [`fail_for_id`]: crate::payment::Bolt11Payment::fail_for_id PaymentClaimable { /// A local identifier used to track the payment. payment_id: PaymentId, @@ -298,18 +294,18 @@ impl_writeable_tlv_based_enum!(Event, (0, PaymentSuccessful) => { (0, payment_hash, required), (1, fee_paid_msat, option), - (3, payment_id, option), + (3, payment_id, required), (5, payment_preimage, option), (7, bolt12_invoice, option), }, (1, PaymentFailed) => { (0, payment_hash, option), (1, reason, upgradable_option), - (3, payment_id, option), + (3, payment_id, required), }, (2, PaymentReceived) => { (0, payment_hash, required), - (1, payment_id, option), + (1, payment_id, required), (2, amount_msat, required), (3, custom_records, optional_vec), }, @@ -535,6 +531,7 @@ where network_graph: Arc, liquidity_source: Arc>>, payment_store: Arc, + pending_payment_store: Arc, peer_store: Arc>, keys_manager: Arc, runtime: Arc, @@ -555,10 +552,10 @@ where channel_manager: Arc, connection_manager: Arc>, output_sweeper: Arc, network_graph: Arc, liquidity_source: Arc>>, payment_store: Arc, - peer_store: Arc>, keys_manager: Arc, - static_invoice_store: Option, onion_messenger: Arc, - om_mailbox: Option>, runtime: Arc, logger: L, - config: Arc, + pending_payment_store: Arc, peer_store: Arc>, + keys_manager: Arc, static_invoice_store: Option, + onion_messenger: Arc, om_mailbox: Option>, + runtime: Arc, logger: L, config: Arc, ) -> Self { Self { event_queue, @@ -570,6 +567,7 @@ where network_graph, liquidity_source, payment_store, + pending_payment_store, peer_store, keys_manager, logger, @@ -611,6 +609,80 @@ where }) } + fn current_time_secs() -> u64 { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::from_secs(0)).as_secs() + } + + async fn prune_expired_pending_payments(&self) -> Result<(), ReplayEvent> { + let now = Self::current_time_secs(); + let expired_payment_ids = self + .pending_payment_store + .list_filter(|payment| payment.has_expired(now)) + .into_iter() + .map(|payment| payment.details.id) + .collect::>(); + + for payment_id in expired_payment_ids { + if let Err(e) = self.pending_payment_store.remove(&payment_id).await { + log_error!( + self.logger, + "Failed to remove expired pending payment with ID {}: {}", + payment_id, + e + ); + return Err(ReplayEvent()); + } + } + + Ok(()) + } + + fn find_inbound_payment_by_hash(&self, payment_hash: &PaymentHash) -> Option { + self.payment_store + .list_filter(|payment| { + payment.direction == PaymentDirection::Inbound + && matches!(&payment.kind, PaymentKind::Bolt11 { hash, .. } if hash == payment_hash) + }) + .first() + .cloned() + } + + async fn find_pending_inbound_payment_id( + &self, payment_hash: &PaymentHash, + ) -> Result, ReplayEvent> { + self.prune_expired_pending_payments().await?; + Ok(self + .pending_payment_store + .list_filter(|payment| { + payment.details.direction == PaymentDirection::Inbound + && payment.details.status == PaymentStatus::Pending + && matches!(&payment.details.kind, PaymentKind::Bolt11 { hash, .. } if hash == payment_hash) + }) + .first() + .map(|payment| payment.details.id)) + } + + fn resolve_inbound_payment_id( + &self, event_payment_id: PaymentId, payment_hash: &PaymentHash, + ) -> (PaymentId, Option) { + if let Some(info) = self.payment_store.get(&event_payment_id) { + return (event_payment_id, Some(info)); + } + + let legacy_id = PaymentId(payment_hash.0); + if legacy_id != event_payment_id { + if let Some(info) = self.payment_store.get(&legacy_id) { + return (legacy_id, Some(info)); + } + } + + if let Some(info) = self.find_inbound_payment_by_hash(payment_hash) { + return (info.id, Some(info)); + } + + (event_payment_id, None) + } + pub async fn handle_event(&self, event: LdkEvent) -> Result<(), ReplayEvent> { match event { LdkEvent::FundingGenerationReady { @@ -714,6 +786,7 @@ where .lsps2_funding_tx_broadcast_safe(user_channel_id, counterparty_node_id); }, LdkEvent::PaymentClaimable { + payment_id, payment_hash, purpose, amount_msat, @@ -722,8 +795,17 @@ where counterparty_skimmed_fee_msat, .. } => { - let payment_id = PaymentId(payment_hash.0); - let payment_info = self.payment_store.get(&payment_id); + let event_payment_id = payment_id.unwrap_or(PaymentId(payment_hash.0)); + let (mut payment_id, payment_info) = + self.resolve_inbound_payment_id(event_payment_id, &payment_hash); + let pending_payment_id = if payment_info.is_none() { + self.find_pending_inbound_payment_id(&payment_hash).await? + } else { + None + }; + if let Some(pending_payment_id) = pending_payment_id { + payment_id = pending_payment_id; + } if let Some(info) = payment_info.as_ref() { if info.direction == PaymentDirection::Outbound { log_info!( @@ -844,47 +926,6 @@ where } } - if let Some(info) = payment_info { - // If this is known by the store but ChannelManager doesn't know the preimage, - // the payment has been registered via `_for_hash` variants and needs to be manually claimed via - // user interaction. - match info.kind { - PaymentKind::Bolt11 { preimage, .. } => { - if purpose.preimage().is_none() { - debug_assert!( - preimage.is_none(), - "We would have registered the preimage if we knew" - ); - - let custom_records = onion_fields - .map(|cf| { - cf.custom_tlvs().into_iter().map(|tlv| tlv.into()).collect() - }) - .unwrap_or_default(); - let event = Event::PaymentClaimable { - payment_id, - payment_hash, - claimable_amount_msat: amount_msat, - claim_deadline, - custom_records, - }; - match self.event_queue.add_event(event).await { - Ok(_) => return Ok(()), - Err(e) => { - log_error!( - self.logger, - "Failed to push to event queue: {}", - e - ); - return Err(ReplayEvent()); - }, - }; - } - }, - _ => {}, - } - } - log_info!( self.logger, "Received payment from payment hash {} of {}msat", @@ -892,7 +933,101 @@ where amount_msat, ); let payment_preimage = match purpose { - PaymentPurpose::Bolt11InvoicePayment { payment_preimage, .. } => { + PaymentPurpose::Bolt11InvoicePayment { + payment_preimage, + payment_secret, + .. + } => { + if payment_info.is_none() { + let kind = PaymentKind::Bolt11 { + hash: payment_hash, + preimage: payment_preimage, + secret: Some(payment_secret), + counterparty_skimmed_fee_msat: if counterparty_skimmed_fee_msat > 0 + { + Some(counterparty_skimmed_fee_msat) + } else { + None + }, + }; + + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + None, + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); + + match self.payment_store.insert(payment).await { + Ok(false) => (), + Ok(true) => { + log_error!( + self.logger, + "Bolt11InvoicePayment with ID {} was previously known", + payment_id, + ); + debug_assert!(false); + }, + Err(e) => { + log_error!( + self.logger, + "Failed to insert payment with ID {}: {}", + payment_id, + e + ); + return Err(ReplayEvent()); + }, + } + + if pending_payment_id.is_some() { + if let Err(e) = self.pending_payment_store.remove(&payment_id).await + { + log_error!( + self.logger, + "Failed to remove pending payment with ID {}: {}", + payment_id, + e + ); + return Err(ReplayEvent()); + } + } + } + + if payment_preimage.is_none() { + if let Some(info) = payment_info.as_ref() { + let stored_preimage = match &info.kind { + PaymentKind::Bolt11 { preimage, .. } => *preimage, + _ => None, + }; + debug_assert!( + stored_preimage.is_none(), + "We would have registered the preimage if we knew" + ); + } + + let custom_records = onion_fields + .map(|cf| { + cf.custom_tlvs().into_iter().map(|tlv| tlv.into()).collect() + }) + .unwrap_or_default(); + let event = Event::PaymentClaimable { + payment_id, + payment_hash, + claimable_amount_msat: amount_msat, + claim_deadline, + custom_records, + }; + match self.event_queue.add_event(event).await { + Ok(_) => return Ok(()), + Err(e) => { + log_error!(self.logger, "Failed to push to event queue: {}", e); + return Err(ReplayEvent()); + }, + }; + } + payment_preimage }, PaymentPurpose::Bolt12OfferPayment { @@ -904,84 +1039,130 @@ where let payer_note = payment_context.invoice_request.payer_note_truncated; let offer_id = payment_context.offer_id; let quantity = payment_context.invoice_request.quantity; - let kind = PaymentKind::Bolt12Offer { - hash: Some(payment_hash), - preimage: payment_preimage, - secret: Some(payment_secret), - offer_id, - payer_note, - quantity, - }; - - let payment = PaymentDetails::new( - payment_id, - kind, - Some(amount_msat), - None, - PaymentDirection::Inbound, - PaymentStatus::Pending, - ); + if payment_info.is_none() { + let kind = PaymentKind::Bolt12Offer { + hash: Some(payment_hash), + preimage: payment_preimage, + secret: Some(payment_secret), + offer_id, + payer_note, + quantity, + }; + + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + None, + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); - match self.payment_store.insert(payment).await { - Ok(false) => (), - Ok(true) => { - log_error!( - self.logger, - "Bolt12OfferPayment with ID {} was previously known", - payment_id, - ); - debug_assert!(false); - }, - Err(e) => { - log_error!( - self.logger, - "Failed to insert payment with ID {}: {}", - payment_id, - e - ); - debug_assert!(false); - }, + match self.payment_store.insert(payment).await { + Ok(false) => (), + Ok(true) => { + log_error!( + self.logger, + "Bolt12OfferPayment with ID {} was previously known", + payment_id, + ); + debug_assert!(false); + }, + Err(e) => { + log_error!( + self.logger, + "Failed to insert payment with ID {}: {}", + payment_id, + e + ); + return Err(ReplayEvent()); + }, + } } payment_preimage }, - PaymentPurpose::Bolt12RefundPayment { payment_preimage, .. } => { + PaymentPurpose::Bolt12RefundPayment { + payment_preimage, + payment_secret, + .. + } => { + if payment_info.is_none() { + let kind = PaymentKind::Bolt12Refund { + hash: Some(payment_hash), + preimage: payment_preimage, + secret: Some(payment_secret), + payer_note: None, + quantity: None, + }; + + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + None, + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); + + match self.payment_store.insert(payment).await { + Ok(false) => (), + Ok(true) => { + log_error!( + self.logger, + "Bolt12RefundPayment with ID {} was previously known", + payment_id, + ); + debug_assert!(false); + }, + Err(e) => { + log_error!( + self.logger, + "Failed to insert payment with ID {}: {}", + payment_id, + e + ); + return Err(ReplayEvent()); + }, + } + } payment_preimage }, PaymentPurpose::SpontaneousPayment(preimage) => { - // Since it's spontaneous, we insert it now into our store. - let kind = PaymentKind::Spontaneous { - hash: payment_hash, - preimage: Some(preimage), - }; - - let payment = PaymentDetails::new( - payment_id, - kind, - Some(amount_msat), - None, - PaymentDirection::Inbound, - PaymentStatus::Pending, - ); + if payment_info.is_none() { + let kind = PaymentKind::Spontaneous { + hash: payment_hash, + preimage: Some(preimage), + }; + + let payment = PaymentDetails::new( + payment_id, + kind, + Some(amount_msat), + None, + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); - match self.payment_store.insert(payment).await { - Ok(false) => (), - Ok(true) => { - log_error!( - self.logger, - "Spontaneous payment with ID {} was previously known", - payment_id, - ); - debug_assert!(false); - }, - Err(e) => { - log_error!( - self.logger, - "Failed to insert payment with ID {}: {}", - payment_id, - e - ); - debug_assert!(false); - }, + match self.payment_store.insert(payment).await { + Ok(false) => (), + Ok(true) => { + log_error!( + self.logger, + "Spontaneous payment with ID {} was previously known", + payment_id, + ); + debug_assert!(false); + }, + Err(e) => { + log_error!( + self.logger, + "Failed to insert payment with ID {}: {}", + payment_id, + e + ); + return Err(ReplayEvent()); + }, + } } Some(preimage) @@ -1013,6 +1194,7 @@ where } }, LdkEvent::PaymentClaimed { + payment_id, payment_hash, purpose, amount_msat, @@ -1020,9 +1202,10 @@ where htlcs: _, sender_intended_total_msat: _, onion_fields, - payment_id: _, } => { - let payment_id = PaymentId(payment_hash.0); + let event_payment_id = payment_id.unwrap_or(PaymentId(payment_hash.0)); + let (payment_id, _) = + self.resolve_inbound_payment_id(event_payment_id, &payment_hash); log_info!( self.logger, "Claimed payment with ID {} from payment hash {} of {}msat.", @@ -1031,43 +1214,83 @@ where amount_msat, ); - let update = match purpose { + let (update, kind_for_insert) = match purpose { PaymentPurpose::Bolt11InvoicePayment { payment_preimage, payment_secret, .. - } => PaymentDetailsUpdate { - preimage: Some(payment_preimage), - secret: Some(Some(payment_secret)), - amount_msat: Some(Some(amount_msat)), - status: Some(PaymentStatus::Succeeded), - ..PaymentDetailsUpdate::new(payment_id) + } => { + let kind = PaymentKind::Bolt11 { + hash: payment_hash, + preimage: payment_preimage, + secret: Some(payment_secret), + counterparty_skimmed_fee_msat: None, + }; + let update = PaymentDetailsUpdate { + preimage: Some(payment_preimage), + secret: Some(Some(payment_secret)), + amount_msat: Some(Some(amount_msat)), + status: Some(PaymentStatus::Succeeded), + ..PaymentDetailsUpdate::new(payment_id) + }; + (update, kind) }, PaymentPurpose::Bolt12OfferPayment { - payment_preimage, payment_secret, .. - } => PaymentDetailsUpdate { - preimage: Some(payment_preimage), - secret: Some(Some(payment_secret)), - amount_msat: Some(Some(amount_msat)), - status: Some(PaymentStatus::Succeeded), - ..PaymentDetailsUpdate::new(payment_id) + payment_preimage, + payment_secret, + payment_context, + .. + } => { + let kind = PaymentKind::Bolt12Offer { + hash: Some(payment_hash), + preimage: payment_preimage, + secret: Some(payment_secret), + offer_id: payment_context.offer_id, + payer_note: payment_context.invoice_request.payer_note_truncated, + quantity: payment_context.invoice_request.quantity, + }; + let update = PaymentDetailsUpdate { + preimage: Some(payment_preimage), + secret: Some(Some(payment_secret)), + amount_msat: Some(Some(amount_msat)), + status: Some(PaymentStatus::Succeeded), + ..PaymentDetailsUpdate::new(payment_id) + }; + (update, kind) }, PaymentPurpose::Bolt12RefundPayment { payment_preimage, payment_secret, .. - } => PaymentDetailsUpdate { - preimage: Some(payment_preimage), - secret: Some(Some(payment_secret)), - amount_msat: Some(Some(amount_msat)), - status: Some(PaymentStatus::Succeeded), - ..PaymentDetailsUpdate::new(payment_id) + } => { + let kind = PaymentKind::Bolt12Refund { + hash: Some(payment_hash), + preimage: payment_preimage, + secret: Some(payment_secret), + payer_note: None, + quantity: None, + }; + let update = PaymentDetailsUpdate { + preimage: Some(payment_preimage), + secret: Some(Some(payment_secret)), + amount_msat: Some(Some(amount_msat)), + status: Some(PaymentStatus::Succeeded), + ..PaymentDetailsUpdate::new(payment_id) + }; + (update, kind) }, - PaymentPurpose::SpontaneousPayment(preimage) => PaymentDetailsUpdate { - preimage: Some(Some(preimage)), - amount_msat: Some(Some(amount_msat)), - status: Some(PaymentStatus::Succeeded), - ..PaymentDetailsUpdate::new(payment_id) + PaymentPurpose::SpontaneousPayment(preimage) => { + let kind = PaymentKind::Spontaneous { + hash: payment_hash, + preimage: Some(preimage), + }; + let update = PaymentDetailsUpdate { + preimage: Some(Some(preimage)), + amount_msat: Some(Some(amount_msat)), + status: Some(PaymentStatus::Succeeded), + ..PaymentDetailsUpdate::new(payment_id) + }; + (update, kind) }, }; @@ -1077,11 +1300,23 @@ where // be the result of a replayed event. ), Ok(DataStoreUpdateResult::NotFound) => { - log_error!( - self.logger, - "Claimed payment with ID {} couldn't be found in store", + let payment = PaymentDetails::new( payment_id, + kind_for_insert, + Some(amount_msat), + None, + PaymentDirection::Inbound, + PaymentStatus::Succeeded, ); + if let Err(e) = self.payment_store.insert(payment).await { + log_error!( + self.logger, + "Failed to insert payment with ID {}: {}", + payment_id, + e + ); + return Err(ReplayEvent()); + } }, Err(e) => { log_error!( @@ -1095,7 +1330,7 @@ where } let event = Event::PaymentReceived { - payment_id: Some(payment_id), + payment_id, payment_hash, amount_msat, custom_records: onion_fields @@ -1160,7 +1395,7 @@ where ); }); let event = Event::PaymentSuccessful { - payment_id: Some(payment_id), + payment_id, payment_hash, payment_preimage: Some(payment_preimage), fee_paid_msat, @@ -1196,8 +1431,7 @@ where }, }; - let event = - Event::PaymentFailed { payment_id: Some(payment_id), payment_hash, reason }; + let event = Event::PaymentFailed { payment_id, payment_hash, reason }; match self.event_queue.add_event(event).await { Ok(_) => return Ok(()), Err(e) => { diff --git a/src/lib.rs b/src/lib.rs index 34fa7f54d..e43db451e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,8 +176,8 @@ use runtime::Runtime; pub use tokio; use types::{ Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph, - HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper, - Wallet, + HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, PendingPaymentStore, + Router, Scorer, Sweeper, Wallet, }; pub use types::{ ChannelCounterparty, ChannelDetails, CustomTlvRecord, PeerDetails, ReserveType, UserChannelId, @@ -244,6 +244,7 @@ pub struct Node { scorer: Arc>, peer_store: Arc>>, payment_store: Arc, + pending_payment_store: Arc, lnurl_auth: Arc, is_running: Arc>, node_metrics: Arc, @@ -605,6 +606,7 @@ impl Node { Arc::clone(&self.network_graph), Arc::clone(&self.liquidity_source), Arc::clone(&self.payment_store), + Arc::clone(&self.pending_payment_store), Arc::clone(&self.peer_store), Arc::clone(&self.keys_manager), static_invoice_store, @@ -945,9 +947,11 @@ impl Node { Bolt11Payment::new( Arc::clone(&self.runtime), Arc::clone(&self.channel_manager), + Arc::clone(&self.keys_manager), Arc::clone(&self.connection_manager), Arc::clone(&self.liquidity_source), Arc::clone(&self.payment_store), + Arc::clone(&self.pending_payment_store), Arc::clone(&self.peer_store), Arc::clone(&self.config), Arc::clone(&self.is_running), @@ -963,9 +967,11 @@ impl Node { Arc::new(Bolt11Payment::new( Arc::clone(&self.runtime), Arc::clone(&self.channel_manager), + Arc::clone(&self.keys_manager), Arc::clone(&self.connection_manager), Arc::clone(&self.liquidity_source), Arc::clone(&self.payment_store), + Arc::clone(&self.pending_payment_store), Arc::clone(&self.peer_store), Arc::clone(&self.config), Arc::clone(&self.is_running), diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 4503dfa06..876ea3872 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -10,6 +10,7 @@ //! [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md use std::sync::{Arc, RwLock}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; @@ -19,10 +20,11 @@ use lightning::ln::channelmanager::{ }; use lightning::ln::outbound_payment::{Bolt11PaymentError, Retry, RetryableSendFailure}; use lightning::routing::router::{PaymentParameters, RouteParameters, RouteParametersConfig}; +use lightning::sign::EntropySource; use lightning_invoice::{ Bolt11Invoice as LdkBolt11Invoice, Bolt11InvoiceDescription as LdkBolt11InvoiceDescription, }; -use lightning_types::payment::{PaymentHash, PaymentPreimage}; +use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; use crate::connection::ConnectionManager; @@ -35,9 +37,10 @@ use crate::payment::store::{ LSPS2Parameters, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus, }; +use crate::payment::PendingPaymentDetails; use crate::peer_store::{PeerInfo, PeerStore}; use crate::runtime::Runtime; -use crate::types::{ChannelManager, PaymentStore}; +use crate::types::{ChannelManager, KeysManager, PaymentStore, PendingPaymentStore}; #[cfg(not(feature = "uniffi"))] type Bolt11Invoice = LdkBolt11Invoice; @@ -69,9 +72,11 @@ impl_writeable_tlv_based!(PaymentMetadata, { pub struct Bolt11Payment { runtime: Arc, channel_manager: Arc, + keys_manager: Arc, connection_manager: Arc>>, liquidity_source: Arc>>, payment_store: Arc, + pending_payment_store: Arc, peer_store: Arc>>, config: Arc, is_running: Arc>, @@ -81,17 +86,19 @@ pub struct Bolt11Payment { impl Bolt11Payment { pub(crate) fn new( runtime: Arc, channel_manager: Arc, - connection_manager: Arc>>, + keys_manager: Arc, connection_manager: Arc>>, liquidity_source: Arc>>, payment_store: Arc, - peer_store: Arc>>, config: Arc, - is_running: Arc>, logger: Arc, + pending_payment_store: Arc, peer_store: Arc>>, + config: Arc, is_running: Arc>, logger: Arc, ) -> Self { Self { runtime, channel_manager, + keys_manager, connection_manager, liquidity_source, payment_store, + pending_payment_store, peer_store, config, is_running, @@ -99,10 +106,86 @@ impl Bolt11Payment { } } + fn current_time_secs() -> u64 { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::from_secs(0)).as_secs() + } + + fn prune_expired_pending_payments(&self) -> Result<(), Error> { + let now = Self::current_time_secs(); + let expired_payment_ids = self + .pending_payment_store + .list_filter(|payment| payment.has_expired(now)) + .into_iter() + .map(|payment| payment.details.id) + .collect::>(); + + for payment_id in expired_payment_ids { + self.runtime.block_on(self.pending_payment_store.remove(&payment_id))?; + } + + Ok(()) + } + + fn has_pending_or_succeeded_inbound_payment(&self, payment_hash: &PaymentHash) -> bool { + !self + .payment_store + .list_filter(|payment| { + payment.direction == PaymentDirection::Inbound + && matches!(&payment.kind, PaymentKind::Bolt11 { hash, .. } if hash == payment_hash) + && matches!(payment.status, PaymentStatus::Pending | PaymentStatus::Succeeded) + }) + .is_empty() + || !self + .pending_payment_store + .list_filter(|payment| { + payment.details.direction == PaymentDirection::Inbound + && matches!(&payment.details.kind, PaymentKind::Bolt11 { hash, .. } if hash == payment_hash) + && matches!( + payment.details.status, + PaymentStatus::Pending | PaymentStatus::Succeeded + ) + }) + .is_empty() + } + + fn register_manual_claim_invoice( + &self, payment_hash: PaymentHash, amount_msat: Option, payment_secret: PaymentSecret, + expiry_secs: u32, + ) -> Result<(), Error> { + let payment_id = PaymentId(self.keys_manager.get_secure_random_bytes()); + let kind = PaymentKind::Bolt11 { + hash: payment_hash, + preimage: None, + secret: Some(payment_secret), + counterparty_skimmed_fee_msat: None, + }; + let payment = PaymentDetails::new( + payment_id, + kind, + amount_msat, + None, + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); + let expires_at = Some(Self::current_time_secs().saturating_add(expiry_secs as u64)); + let pending_payment = + PendingPaymentDetails::new_with_expiry(payment, Vec::new(), expires_at); + self.runtime.block_on(self.pending_payment_store.insert_or_update(pending_payment))?; + Ok(()) + } + pub(crate) fn receive_inner( &self, amount_msat: Option, invoice_description: &LdkBolt11InvoiceDescription, expiry_secs: u32, manual_claim_payment_hash: Option, ) -> Result { + if let Some(payment_hash) = manual_claim_payment_hash { + self.prune_expired_pending_payments()?; + if self.has_pending_or_succeeded_inbound_payment(&payment_hash) { + log_error!(self.logger, "Payment error: an invoice must not be paid twice."); + return Err(Error::DuplicatePayment); + } + } + let invoice = { let invoice_params = Bolt11InvoiceParameters { amount_msats: amount_msat, @@ -124,41 +207,14 @@ impl Bolt11Payment { } }; - let payment_hash = invoice.payment_hash(); - let payment_secret = invoice.payment_secret(); - let id = PaymentId(payment_hash.0); - let preimage = if manual_claim_payment_hash.is_none() { - // If the user hasn't registered a custom payment hash, we're positive ChannelManager - // will know the preimage at this point. - let mut payment_metadata = invoice.payment_metadata().cloned(); - let res = self - .channel_manager - .get_payment_preimage_decrypt_metadata( - payment_hash, - payment_secret.clone(), - payment_metadata.as_deref_mut(), - ) - .ok(); - debug_assert!(res.is_some(), "We just let ChannelManager create an inbound payment, it can't have forgotten the preimage by now."); - res - } else { - None - }; - let kind = PaymentKind::Bolt11 { - hash: payment_hash, - preimage, - secret: Some(payment_secret.clone()), - counterparty_skimmed_fee_msat: None, - }; - let payment = PaymentDetails::new( - id, - kind, - amount_msat, - None, - PaymentDirection::Inbound, - PaymentStatus::Pending, - ); - self.runtime.block_on(self.payment_store.insert(payment))?; + if let Some(payment_hash) = manual_claim_payment_hash { + self.register_manual_claim_invoice( + payment_hash, + amount_msat, + *invoice.payment_secret(), + expiry_secs, + )?; + } Ok(invoice) } @@ -168,6 +224,14 @@ impl Bolt11Payment { expiry_secs: u32, max_total_lsp_fee_limit_msat: Option, max_proportional_lsp_fee_limit_ppm_msat: Option, payment_hash: Option, ) -> Result { + if let Some(payment_hash) = payment_hash { + self.prune_expired_pending_payments()?; + if self.has_pending_or_succeeded_inbound_payment(&payment_hash) { + log_error!(self.logger, "Payment error: an invoice must not be paid twice."); + return Err(Error::DuplicatePayment); + } + } + let connection_manager = Arc::clone(&self.connection_manager); let (invoice, chosen_lsp) = self.runtime.block_on(async move { if let Some(amount_msat) = amount_msat { @@ -196,39 +260,19 @@ impl Bolt11Payment { } })?; - // Register payment in payment store. - let payment_hash = invoice.payment_hash(); - let payment_secret = invoice.payment_secret(); - let id = PaymentId(payment_hash.0); - let mut payment_metadata = invoice.payment_metadata().cloned(); - let preimage = self - .channel_manager - .get_payment_preimage_decrypt_metadata( - payment_hash, - payment_secret.clone(), - payment_metadata.as_deref_mut(), - ) - .ok(); - let kind = PaymentKind::Bolt11 { - hash: payment_hash, - preimage, - secret: Some(payment_secret.clone()), - counterparty_skimmed_fee_msat: None, - }; - let payment = PaymentDetails::new( - id, - kind, - amount_msat, - None, - PaymentDirection::Inbound, - PaymentStatus::Pending, - ); - self.runtime.block_on(self.payment_store.insert(payment))?; - // Persist the chosen LSP peer to make sure we reconnect on restart. let peer_info = PeerInfo { node_id: chosen_lsp.node_id, address: chosen_lsp.address }; self.runtime.block_on(self.peer_store.add_peer(peer_info))?; + if let Some(payment_hash) = payment_hash { + self.register_manual_claim_invoice( + payment_hash, + amount_msat, + *invoice.payment_secret(), + expiry_secs, + )?; + } + Ok(invoice) } } @@ -275,15 +319,7 @@ impl Bolt11Payment { } let payment_hash = invoice.payment_hash(); - let payment_id = PaymentId(invoice.payment_hash().0); - if let Some(payment) = self.payment_store.get(&payment_id) { - if payment.status == PaymentStatus::Pending - || payment.status == PaymentStatus::Succeeded - { - log_error!(self.logger, "Payment error: an invoice must not be paid twice."); - return Err(Error::DuplicatePayment); - } - } + let payment_id = PaymentId(self.keys_manager.get_secure_random_bytes()); let route_params_config = route_parameters.or(self.config.route_parameters).unwrap_or_default(); @@ -490,13 +526,31 @@ impl Bolt11Payment { /// [`receive_variable_amount_for_hash`]: Self::receive_variable_amount_for_hash /// [`PaymentClaimable`]: crate::Event::PaymentClaimable /// [`PaymentReceived`]: crate::Event::PaymentReceived - pub fn claim_for_hash( - &self, payment_hash: PaymentHash, claimable_amount_msat: u64, preimage: PaymentPreimage, + pub fn claim_for_id( + &self, payment_id: PaymentId, claimable_amount_msat: u64, preimage: PaymentPreimage, ) -> Result<(), Error> { - let payment_id = PaymentId(payment_hash.0); + let details = self.payment_store.get(&payment_id).ok_or_else(|| { + log_error!( + self.logger, + "Failed to manually claim unknown payment with ID: {}", + payment_id + ); + Error::InvalidPaymentId + })?; - let expected_payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array()); + let payment_hash = match details.kind { + PaymentKind::Bolt11 { hash, .. } => hash, + _ => { + log_error!( + self.logger, + "Failed to manually claim payment with ID {} of unsupported kind", + payment_id + ); + return Err(Error::InvalidPaymentId); + }, + }; + let expected_payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array()); if expected_payment_hash != payment_hash { log_error!( self.logger, @@ -506,40 +560,30 @@ impl Bolt11Payment { return Err(Error::InvalidPaymentPreimage); } - if let Some(details) = self.payment_store.get(&payment_id) { - // For payments requested via `receive*_via_jit_channel_for_hash()` - // `skimmed_fee_msat` held by LSP must be taken into account. - let skimmed_fee_msat = match details.kind { - PaymentKind::Bolt11 { - counterparty_skimmed_fee_msat: Some(skimmed_fee_msat), - .. - } => skimmed_fee_msat, - _ => 0, - }; - if let Some(invoice_amount_msat) = details.amount_msat { - if claimable_amount_msat < invoice_amount_msat.saturating_sub(skimmed_fee_msat) { - log_error!( - self.logger, - "Failed to manually claim payment {} as the claimable amount is less than expected", - payment_id - ); - return Err(Error::InvalidAmount); - } + // For payments requested via `receive*_via_jit_channel_for_hash()` + // `skimmed_fee_msat` held by LSP must be taken into account. + let skimmed_fee_msat = match details.kind { + PaymentKind::Bolt11 { + counterparty_skimmed_fee_msat: Some(skimmed_fee_msat), .. + } => skimmed_fee_msat, + _ => 0, + }; + if let Some(invoice_amount_msat) = details.amount_msat { + if claimable_amount_msat < invoice_amount_msat.saturating_sub(skimmed_fee_msat) { + log_error!( + self.logger, + "Failed to manually claim payment {} as the claimable amount is less than expected", + payment_id + ); + return Err(Error::InvalidAmount); } - } else { - log_error!( - self.logger, - "Failed to manually claim unknown payment with hash: {}", - payment_hash - ); - return Err(Error::InvalidPaymentHash); } self.channel_manager.claim_funds(preimage); Ok(()) } - /// Allows to manually fail payments with the given hash that have previously + /// Allows to manually fail payments with the given id that have previously /// been registered via [`receive_for_hash`] or [`receive_variable_amount_for_hash`]. /// /// This should be called in reponse to a [`PaymentClaimable`] event if the payment needs to be @@ -552,8 +596,27 @@ impl Bolt11Payment { /// [`receive_for_hash`]: Self::receive_for_hash /// [`receive_variable_amount_for_hash`]: Self::receive_variable_amount_for_hash /// [`PaymentClaimable`]: crate::Event::PaymentClaimable - pub fn fail_for_hash(&self, payment_hash: PaymentHash) -> Result<(), Error> { - let payment_id = PaymentId(payment_hash.0); + pub fn fail_for_id(&self, payment_id: PaymentId) -> Result<(), Error> { + let details = self.payment_store.get(&payment_id).ok_or_else(|| { + log_error!( + self.logger, + "Failed to manually fail unknown payment with ID {}", + payment_id, + ); + Error::InvalidPaymentId + })?; + + let payment_hash = match details.kind { + PaymentKind::Bolt11 { hash, .. } => hash, + _ => { + log_error!( + self.logger, + "Failed to manually fail payment with ID {} of unsupported kind", + payment_id + ); + return Err(Error::InvalidPaymentId); + }, + }; let update = PaymentDetailsUpdate { status: Some(PaymentStatus::Failed), @@ -565,10 +628,10 @@ impl Bolt11Payment { Ok(DataStoreUpdateResult::NotFound) => { log_error!( self.logger, - "Failed to manually fail unknown payment with hash {}", - payment_hash, + "Failed to manually fail unknown payment with ID {}", + payment_id, ); - return Err(Error::InvalidPaymentHash); + return Err(Error::InvalidPaymentId); }, Err(e) => { log_error!( @@ -604,13 +667,13 @@ impl Bolt11Payment { /// the inbound payment arrives. /// /// **Note:** users *MUST* handle this event and claim the payment manually via - /// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given + /// [`claim_for_id`] as soon as they have obtained access to the preimage of the given /// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via - /// [`fail_for_hash`]. + /// [`fail_for_id`]. /// /// [`PaymentClaimable`]: crate::Event::PaymentClaimable - /// [`claim_for_hash`]: Self::claim_for_hash - /// [`fail_for_hash`]: Self::fail_for_hash + /// [`claim_for_id`]: Self::claim_for_id + /// [`fail_for_id`]: Self::fail_for_id pub fn receive_for_hash( &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash, @@ -640,13 +703,13 @@ impl Bolt11Payment { /// the inbound payment arrives. /// /// **Note:** users *MUST* handle this event and claim the payment manually via - /// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given + /// [`claim_for_id`] as soon as they have obtained access to the preimage of the given /// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via - /// [`fail_for_hash`]. + /// [`fail_for_id`]. /// /// [`PaymentClaimable`]: crate::Event::PaymentClaimable - /// [`claim_for_hash`]: Self::claim_for_hash - /// [`fail_for_hash`]: Self::fail_for_hash + /// [`claim_for_id`]: Self::claim_for_id + /// [`fail_for_id`]: Self::fail_for_id pub fn receive_variable_amount_for_hash( &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash, ) -> Result { @@ -695,14 +758,14 @@ impl Bolt11Payment { /// is performed *before* emitting the event. /// /// **Note:** users *MUST* handle this event and claim the payment manually via - /// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given + /// [`claim_for_id`] as soon as they have obtained access to the preimage of the given /// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via - /// [`fail_for_hash`]. + /// [`fail_for_id`]. /// /// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md /// [`PaymentClaimable`]: crate::Event::PaymentClaimable - /// [`claim_for_hash`]: Self::claim_for_hash - /// [`fail_for_hash`]: Self::fail_for_hash + /// [`claim_for_id`]: Self::claim_for_id + /// [`fail_for_id`]: Self::fail_for_id /// [`counterparty_skimmed_fee_msat`]: crate::payment::PaymentKind::Bolt11::counterparty_skimmed_fee_msat pub fn receive_via_jit_channel_for_hash( &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, @@ -762,14 +825,14 @@ impl Bolt11Payment { /// is performed *before* emitting the event. /// /// **Note:** users *MUST* handle this event and claim the payment manually via - /// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given + /// [`claim_for_id`] as soon as they have obtained access to the preimage of the given /// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via - /// [`fail_for_hash`]. + /// [`fail_for_id`]. /// /// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md /// [`PaymentClaimable`]: crate::Event::PaymentClaimable - /// [`claim_for_hash`]: Self::claim_for_hash - /// [`fail_for_hash`]: Self::fail_for_hash + /// [`claim_for_id`]: Self::claim_for_id + /// [`fail_for_id`]: Self::fail_for_id /// [`counterparty_skimmed_fee_msat`]: crate::payment::PaymentKind::Bolt11::counterparty_skimmed_fee_msat pub fn receive_variable_amount_via_jit_channel_for_hash( &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, diff --git a/src/payment/pending_payment_store.rs b/src/payment/pending_payment_store.rs index eb72f89ec..358c07851 100644 --- a/src/payment/pending_payment_store.rs +++ b/src/payment/pending_payment_store.rs @@ -20,22 +20,35 @@ pub struct PendingPaymentDetails { pub details: PaymentDetails, /// Transaction IDs that have replaced or conflict with this payment. pub conflicting_txids: Vec, + /// The timestamp after which this pending payment can be pruned. + pub expires_at: Option, } impl PendingPaymentDetails { pub(crate) fn new(details: PaymentDetails, conflicting_txids: Vec) -> Self { - Self { details, conflicting_txids } + Self::new_with_expiry(details, conflicting_txids, None) + } + + pub(crate) fn new_with_expiry( + details: PaymentDetails, conflicting_txids: Vec, expires_at: Option, + ) -> Self { + Self { details, conflicting_txids, expires_at } } /// Convert to finalized payment for the main payment store pub fn into_payment_details(self) -> PaymentDetails { self.details } + + pub(crate) fn has_expired(&self, now: u64) -> bool { + self.expires_at.map_or(false, |expires_at| expires_at <= now) + } } impl_writeable_tlv_based!(PendingPaymentDetails, { (0, details, required), (2, conflicting_txids, optional_vec), + (4, expires_at, option), }); #[derive(Clone, Debug, PartialEq, Eq)] @@ -43,6 +56,7 @@ pub(crate) struct PendingPaymentDetailsUpdate { pub id: PaymentId, pub payment_update: Option, pub conflicting_txids: Option>, + pub expires_at: Option>, } impl StorableObject for PendingPaymentDetails { @@ -68,6 +82,13 @@ impl StorableObject for PendingPaymentDetails { } } + if let Some(new_expires_at) = update.expires_at { + if self.expires_at != new_expires_at { + self.expires_at = new_expires_at; + updated = true; + } + } + updated } @@ -89,6 +110,11 @@ impl From<&PendingPaymentDetails> for PendingPaymentDetailsUpdate { } else { Some(value.conflicting_txids.clone()) }; - Self { id: value.id(), payment_update: Some(value.details.to_update()), conflicting_txids } + Self { + id: value.id(), + payment_update: Some(value.details.to_update()), + conflicting_txids, + expires_at: Some(value.expires_at), + } } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index adeb327bf..686c50c07 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -222,7 +222,7 @@ macro_rules! expect_payment_received_event { ref e @ Event::PaymentReceived { payment_id, amount_msat, .. } => { println!("{} got event {:?}", $node.node_id(), e); assert_eq!(amount_msat, $amount_msat); - let payment = $node.payment(&payment_id.unwrap()).unwrap(); + let payment = $node.payment(&payment_id).unwrap(); if !matches!(payment.kind, ldk_node::payment::PaymentKind::Onchain { .. }) { assert_eq!(payment.fee_paid_msat, None); } @@ -239,6 +239,36 @@ macro_rules! expect_payment_received_event { pub(crate) use expect_payment_received_event; macro_rules! expect_payment_claimable_event { + ($node:expr, $payment_hash:expr, $claimable_amount_msat:expr) => {{ + let event = tokio::time::timeout( + std::time::Duration::from_secs(crate::common::INTEROP_TIMEOUT_SECS), + $node.next_event_async(), + ) + .await + .unwrap_or_else(|_| { + panic!( + "{} timed out waiting for PaymentClaimable event after 60s", + std::stringify!($node) + ) + }); + match event { + ref e @ Event::PaymentClaimable { + payment_id, + payment_hash, + claimable_amount_msat, + .. + } => { + println!("{} got event {:?}", std::stringify!($node), e); + assert_eq!(payment_hash, $payment_hash); + assert_eq!(claimable_amount_msat, $claimable_amount_msat); + $node.event_handled().unwrap(); + (payment_id, claimable_amount_msat) + }, + ref e => { + panic!("{} got unexpected event!: {:?}", std::stringify!($node), e); + }, + } + }}; ($node:expr, $payment_id:expr, $payment_hash:expr, $claimable_amount_msat:expr) => {{ let event = tokio::time::timeout( std::time::Duration::from_secs(crate::common::INTEROP_TIMEOUT_SECS), @@ -290,7 +320,7 @@ macro_rules! expect_payment_successful_event { if let Some(fee_msat) = $fee_paid_msat { assert_eq!(fee_paid_msat, fee_msat); } - let payment = $node.payment(&$payment_id.unwrap()).unwrap(); + let payment = $node.payment(&$payment_id).unwrap(); assert_eq!(payment.fee_paid_msat, fee_paid_msat); assert_eq!(payment_id, $payment_id); $node.event_handled().unwrap(); @@ -1072,10 +1102,10 @@ pub(crate) async fn do_channel_full_cycle( .unwrap(); println!("\nA send"); - let payment_id = node_a.bolt11_payment().send(&invoice, None).unwrap(); + let outbound_payment_id = node_a.bolt11_payment().send(&invoice, None).unwrap(); assert_eq!(node_a.bolt11_payment().send(&invoice, None), Err(NodeError::DuplicatePayment)); - assert!(!node_a.list_payments_with_filter(|p| p.id == payment_id).is_empty()); + assert!(!node_a.list_payments_with_filter(|p| p.id == outbound_payment_id).is_empty()); let outbound_payments_a = node_a.list_payments_with_filter(|p| { p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) @@ -1095,7 +1125,7 @@ pub(crate) async fn do_channel_full_cycle( let inbound_payments_b = node_b.list_payments_with_filter(|p| { p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) }); - assert_eq!(inbound_payments_b.len(), 1); + assert_eq!(inbound_payments_b.len(), 0); // Verify bolt12_invoice is None for BOLT11 payments match node_a.next_event_async().await { @@ -1108,24 +1138,42 @@ pub(crate) async fn do_channel_full_cycle( panic!("{} got unexpected event!: {:?}", std::stringify!(node_a), e); }, } - expect_event!(node_b, PaymentReceived); - 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!(matches!(node_a.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); - 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!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + let inbound_payment_id = expect_payment_received_event!(node_b, invoice_amount_1_msat); + assert_eq!(node_a.payment(&outbound_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&outbound_payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!( + node_a.payment(&outbound_payment_id).unwrap().amount_msat, + Some(invoice_amount_1_msat) + ); + assert!(matches!( + node_a.payment(&outbound_payment_id).unwrap().kind, + PaymentKind::Bolt11 { .. } + )); + assert_eq!(node_b.payment(&inbound_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&inbound_payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!( + node_b.payment(&inbound_payment_id).unwrap().amount_msat, + Some(invoice_amount_1_msat) + ); + assert!(matches!( + node_b.payment(&inbound_payment_id).unwrap().kind, + PaymentKind::Bolt11 { .. } + )); // Assert we fail duplicate outbound payments and check the status hasn't changed. assert_eq!(Err(NodeError::DuplicatePayment), node_a.bolt11_payment().send(&invoice, None)); - 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_eq!(node_a.payment(&outbound_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&outbound_payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!( + node_a.payment(&outbound_payment_id).unwrap().amount_msat, + Some(invoice_amount_1_msat) + ); + assert_eq!(node_b.payment(&inbound_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&inbound_payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!( + node_b.payment(&inbound_payment_id).unwrap().amount_msat, + Some(invoice_amount_1_msat) + ); // Test under-/overpayment let invoice_amount_2_msat = 2500_000; @@ -1148,28 +1196,40 @@ pub(crate) async fn do_channel_full_cycle( let overpaid_amount_msat = invoice_amount_2_msat + 100; println!("\nA overpaid send"); - let payment_id = + let outbound_payment_id = node_a.bolt11_payment().send_using_amount(&invoice, overpaid_amount_msat, None).unwrap(); expect_event!(node_a, PaymentSuccessful); - let received_amount = match node_b.next_event_async().await { - ref e @ Event::PaymentReceived { amount_msat, .. } => { + let (inbound_payment_id, received_amount) = match node_b.next_event_async().await { + ref e @ Event::PaymentReceived { payment_id, amount_msat, .. } => { println!("{} got event {:?}", std::stringify!(node_b), e); node_b.event_handled().unwrap(); - amount_msat + (payment_id, amount_msat) }, ref e => { panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e); }, }; assert_eq!(received_amount, 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!(matches!(node_a.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); - 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)); - assert!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + assert_eq!(node_a.payment(&outbound_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&outbound_payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!( + node_a.payment(&outbound_payment_id).unwrap().amount_msat, + Some(overpaid_amount_msat) + ); + assert!(matches!( + node_a.payment(&outbound_payment_id).unwrap().kind, + PaymentKind::Bolt11 { .. } + )); + assert_eq!(node_b.payment(&inbound_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&inbound_payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!( + node_b.payment(&inbound_payment_id).unwrap().amount_msat, + Some(overpaid_amount_msat) + ); + assert!(matches!( + node_b.payment(&inbound_payment_id).unwrap().kind, + PaymentKind::Bolt11 { .. } + )); // Test "zero-amount" invoice payment println!("\nB receive_variable_amount_payment"); @@ -1183,31 +1243,43 @@ pub(crate) async fn do_channel_full_cycle( node_a.bolt11_payment().send(&variable_amount_invoice, None) ); println!("\nA send_using_amount"); - let payment_id = node_a + let outbound_payment_id = node_a .bolt11_payment() .send_using_amount(&variable_amount_invoice, determined_amount_msat, None) .unwrap(); expect_event!(node_a, PaymentSuccessful); - let received_amount = match node_b.next_event_async().await { - ref e @ Event::PaymentReceived { amount_msat, .. } => { + let (inbound_payment_id, received_amount) = match node_b.next_event_async().await { + ref e @ Event::PaymentReceived { payment_id, amount_msat, .. } => { println!("{} got event {:?}", std::stringify!(node_b), e); node_b.event_handled().unwrap(); - amount_msat + (payment_id, amount_msat) }, ref e => { panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e); }, }; assert_eq!(received_amount, 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!(matches!(node_a.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); - 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)); - assert!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + assert_eq!(node_a.payment(&outbound_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&outbound_payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!( + node_a.payment(&outbound_payment_id).unwrap().amount_msat, + Some(determined_amount_msat) + ); + assert!(matches!( + node_a.payment(&outbound_payment_id).unwrap().kind, + PaymentKind::Bolt11 { .. } + )); + assert_eq!(node_b.payment(&inbound_payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&inbound_payment_id).unwrap().direction, PaymentDirection::Inbound); + assert_eq!( + node_b.payment(&inbound_payment_id).unwrap().amount_msat, + Some(determined_amount_msat) + ); + assert!(matches!( + node_b.payment(&inbound_payment_id).unwrap().kind, + PaymentKind::Bolt11 { .. } + )); // Test claiming manually registered payments. let invoice_amount_3_msat = 5_532_000; @@ -1222,27 +1294,43 @@ pub(crate) async fn do_channel_full_cycle( manual_payment_hash, ) .unwrap(); - let manual_payment_id = node_a.bolt11_payment().send(&manual_invoice, None).unwrap(); + assert!(matches!( + node_b.bolt11_payment().receive_for_hash( + invoice_amount_3_msat, + &invoice_description.clone().into(), + 9217, + manual_payment_hash, + ), + Err(NodeError::DuplicatePayment) + )); + let outbound_manual_payment_id = node_a.bolt11_payment().send(&manual_invoice, None).unwrap(); - let claimable_amount_msat = expect_payment_claimable_event!( - node_b, - manual_payment_id, - manual_payment_hash, - invoice_amount_3_msat - ); + let (manual_payment_id, claimable_amount_msat) = + expect_payment_claimable_event!(node_b, manual_payment_hash, invoice_amount_3_msat); + assert_ne!(manual_payment_id.0, manual_payment_hash.0); node_b .bolt11_payment() - .claim_for_hash(manual_payment_hash, claimable_amount_msat, manual_preimage) + .claim_for_id(manual_payment_id, claimable_amount_msat, manual_preimage) .unwrap(); - expect_payment_received_event!(node_b, claimable_amount_msat); - expect_payment_successful_event!(node_a, Some(manual_payment_id), None); - assert_eq!(node_a.payment(&manual_payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&manual_payment_id).unwrap().direction, PaymentDirection::Outbound); + let received_payment_id = expect_payment_received_event!(node_b, claimable_amount_msat); + assert_eq!(received_payment_id, manual_payment_id); + expect_payment_successful_event!(node_a, outbound_manual_payment_id, None); + assert_eq!( + node_a.payment(&outbound_manual_payment_id).unwrap().status, + PaymentStatus::Succeeded + ); + assert_eq!( + node_a.payment(&outbound_manual_payment_id).unwrap().direction, + PaymentDirection::Outbound + ); assert_eq!( - node_a.payment(&manual_payment_id).unwrap().amount_msat, + node_a.payment(&outbound_manual_payment_id).unwrap().amount_msat, Some(invoice_amount_3_msat) ); - assert!(matches!(node_a.payment(&manual_payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + assert!(matches!( + node_a.payment(&outbound_manual_payment_id).unwrap().kind, + PaymentKind::Bolt11 { .. } + )); assert_eq!(node_b.payment(&manual_payment_id).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_b.payment(&manual_payment_id).unwrap().direction, PaymentDirection::Inbound); assert_eq!( @@ -1265,27 +1353,28 @@ pub(crate) async fn do_channel_full_cycle( manual_fail_payment_hash, ) .unwrap(); - let manual_fail_payment_id = node_a.bolt11_payment().send(&manual_fail_invoice, None).unwrap(); + let outbound_manual_fail_payment_id = + node_a.bolt11_payment().send(&manual_fail_invoice, None).unwrap(); - expect_payment_claimable_event!( - node_b, - manual_fail_payment_id, - manual_fail_payment_hash, - invoice_amount_4_msat - ); - node_b.bolt11_payment().fail_for_hash(manual_fail_payment_hash).unwrap(); + let (manual_fail_payment_id, _) = + expect_payment_claimable_event!(node_b, manual_fail_payment_hash, invoice_amount_4_msat); + assert_ne!(manual_fail_payment_id.0, manual_fail_payment_hash.0); + node_b.bolt11_payment().fail_for_id(manual_fail_payment_id).unwrap(); expect_event!(node_a, PaymentFailed); - assert_eq!(node_a.payment(&manual_fail_payment_id).unwrap().status, PaymentStatus::Failed); assert_eq!( - node_a.payment(&manual_fail_payment_id).unwrap().direction, + node_a.payment(&outbound_manual_fail_payment_id).unwrap().status, + PaymentStatus::Failed + ); + assert_eq!( + node_a.payment(&outbound_manual_fail_payment_id).unwrap().direction, PaymentDirection::Outbound ); assert_eq!( - node_a.payment(&manual_fail_payment_id).unwrap().amount_msat, + node_a.payment(&outbound_manual_fail_payment_id).unwrap().amount_msat, Some(invoice_amount_4_msat) ); assert!(matches!( - node_a.payment(&manual_fail_payment_id).unwrap().kind, + node_a.payment(&outbound_manual_fail_payment_id).unwrap().kind, PaymentKind::Bolt11 { .. } )); assert_eq!(node_b.payment(&manual_fail_payment_id).unwrap().status, PaymentStatus::Failed); @@ -1312,16 +1401,19 @@ pub(crate) async fn do_channel_full_cycle( .unwrap(); expect_event!(node_a, PaymentSuccessful); let next_event = node_b.next_event_async().await; - let (received_keysend_amount, received_custom_records) = match next_event { - ref e @ Event::PaymentReceived { amount_msat, ref custom_records, .. } => { - println!("{} got event {:?}", std::stringify!(node_b), e); - node_b.event_handled().unwrap(); - (amount_msat, custom_records) - }, - ref e => { - panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e); - }, - }; + let (received_keysend_payment_id, received_keysend_amount, received_custom_records) = + match next_event { + ref e @ Event::PaymentReceived { + payment_id, amount_msat, ref custom_records, .. + } => { + println!("{} got event {:?}", std::stringify!(node_b), e); + node_b.event_handled().unwrap(); + (payment_id, amount_msat, custom_records) + }, + ref e => { + panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e); + }, + }; assert_eq!(received_keysend_amount, 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); @@ -1331,11 +1423,20 @@ pub(crate) async fn do_channel_full_cycle( PaymentKind::Spontaneous { .. } )); assert_eq!(received_custom_records, &custom_tlvs); - 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)); + assert_eq!( + node_b.payment(&received_keysend_payment_id).unwrap().status, + PaymentStatus::Succeeded + ); + assert_eq!( + node_b.payment(&received_keysend_payment_id).unwrap().direction, + PaymentDirection::Inbound + ); + assert_eq!( + node_b.payment(&received_keysend_payment_id).unwrap().amount_msat, + Some(keysend_amount_msat) + ); assert!(matches!( - node_b.payment(&keysend_payment_id).unwrap().kind, + node_b.payment(&received_keysend_payment_id).unwrap().kind, PaymentKind::Spontaneous { .. } )); assert_eq!( @@ -1344,7 +1445,7 @@ pub(crate) async fn do_channel_full_cycle( ); assert_eq!( node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(), - 6 + 5 ); assert_eq!( node_a diff --git a/tests/integration_tests_hrn.rs b/tests/integration_tests_hrn.rs index 910240039..8f910b860 100644 --- a/tests/integration_tests_hrn.rs +++ b/tests/integration_tests_hrn.rs @@ -79,5 +79,5 @@ async fn unified_send_to_hrn() { }, }; - expect_payment_successful_event!(node_a, Some(offer_payment_id), None); + expect_payment_successful_event!(node_a, offer_payment_id, None); } diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index fab73ed0c..9490adc26 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -290,7 +290,7 @@ async fn multi_hop_sending() { .bolt11_payment() .receive(2_500_000, &invoice_description.clone().into(), 9217) .unwrap(); - nodes[0].bolt11_payment().send(&invoice, Some(route_params)).unwrap(); + let outbound_payment_id = nodes[0].bolt11_payment().send(&invoice, Some(route_params)).unwrap(); expect_event!(nodes[1], PaymentForwarded); @@ -299,9 +299,9 @@ async fn multi_hop_sending() { let node_3_fwd_event = matches!(nodes[3].next_event(), Some(Event::PaymentForwarded { .. })); assert!(node_2_fwd_event || node_3_fwd_event); - let payment_id = expect_payment_received_event!(&nodes[4], 2_500_000); + expect_payment_received_event!(&nodes[4], 2_500_000); let fee_paid_msat = Some(2000); - expect_payment_successful_event!(nodes[0], payment_id, Some(fee_paid_msat)); + expect_payment_successful_event!(nodes[0], outbound_payment_id, Some(fee_paid_msat)); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -380,13 +380,11 @@ async fn split_underpaid_bolt11_payment() { .unwrap(); let receiver_payment_id = expect_payment_received_event!(node_c, amount_msat); - assert_eq!(receiver_payment_id, Some(PaymentId(invoice.payment_hash().0))); - expect_payment_successful_event!(node_a, Some(payment_id_a), None); - expect_payment_successful_event!(node_b, Some(payment_id_b), None); + expect_payment_successful_event!(node_a, payment_id_a, None); + expect_payment_successful_event!(node_b, payment_id_b, None); // The receiver records the full invoice amount; each payer records only its own half. - let receiver_payments = - node_c.list_payments_with_filter(|p| p.id == receiver_payment_id.unwrap()); + let receiver_payments = node_c.list_payments_with_filter(|p| p.id == receiver_payment_id); assert_eq!(receiver_payments.len(), 1); assert_eq!(receiver_payments.first().unwrap().amount_msat, Some(amount_msat)); @@ -1202,7 +1200,7 @@ async fn splice_channel() { let payment_id = node_b.spontaneous_payment().send(amount_msat, node_a.node_id(), None).unwrap(); - expect_payment_successful_event!(node_b, Some(payment_id), None); + expect_payment_successful_event!(node_b, payment_id, None); expect_payment_received_event!(node_a, amount_msat); // Mine a block to give time for the HTLC to resolve @@ -1298,7 +1296,7 @@ async fn simple_bolt12_send_receive() { match event { ref e @ Event::PaymentSuccessful { payment_id: ref evt_id, ref bolt12_invoice, .. } => { println!("{} got event {:?}", node_a.node_id(), e); - assert_eq!(*evt_id, Some(payment_id)); + assert_eq!(*evt_id, payment_id); assert!( bolt12_invoice.is_some(), "bolt12_invoice should be present for BOLT12 payments" @@ -1372,12 +1370,12 @@ async fn simple_bolt12_send_receive() { ) .unwrap(); - expect_payment_successful_event!(node_a, Some(payment_id), None); + expect_payment_successful_event!(node_a, payment_id, None); let node_a_payments = node_a.list_payments_with_filter(|p| { 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 { + match node_a_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { hash, preimage, @@ -1393,7 +1391,6 @@ async fn simple_bolt12_send_receive() { 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 // API currently doesn't allow to do that. - hash.unwrap() }, _ => { panic!("Unexpected payment kind"); @@ -1401,8 +1398,7 @@ async fn simple_bolt12_send_receive() { }; assert_eq!(node_a_payments.first().unwrap().amount_msat, Some(expected_amount_msat)); - expect_payment_received_event!(node_b, expected_amount_msat); - let node_b_payment_id = PaymentId(payment_hash.0); + let node_b_payment_id = expect_payment_received_event!(node_b, expected_amount_msat); let node_b_payments = node_b.list_payments_with_filter(|p| { matches!(p.kind, PaymentKind::Bolt12Offer { .. }) && p.id == node_b_payment_id }); @@ -1434,8 +1430,8 @@ async fn simple_bolt12_send_receive() { None, ) .unwrap(); - let invoice = node_a.bolt12_payment().request_refund_payment(&refund).unwrap(); - expect_payment_received_event!(node_a, overpaid_amount); + let _invoice = node_a.bolt12_payment().request_refund_payment(&refund).unwrap(); + let node_a_payment_id = expect_payment_received_event!(node_a, overpaid_amount); let node_b_payment_id = node_b .list_payments_with_filter(|p| { @@ -1445,7 +1441,7 @@ async fn simple_bolt12_send_receive() { .first() .unwrap() .id; - expect_payment_successful_event!(node_b, Some(node_b_payment_id), None); + expect_payment_successful_event!(node_b, node_b_payment_id, None); let node_b_payments = node_b.list_payments_with_filter(|p| { matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_b_payment_id @@ -1472,7 +1468,6 @@ async fn simple_bolt12_send_receive() { } assert_eq!(node_b_payments.first().unwrap().amount_msat, Some(overpaid_amount)); - let node_a_payment_id = PaymentId(invoice.payment_hash().0); let node_a_payments = node_a.list_payments_with_filter(|p| { matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_a_payment_id }); @@ -1619,7 +1614,7 @@ async fn async_payment() { node_receiver.start().unwrap(); - expect_payment_successful_event!(node_sender, Some(payment_id), None); + expect_payment_successful_event!(node_sender, payment_id, None); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -1845,7 +1840,7 @@ async fn unified_send_receive_bip21_uri() { }, }; - expect_payment_successful_event!(node_a, Some(offer_payment_id), None); + expect_payment_successful_event!(node_a, offer_payment_id, None); // Cut off the BOLT12 part to fallback to BOLT11. let uri_str_without_offer = uri_str.split("&lno=").next().unwrap(); @@ -1865,7 +1860,7 @@ async fn unified_send_receive_bip21_uri() { panic!("Expected Bolt11 payment but got error: {:?}", e); }, }; - expect_payment_successful_event!(node_a, Some(invoice_payment_id), None); + expect_payment_successful_event!(node_a, invoice_payment_id, None); let expect_onchain_amount_sats = 800_000; let onchain_uni_payment = @@ -1991,7 +1986,7 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { // Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node. println!("Paying JIT invoice!"); - let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap(); + let payer_payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap(); expect_channel_pending_event!(service_node, client_node.node_id()); expect_channel_ready_event!(service_node, client_node.node_id()); expect_event!(service_node, PaymentForwarded); @@ -2000,9 +1995,9 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { let service_fee_msat = (jit_amount_msat * channel_opening_fee_ppm as u64) / 1_000_000; let expected_received_amount_msat = jit_amount_msat - service_fee_msat; - expect_payment_successful_event!(payer_node, Some(payment_id), None); + expect_payment_successful_event!(payer_node, payer_payment_id, None); let client_payment_id = - expect_payment_received_event!(client_node, expected_received_amount_msat).unwrap(); + expect_payment_received_event!(client_node, expected_received_amount_msat); let client_payment = client_node.payment(&client_payment_id).unwrap(); match client_payment.kind { PaymentKind::Bolt11 { counterparty_skimmed_fee_msat, .. } => { @@ -2029,12 +2024,12 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { // are working as expected. println!("Paying regular invoice!"); let payment_id = payer_node.bolt11_payment().send(&invoice, None).unwrap(); - expect_payment_successful_event!(payer_node, Some(payment_id), None); + expect_payment_successful_event!(payer_node, payment_id, None); expect_event!(service_node, PaymentForwarded); expect_payment_received_event!(client_node, amount_msat); //////////////////////////////////////////////////////////////////////////// - // receive_via_jit_channel_for_hash and claim_for_hash + // receive_via_jit_channel_for_hash and claim_for_id //////////////////////////////////////////////////////////////////////////// println!("Generating JIT invoice!"); // Increase the amount to make sure it does not fit into the existing channels. @@ -2054,7 +2049,7 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { // Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node. println!("Paying JIT invoice!"); - let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap(); + let payer_payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap(); expect_channel_pending_event!(service_node, client_node.node_id()); expect_channel_ready_event!(service_node, client_node.node_id()); expect_channel_pending_event!(client_node, service_node.node_id()); @@ -2062,22 +2057,23 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { let service_fee_msat = (jit_amount_msat * channel_opening_fee_ppm as u64) / 1_000_000; let expected_received_amount_msat = jit_amount_msat - service_fee_msat; - let claimable_amount_msat = expect_payment_claimable_event!( + let (client_payment_id, claimable_amount_msat) = expect_payment_claimable_event!( client_node, - payment_id, manual_payment_hash, expected_received_amount_msat ); + assert_ne!(client_payment_id.0, manual_payment_hash.0); println!("Claiming payment!"); client_node .bolt11_payment() - .claim_for_hash(manual_payment_hash, claimable_amount_msat, manual_preimage) + .claim_for_id(client_payment_id, claimable_amount_msat, manual_preimage) .unwrap(); expect_event!(service_node, PaymentForwarded); - expect_payment_successful_event!(payer_node, Some(payment_id), None); - let client_payment_id = - expect_payment_received_event!(client_node, expected_received_amount_msat).unwrap(); + expect_payment_successful_event!(payer_node, payer_payment_id, None); + let received_payment_id = + expect_payment_received_event!(client_node, expected_received_amount_msat); + assert_eq!(received_payment_id, client_payment_id); let client_payment = client_node.payment(&client_payment_id).unwrap(); match client_payment.kind { PaymentKind::Bolt11 { counterparty_skimmed_fee_msat, .. } => { @@ -2087,7 +2083,7 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { } //////////////////////////////////////////////////////////////////////////// - // receive_via_jit_channel_for_hash and fail_for_hash + // receive_via_jit_channel_for_hash and fail_for_id //////////////////////////////////////////////////////////////////////////// println!("Generating JIT invoice!"); // Increase the amount to make sure it does not fit into the existing channels. @@ -2107,7 +2103,7 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { // Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node. println!("Paying JIT invoice!"); - let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap(); + let _payer_payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap(); expect_channel_pending_event!(service_node, client_node.node_id()); expect_channel_ready_event!(service_node, client_node.node_id()); expect_channel_pending_event!(client_node, service_node.node_id()); @@ -2115,17 +2111,17 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { let service_fee_msat = (jit_amount_msat * channel_opening_fee_ppm as u64) / 1_000_000; let expected_received_amount_msat = jit_amount_msat - service_fee_msat; - expect_payment_claimable_event!( + let (client_payment_id, _) = expect_payment_claimable_event!( client_node, - payment_id, manual_payment_hash, expected_received_amount_msat ); + assert_ne!(client_payment_id.0, manual_payment_hash.0); println!("Failing payment!"); - client_node.bolt11_payment().fail_for_hash(manual_payment_hash).unwrap(); + client_node.bolt11_payment().fail_for_id(client_payment_id).unwrap(); expect_event!(payer_node, PaymentFailed); - assert_eq!(client_node.payment(&payment_id).unwrap().status, PaymentStatus::Failed); + assert_eq!(client_node.payment(&client_payment_id).unwrap().status, PaymentStatus::Failed); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -2182,7 +2178,7 @@ async fn spontaneous_send_with_custom_preimage() { .unwrap(); // check payment status and verify stored preimage - expect_payment_successful_event!(node_a, Some(payment_id), None); + expect_payment_successful_event!(node_a, 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); @@ -2317,8 +2313,8 @@ async fn lsps2_client_trusts_lsp() { // Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node. println!("Paying JIT invoice!"); - let payment_id = payer_node.bolt11_payment().send(&res, None).unwrap(); - println!("Payment ID: {:?}", payment_id); + let payer_payment_id = payer_node.bolt11_payment().send(&res, None).unwrap(); + println!("Payment ID: {:?}", payer_payment_id); let funding_txo = expect_channel_pending_event!(service_node, client_node.node_id()); expect_channel_ready_event!(service_node, client_node.node_id()); expect_channel_pending_event!(client_node, service_node.node_id()); @@ -2356,21 +2352,22 @@ async fn lsps2_client_trusts_lsp() { let service_fee_msat = (jit_amount_msat * channel_opening_fee_ppm as u64) / 1_000_000; let expected_received_amount_msat = jit_amount_msat - service_fee_msat; - let _ = expect_payment_claimable_event!( + let (client_payment_id, _) = expect_payment_claimable_event!( client_node, - payment_id, manual_payment_hash, expected_received_amount_msat ); client_node .bolt11_payment() - .claim_for_hash(manual_payment_hash, jit_amount_msat, manual_preimage) + .claim_for_id(client_payment_id, jit_amount_msat, manual_preimage) .unwrap(); - expect_payment_successful_event!(payer_node, Some(payment_id), None); + expect_payment_successful_event!(payer_node, payer_payment_id, None); - let _ = expect_payment_received_event!(client_node, expected_received_amount_msat).unwrap(); + let received_payment_id = + expect_payment_received_event!(client_node, expected_received_amount_msat); + assert_eq!(received_payment_id, client_payment_id); // Check the nodes pick up on the confirmed funding tx now. wait_for_tx(&electrsd.client, funding_txo.txid).await; diff --git a/tests/upgrade_downgrade_tests.rs b/tests/upgrade_downgrade_tests.rs index f07e49427..e39da6e26 100644 --- a/tests/upgrade_downgrade_tests.rs +++ b/tests/upgrade_downgrade_tests.rs @@ -302,7 +302,7 @@ async fn expect_current_payment_successful( ) { match next_current_event(node).await { ldk_node::Event::PaymentSuccessful { payment_id, .. } => { - assert_eq!(payment_id.as_ref(), Some(expected_payment_id)); + assert_eq!(&payment_id, expected_payment_id); node.event_handled().unwrap(); }, event => panic!("{} got unexpected event: {:?}", node.node_id(), event), @@ -311,9 +311,8 @@ async fn expect_current_payment_successful( async fn expect_current_payment_received(node: &CurrentNode, expected_amount_msat: u64) { match next_current_event(node).await { - ldk_node::Event::PaymentReceived { amount_msat, payment_id, .. } => { + ldk_node::Event::PaymentReceived { amount_msat, .. } => { assert_eq!(amount_msat, expected_amount_msat); - assert!(payment_id.is_some()); node.event_handled().unwrap(); }, event => panic!("{} got unexpected event: {:?}", node.node_id(), event),