diff --git a/CHANGELOG.md b/CHANGELOG.md index 115b0ed058..69ccd4e81f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - `EsploraSyncConfig` and `ElectrumSyncConfig` now support `force_wallet_full_scan`. When set, the on-chain wallet keeps using BDK `full_scan` instead of incremental sync until a full scan succeeds, allowing restored wallets to rediscover funds sent to previously-unknown addresses. +- `Node::list_payments` now retrieves payments page-by-page, ordered from most recently created to + least recently created, instead of returning all payments at once. ## Bug Fixes and Improvements - Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the diff --git a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt index 006878a4c8..e3c4f2b42b 100644 --- a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt +++ b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt @@ -301,8 +301,8 @@ class LibraryTest { assert(paymentReceivedEvent is Event.PaymentReceived) node2.eventHandled() - assert(node1.listPayments().size == 3) - assert(node2.listPayments().size == 2) + assert(node1.listPayments(null).payments.size == 3) + assert(node2.listPayments(null).payments.size == 2) node2.closeChannel(userChannelId, nodeId1) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 7c0edc5359..4177930a20 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -136,7 +136,8 @@ interface Node { [Throws=NodeError] void remove_payment([ByRef]PaymentId payment_id); BalanceDetails list_balances(); - sequence list_payments(); + [Throws=NodeError] + PaymentDetailsPage list_payments(PageToken? page_token); sequence list_peers(); sequence list_channels(); NetworkGraph network_graph(); @@ -261,6 +262,11 @@ enum PaymentFailureReason { typedef dictionary PaymentDetails; +typedef dictionary PaymentDetailsPage; + +[Remote] +interface PageToken {}; + [Remote] dictionary RouteParametersConfig { u64? max_total_routing_fee_msat; diff --git a/src/data_store.rs b/src/data_store.rs index b1ed816df9..4d8f63b8ed 100644 --- a/src/data_store.rs +++ b/src/data_store.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use std::ops::Deref; use std::sync::{Arc, Mutex}; -use lightning::util::persist::KVStore; +use lightning::util::persist::{KVStore, PageToken, PaginatedKVStore}; use lightning::util::ser::{Readable, Writeable}; use crate::logger::{log_error, LdkLogger}; @@ -179,6 +179,47 @@ where self.objects.lock().expect("lock").values().filter(f).cloned().collect::>() } + /// Returns a page of objects, ordered from most recently created to least recently created, + /// together with a token that can be passed to a subsequent call to retrieve the next page. + /// + /// The underlying store is only queried for the page's key order, which the in-memory map + /// doesn't track; the objects themselves are served from memory. + pub(crate) async fn list_page( + &self, page_token: Option, + ) -> Result<(Vec, Option), Error> { + let response = PaginatedKVStore::list_paginated( + &*self.kv_store, + &self.primary_namespace, + &self.secondary_namespace, + page_token, + ) + .await + .map_err(|e| { + log_error!( + self.logger, + "Listing object data under {}/{} failed due to: {}", + &self.primary_namespace, + &self.secondary_namespace, + e + ); + Error::PersistenceFailed + })?; + + let locked_objects = self.objects.lock().expect("lock"); + let objects_by_store_key: HashMap = + locked_objects.values().map(|obj| (obj.id().encode_to_hex_str(), obj)).collect(); + + // Objects removed between listing the page's keys and this lookup are skipped rather + // than failing the page. + let objects = response + .keys + .iter() + .filter_map(|key| objects_by_store_key.get(key).map(|obj| (*obj).clone())) + .collect(); + + Ok((objects, response.next_page_token)) + } + async fn persist(&self, object: &SO) -> Result<(), Error> { let (store_key, data) = Self::encode_object(object); self.persist_encoded(store_key, data).await @@ -337,6 +378,44 @@ mod tests { ) } + #[tokio::test] + async fn list_page_paginates_in_reverse_creation_order() { + let store: Arc = Arc::new(DynStoreWrapper(InMemoryStore::new())); + let logger = Arc::new(TestLogger::new()); + let data_store: DataStore> = DataStore::new( + Vec::new(), + "datastore_test_primary".to_string(), + "datastore_test_secondary".to_string(), + Arc::clone(&store), + logger, + ); + + // Insert more objects than fit in a single page to exercise the pagination loop. + let num_objects = 120u32; + for i in 0..num_objects { + let id = TestObjectId { id: i.to_be_bytes() }; + data_store.insert(TestObject { id, data: [7u8; 3] }).await.unwrap(); + } + + let mut listed = Vec::with_capacity(num_objects as usize); + let mut page_token = None; + loop { + let (page, next_page_token) = data_store.list_page(page_token).await.unwrap(); + assert!(!page.is_empty()); + listed.extend(page); + page_token = next_page_token; + if page_token.is_none() { + break; + } + } + + let expected: Vec = (0..num_objects) + .rev() + .map(|i| TestObject { id: TestObjectId { id: i.to_be_bytes() }, data: [7u8; 3] }) + .collect(); + assert_eq!(listed, expected); + } + #[tokio::test] async fn data_is_persisted() { let store: Arc = Arc::new(DynStoreWrapper(InMemoryStore::new())); diff --git a/src/lib.rs b/src/lib.rs index a2c4cf3be1..754bfa2f4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,6 +156,7 @@ use lightning::ln::peer_handler::CustomMessageHandler; use lightning::routing::gossip::NodeAlias; use lightning::sign::EntropySource; use lightning::util::persist::KVStore; +pub use lightning::util::persist::PageToken; use lightning::util::wallet_utils::{Input, Wallet as LdkWallet}; use lightning_background_processor::process_events_async; pub use lightning_invoice; @@ -2127,15 +2128,44 @@ impl Node { /// # let node = builder.build(node_entropy.into()).unwrap(); /// node.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound); /// ``` + #[deprecated( + note = "Use the paginated list_payments API and filter the returned pages instead." + )] pub fn list_payments_with_filter bool>( &self, f: F, ) -> Vec { self.payment_store.list_filter(f) } - /// Retrieves all payments. - pub fn list_payments(&self) -> Vec { - self.payment_store.list_filter(|_| true) + /// Retrieves a page of payments from the underlying paginated store, ordered from most + /// recently created to least recently created. + /// + /// Pass `None` to start listing from the most recently created payment. If the returned + /// [`PaymentDetailsPage::next_page_token`] is `Some`, pass it to a subsequent call to + /// retrieve the next page. + #[cfg(not(feature = "uniffi"))] + pub fn list_payments( + &self, page_token: Option, + ) -> Result { + let (payments, next_page_token) = + self.runtime.block_on(self.payment_store.list_page(page_token))?; + Ok(PaymentDetailsPage { payments, next_page_token }) + } + + /// Retrieves a page of payments from the underlying paginated store, ordered from most + /// recently created to least recently created. + /// + /// Pass `None` to start listing from the most recently created payment. If the returned + /// [`PaymentDetailsPage::next_page_token`] is `Some`, pass it to a subsequent call to + /// retrieve the next page. + #[cfg(feature = "uniffi")] + pub fn list_payments( + &self, page_token: Option>, + ) -> Result { + let page_token = page_token.map(|t| (*t).clone()); + let (payments, next_page_token) = + self.runtime.block_on(self.payment_store.list_page(page_token))?; + Ok(PaymentDetailsPage { payments, next_page_token: next_page_token.map(Arc::new) }) } /// Retrieves a list of known peers. @@ -2253,6 +2283,20 @@ impl Drop for Node { } } +/// A page of payments returned from a paginated listing. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct PaymentDetailsPage { + /// Payments in this page, ordered from most recently created to least recently created. + pub payments: Vec, + /// Token to pass to the next call to continue listing, if another page exists. + #[cfg(not(feature = "uniffi"))] + pub next_page_token: Option, + /// Token to pass to the next call to continue listing, if another page exists. + #[cfg(feature = "uniffi")] + pub next_page_token: Option>, +} + /// The best known block as identified by its hash and height. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 518d09bf3c..208afecefe 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -43,7 +43,9 @@ use ldk_node::config::{ }; use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy}; use ldk_node::io::sqlite_store::SqliteStore; -use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus, TransactionType}; +use ldk_node::payment::{ + PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, TransactionType, +}; use ldk_node::{ Builder, ChannelShutdownState, CustomTlvRecord, Event, LightningBalance, Node, NodeError, PendingSweepBalance, UserChannelId, @@ -67,6 +69,48 @@ use in_memory_store::InMemoryStore; /// Shared timeout (in seconds) for waiting on LDK events and external node operations. pub(crate) const INTEROP_TIMEOUT_SECS: u64 = 60; +pub(crate) trait NodePaymentExt { + fn list_first_page_payments(&self) -> Vec; + fn list_first_page_payments_with_filter bool>( + &self, f: F, + ) -> Vec; + fn list_all_payments(&self) -> Vec; + fn list_all_payments_with_filter bool>( + &self, f: F, + ) -> Vec; +} + +impl NodePaymentExt for Node { + fn list_first_page_payments(&self) -> Vec { + self.list_payments(None).unwrap().payments + } + + fn list_first_page_payments_with_filter bool>( + &self, mut f: F, + ) -> Vec { + self.list_first_page_payments().into_iter().filter(|p| f(p)).collect() + } + + fn list_all_payments(&self) -> Vec { + let mut payments = Vec::new(); + let mut page_token: Option = None; + loop { + let page = self.list_payments(page_token).unwrap(); + payments.extend(page.payments); + page_token = page.next_page_token; + if page_token.is_none() { + return payments; + } + } + } + + fn list_all_payments_with_filter bool>( + &self, mut f: F, + ) -> Vec { + self.list_all_payments().into_iter().filter(|p| f(p)).collect() + } +} + macro_rules! expect_event { ($node:expr, $event_type:ident) => {{ let event = tokio::time::timeout( @@ -409,7 +453,7 @@ type TestNode = Arc; type TestNode = Node; fn has_onchain_tx_type bool>(node: &TestNode, predicate: F) -> bool { - node.list_payments().into_iter().any(|payment| { + node.list_all_payments().into_iter().any(|payment| { matches!( payment.kind, PaymentKind::Onchain { tx_type: Some(ref tx_type), .. } if predicate(tx_type) @@ -427,7 +471,7 @@ fn assert_any_node_has_onchain_tx_type bool + Copy>( let observed: Vec = nodes .iter() .flat_map(|(name, node)| { - node.list_payments().into_iter().filter_map(move |payment| match payment.kind { + node.list_all_payments().into_iter().filter_map(move |payment| match payment.kind { PaymentKind::Onchain { tx_type, .. } => Some(format!("{}:{:?}", name, tx_type)), _ => None, }) @@ -446,7 +490,7 @@ fn assert_all_nodes_have_onchain_tx_type bool + Copy> let observed: Vec = nodes .iter() .flat_map(|(name, node)| { - node.list_payments().into_iter().filter_map(move |payment| match payment.kind { + node.list_all_payments().into_iter().filter_map(move |payment| match payment.kind { PaymentKind::Onchain { tx_type, .. } => Some(format!("{}:{:?}", name, tx_type)), _ => None, }) @@ -1048,28 +1092,28 @@ pub(crate) async fn do_channel_full_cycle( // Check we saw the node funding transactions. assert_eq!( node_a - .list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound + .list_first_page_payments_with_filter(|p| p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. })) .len(), 1 ); assert_eq!( node_a - .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound + .list_first_page_payments_with_filter(|p| p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. })) .len(), 0 ); assert_eq!( node_b - .list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound + .list_first_page_payments_with_filter(|p| p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. })) .len(), 1 ); assert_eq!( node_b - .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound + .list_first_page_payments_with_filter(|p| p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. })) .len(), 0 @@ -1122,7 +1166,7 @@ pub(crate) async fn do_channel_full_cycle( // Check we now see the channel funding transaction as outbound. assert_eq!( node_a - .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound + .list_first_page_payments_with_filter(|p| p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. })) .len(), 1 @@ -1199,24 +1243,24 @@ pub(crate) async fn do_channel_full_cycle( let 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_first_page_payments_with_filter(|p| p.id == payment_id).is_empty()); - let outbound_payments_a = node_a.list_payments_with_filter(|p| { + let outbound_payments_a = node_a.list_first_page_payments_with_filter(|p| { p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) }); assert_eq!(outbound_payments_a.len(), 1); - let inbound_payments_a = node_a.list_payments_with_filter(|p| { + let inbound_payments_a = node_a.list_first_page_payments_with_filter(|p| { p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) }); assert_eq!(inbound_payments_a.len(), 0); - let outbound_payments_b = node_b.list_payments_with_filter(|p| { + let outbound_payments_b = node_b.list_first_page_payments_with_filter(|p| { p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) }); assert_eq!(outbound_payments_b.len(), 0); - let inbound_payments_b = node_b.list_payments_with_filter(|p| { + let inbound_payments_b = node_b.list_first_page_payments_with_filter(|p| { p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) }); assert_eq!(inbound_payments_b.len(), 1); @@ -1463,22 +1507,30 @@ pub(crate) async fn do_channel_full_cycle( PaymentKind::Spontaneous { .. } )); assert_eq!( - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(), + node_a + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })) + .len(), 5 ); assert_eq!( - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(), + node_b + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })) + .len(), 6 ); assert_eq!( node_a - .list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Spontaneous { .. })) + .list_first_page_payments_with_filter(|p| { + matches!(p.kind, PaymentKind::Spontaneous { .. }) + }) .len(), 1 ); assert_eq!( node_b - .list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Spontaneous { .. })) + .list_first_page_payments_with_filter(|p| { + matches!(p.kind, PaymentKind::Spontaneous { .. }) + }) .len(), 1 ); @@ -1505,7 +1557,7 @@ pub(crate) async fn do_channel_full_cycle( assert_eq!( node_a - .list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound + .list_first_page_payments_with_filter(|p| p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. })) .len(), 2 @@ -1527,7 +1579,7 @@ pub(crate) async fn do_channel_full_cycle( assert_eq!( node_a - .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound + .list_first_page_payments_with_filter(|p| p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. })) .len(), 2 @@ -1700,13 +1752,13 @@ pub(crate) async fn do_channel_full_cycle( // Now we should have seen the channel closing transaction on-chain. let node_a_inbound_onchain_count = node_a - .list_payments_with_filter(|p| { + .list_all_payments_with_filter(|p| { p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. }) }) .len(); let node_b_inbound_onchain_count = node_b - .list_payments_with_filter(|p| { + .list_all_payments_with_filter(|p| { p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. }) }) diff --git a/tests/integration_tests_migration.rs b/tests/integration_tests_migration.rs index c4e63451a8..d8adf634c4 100644 --- a/tests/integration_tests_migration.rs +++ b/tests/integration_tests_migration.rs @@ -15,7 +15,7 @@ use std::path::PathBuf; use common::{ drop_table, expect_channel_ready_event, expect_payment_received_event, - expect_payment_successful_event, test_connection_string, + expect_payment_successful_event, test_connection_string, NodePaymentExt, }; use ldk_node::entropy::NodeEntropy; use ldk_node::io::postgres_store::PostgresStore; @@ -224,7 +224,7 @@ async fn migrate_node_across_all_backends() { // Capture the state we expect to survive every migration. let expected_balance_sats = node.list_balances().total_onchain_balance_sats; let expected_ln_balance_sats = node.list_balances().total_lightning_balance_sats; - let mut expected_payments = node.list_payments(); + let mut expected_payments = node.list_all_payments(); expected_payments.sort_by_key(|p| p.id.0); assert!(expected_payments.len() >= 4); @@ -249,7 +249,7 @@ async fn migrate_node_across_all_backends() { assert_eq!(node.list_balances().total_onchain_balance_sats, expected_balance_sats); assert_eq!(node.list_balances().total_lightning_balance_sats, expected_ln_balance_sats); assert_eq!(node.list_channels().len(), 1); - let mut migrated_payments = node.list_payments(); + let mut migrated_payments = node.list_all_payments(); migrated_payments.sort_by_key(|p| p.id.0); assert_eq!(migrated_payments, expected_payments); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index b07a90629f..2ae1381055 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -24,8 +24,8 @@ use common::{ generate_listening_addresses, invalidate_blocks, open_channel, open_channel_push_amt, open_channel_with_all, premine_and_distribute_funds, premine_blocks, prepare_rbf, random_chain_source, random_config, setup_bitcoind_and_electrsd, setup_builder, setup_node, - setup_two_nodes, splice_in_with_all, wait_for_block, wait_for_tx, TestChainSource, TestConfig, - TestStoreType, TestSyncStore, + setup_two_nodes, splice_in_with_all, wait_for_block, wait_for_tx, NodePaymentExt, + TestChainSource, TestConfig, TestStoreType, TestSyncStore, }; use electrsd::corepc_node::{self, Node as BitcoinD}; use electrsd::ElectrsD; @@ -54,7 +54,7 @@ use serde_json::json; async fn wait_for_classified_funding_payment(node: &Node, funding_txid: Txid) { let poll = async { loop { - let classified = node.list_payments().into_iter().any(|p| { + let classified = node.list_first_page_payments().into_iter().any(|p| { matches!( p.kind, PaymentKind::Onchain { txid, tx_type: Some(_), .. } if txid == funding_txid @@ -466,15 +466,15 @@ async fn split_underpaid_bolt11_payment() { // 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()); + node_c.list_first_page_payments_with_filter(|p| p.id == receiver_payment_id.unwrap()); assert_eq!(receiver_payments.len(), 1); assert_eq!(receiver_payments.first().unwrap().amount_msat, Some(amount_msat)); - let node_a_payments = node_a.list_payments_with_filter(|p| p.id == payment_id_a); + let node_a_payments = node_a.list_first_page_payments_with_filter(|p| p.id == payment_id_a); assert_eq!(node_a_payments.len(), 1); assert_eq!(node_a_payments.first().unwrap().amount_msat, Some(half_amount_msat)); - let node_b_payments = node_b.list_payments_with_filter(|p| p.id == payment_id_b); + let node_b_payments = node_b.list_first_page_payments_with_filter(|p| p.id == payment_id_b); assert_eq!(node_b_payments.len(), 1); assert_eq!(node_b_payments.first().unwrap().amount_msat, Some(half_amount_msat)); } @@ -578,8 +578,8 @@ async fn onchain_send_receive() { assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat); - let node_a_payments = node_a.list_payments(); - let node_b_payments = node_b.list_payments(); + let node_a_payments = node_a.list_first_page_payments(); + let node_b_payments = node_b.list_first_page_payments(); for payments in [&node_a_payments, &node_b_payments] { assert_eq!(payments.len(), 1) } @@ -606,11 +606,11 @@ async fn onchain_send_receive() { expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); - let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + let node_a_payments = node_a + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); assert_eq!(node_a_payments.len(), 1); - let node_b_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + let node_b_payments = node_b + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); assert_eq!(node_b_payments.len(), 2); let onchain_fee_buffer_sat = 1000; @@ -681,11 +681,11 @@ async fn onchain_send_receive() { assert!(node_b.list_balances().spendable_onchain_balance_sats > expected_node_b_balance_lower); assert!(node_b.list_balances().spendable_onchain_balance_sats < expected_node_b_balance_upper); - let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + let node_a_payments = node_a + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); assert_eq!(node_a_payments.len(), 2); - let node_b_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + let node_b_payments = node_b + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); assert_eq!(node_b_payments.len(), 3); let payment_a = node_a.payment(&payment_id).unwrap(); @@ -724,11 +724,11 @@ async fn onchain_send_receive() { assert!(node_b.list_balances().spendable_onchain_balance_sats > expected_node_b_balance_lower); assert!(node_b.list_balances().spendable_onchain_balance_sats < expected_node_b_balance_upper); - let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + let node_a_payments = node_a + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); assert_eq!(node_a_payments.len(), 3); - let node_b_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + let node_b_payments = node_b + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); assert_eq!(node_b_payments.len(), 4); let addr_b = node_b.onchain_payment().new_address().unwrap(); @@ -748,11 +748,11 @@ async fn onchain_send_receive() { assert!(node_b.list_balances().spendable_onchain_balance_sats > expected_node_b_balance_lower); assert!(node_b.list_balances().spendable_onchain_balance_sats < expected_node_b_balance_upper); - let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + let node_a_payments = node_a + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); assert_eq!(node_a_payments.len(), 4); - let node_b_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + let node_b_payments = node_b + .list_first_page_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); assert_eq!(node_b_payments.len(), 5); } @@ -1631,7 +1631,7 @@ async fn splice_channel() { // them to the channel balance since there may not be a change output. let expected_splice_in_lightning_balance_sat = 4_000_002; - let payments = node_b.list_payments(); + let payments = node_b.list_first_page_payments(); let payment = payments.into_iter().find(|p| p.id == PaymentId(txo.txid.to_byte_array())).unwrap(); assert_eq!(payment.fee_paid_msat, Some(expected_splice_in_fee_sat * 1_000)); @@ -1684,7 +1684,7 @@ async fn splice_channel() { let expected_splice_out_fee_sat = 183; - let payments = node_a.list_payments(); + let payments = node_a.list_first_page_payments(); let payment = payments.into_iter().find(|p| p.id == PaymentId(txo.txid.to_byte_array())).unwrap(); assert_eq!(payment.fee_paid_msat, Some(expected_splice_out_fee_sat * 1_000)); @@ -1853,7 +1853,7 @@ async fn run_rbf_splice_channel_test(confirm_original: bool) { } assert_eq!(payment.status, PaymentStatus::Pending); // Only one Onchain Pending payment for this splice attempt (not one per candidate). - let splice_payments = node_b.list_payments_with_filter(|p| { + let splice_payments = node_b.list_first_page_payments_with_filter(|p| { p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. }) && p.status == PaymentStatus::Pending @@ -2179,8 +2179,9 @@ async fn simple_bolt12_send_receive() { }, ref e => panic!("{} got unexpected event!: {:?}", "node_a", e), } - let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. })); + let node_a_payments = node_a.list_first_page_payments_with_filter(|p| { + matches!(p.kind, PaymentKind::Bolt12Offer { .. }) + }); assert_eq!(node_a_payments.len(), 1); match node_a_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { @@ -2206,8 +2207,9 @@ 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_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. })); + let node_b_payments = node_b.list_first_page_payments_with_filter(|p| { + matches!(p.kind, PaymentKind::Bolt12Offer { .. }) + }); assert_eq!(node_b_payments.len(), 1); match node_b_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { hash, preimage, secret, offer_id, .. } => { @@ -2245,7 +2247,7 @@ async fn simple_bolt12_send_receive() { .unwrap(); expect_payment_successful_event!(node_a, Some(payment_id), None); - let node_a_payments = node_a.list_payments_with_filter(|p| { + let node_a_payments = node_a.list_first_page_payments_with_filter(|p| { matches!(p.kind, PaymentKind::Bolt12Offer { .. }) && p.id == payment_id }); assert_eq!(node_a_payments.len(), 1); @@ -2275,7 +2277,7 @@ async fn simple_bolt12_send_receive() { expect_payment_received_event!(node_b, expected_amount_msat); let node_b_payment_id = PaymentId(payment_hash.0); - let node_b_payments = node_b.list_payments_with_filter(|p| { + let node_b_payments = node_b.list_first_page_payments_with_filter(|p| { matches!(p.kind, PaymentKind::Bolt12Offer { .. }) && p.id == node_b_payment_id }); assert_eq!(node_b_payments.len(), 1); @@ -2310,7 +2312,7 @@ async fn simple_bolt12_send_receive() { expect_payment_received_event!(node_a, overpaid_amount); let node_b_payment_id = node_b - .list_payments_with_filter(|p| { + .list_first_page_payments_with_filter(|p| { matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.amount_msat == Some(overpaid_amount) }) @@ -2319,7 +2321,7 @@ async fn simple_bolt12_send_receive() { .id; expect_payment_successful_event!(node_b, Some(node_b_payment_id), None); - let node_b_payments = node_b.list_payments_with_filter(|p| { + let node_b_payments = node_b.list_first_page_payments_with_filter(|p| { matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_b_payment_id }); assert_eq!(node_b_payments.len(), 1); @@ -2345,7 +2347,7 @@ 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| { + let node_a_payments = node_a.list_first_page_payments_with_filter(|p| { matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_a_payment_id }); assert_eq!(node_a_payments.len(), 1); @@ -3055,8 +3057,11 @@ async fn spontaneous_send_with_custom_preimage() { // check payment status and verify stored preimage expect_payment_successful_event!(node_a, Some(payment_id), None); - let details: PaymentDetails = - node_a.list_payments_with_filter(|p| p.id == payment_id).first().unwrap().clone(); + let details: PaymentDetails = node_a + .list_first_page_payments_with_filter(|p| p.id == payment_id) + .first() + .unwrap() + .clone(); assert_eq!(details.status, PaymentStatus::Succeeded); if let PaymentKind::Spontaneous { preimage: Some(pi), .. } = details.kind { assert_eq!(pi.0, custom_bytes); @@ -3066,7 +3071,7 @@ async fn spontaneous_send_with_custom_preimage() { // Verify receiver side (node_b) expect_payment_received_event!(node_b, amount_msat); - let receiver_payments: Vec = node_b.list_payments_with_filter(|p| { + let receiver_payments: Vec = node_b.list_first_page_payments_with_filter(|p| { p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Spontaneous { .. }) }); @@ -3468,7 +3473,7 @@ async fn payment_persistence_after_restart() { println!("All {} payments completed successfully", num_payments); // Verify node_a has 200 outbound Bolt11 payments before shutdown - let outbound_payments_before = node_a.list_payments_with_filter(|p| { + let outbound_payments_before = node_a.list_all_payments_with_filter(|p| { p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) }); @@ -3485,7 +3490,7 @@ async fn payment_persistence_after_restart() { let restarted_node_a = setup_node(&chain_source, config_a); // Assert all 200 payments are still in the store - let outbound_payments_after = restarted_node_a.list_payments_with_filter(|p| { + let outbound_payments_after = restarted_node_a.list_all_payments_with_filter(|p| { p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) }); assert_eq!( @@ -3817,7 +3822,7 @@ async fn onchain_fee_bump_rbf() { } // Verify node A received the funds correctly - let node_a_received_payment = node_a.list_payments_with_filter(|p| { + let node_a_received_payment = node_a.list_first_page_payments_with_filter(|p| { p.id == payment_id && matches!(p.kind, PaymentKind::Onchain { .. }) }); diff --git a/tests/reorg_test.rs b/tests/reorg_test.rs index 295d9fdd24..ff315c26cf 100644 --- a/tests/reorg_test.rs +++ b/tests/reorg_test.rs @@ -10,7 +10,7 @@ use proptest::proptest; use crate::common::{ expect_event, generate_blocks_and_wait, invalidate_blocks, open_channel, premine_and_distribute_funds, random_chain_source, random_config, setup_bitcoind_and_electrsd, - setup_node, wait_for_outpoint_spend, + setup_node, wait_for_outpoint_spend, NodePaymentExt, }; proptest! { @@ -101,10 +101,9 @@ proptest! { let mut node_channels_id = HashMap::new(); for (i, node) in nodes.iter().enumerate() { assert_eq!( - node - .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound - && matches!(p.kind, PaymentKind::Onchain { .. })) - .len(), + node.list_first_page_payments_with_filter(|p| p.direction == PaymentDirection::Outbound + && matches!(p.kind, PaymentKind::Onchain { .. })) + .len(), 1 );