diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e012a14..29f7e5c67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,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/ldk_node.udl b/bindings/ldk_node.udl index 7c0edc535..4177930a2 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 13afeca7e..0b4f3e2e7 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 @@ -338,6 +379,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 c97e16fe6..97d94232a 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; @@ -2124,15 +2125,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. @@ -2250,6 +2280,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 a56d46e05..1cd1d9184 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -42,7 +42,7 @@ 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}; +use ldk_node::payment::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; use ldk_node::{ Builder, ChannelShutdownState, CustomTlvRecord, Event, LightningBalance, Node, NodeError, PendingSweepBalance, UserChannelId, @@ -66,6 +66,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( @@ -928,28 +970,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 @@ -1002,7 +1044,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 @@ -1079,24 +1121,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); @@ -1343,22 +1385,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 ); @@ -1385,7 +1435,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 @@ -1407,7 +1457,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 @@ -1625,14 +1675,14 @@ pub(crate) async fn do_channel_full_cycle( // Now we should have seen the channel closing transaction on-chain. 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(), 3 ); 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(), 2 diff --git a/tests/integration_tests_migration.rs b/tests/integration_tests_migration.rs index ee5ad26c8..807fa4c8d 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 c3c2f4262..298a69b1e 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 @@ -416,15 +416,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)); } @@ -528,8 +528,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) } @@ -556,11 +556,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; @@ -629,11 +629,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(); @@ -670,11 +670,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(); @@ -694,11 +694,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); } @@ -1507,7 +1507,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)); @@ -1560,7 +1560,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)); @@ -1729,7 +1729,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 @@ -2055,8 +2055,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 { @@ -2082,8 +2083,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, .. } => { @@ -2121,7 +2123,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); @@ -2151,7 +2153,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); @@ -2186,7 +2188,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) }) @@ -2195,7 +2197,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); @@ -2221,7 +2223,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); @@ -2931,8 +2933,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); @@ -2942,7 +2947,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 { .. }) }); @@ -3344,7 +3349,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 { .. }) }); @@ -3361,7 +3366,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!( @@ -3693,7 +3698,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 295d9fdd2..ff315c26c 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 );