Skip to content

Commit 9241a4a

Browse files
authored
Merge pull request #49 from G8XSU/payments-pagination-2
Support pagination in ListPayments API.
2 parents 40bae58 + 4655037 commit 9241a4a

File tree

6 files changed

+174
-15
lines changed

6 files changed

+174
-15
lines changed

ldk-server-cli/src/main.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ use ldk_server_client::ldk_server_protos::api::{
1010
OnchainReceiveRequest, OnchainSendRequest, OpenChannelRequest,
1111
};
1212
use ldk_server_client::ldk_server_protos::types::{
13-
bolt11_invoice_description, Bolt11InvoiceDescription,
13+
bolt11_invoice_description, Bolt11InvoiceDescription, PageToken, Payment,
1414
};
15+
use std::fmt::Debug;
1516

1617
#[derive(Parser, Debug)]
1718
#[command(version, about, long_about = None)]
@@ -87,7 +88,13 @@ enum Commands {
8788
announce_channel: bool,
8889
},
8990
ListChannels,
90-
ListPayments,
91+
ListPayments {
92+
#[arg(short, long)]
93+
#[arg(
94+
help = "Minimum number of payments to return. If not provided, only the first page of the paginated list is returned."
95+
)]
96+
number_of_payments: Option<u64>,
97+
},
9198
}
9299

93100
#[tokio::main]
@@ -186,13 +193,33 @@ async fn main() {
186193
Commands::ListChannels => {
187194
handle_response_result(client.list_channels(ListChannelsRequest {}).await);
188195
},
189-
Commands::ListPayments => {
190-
handle_response_result(client.list_payments(ListPaymentsRequest {}).await);
196+
Commands::ListPayments { number_of_payments } => {
197+
handle_response_result(list_n_payments(client, number_of_payments).await);
191198
},
192199
}
193200
}
194201

195-
fn handle_response_result<Rs: ::prost::Message>(response: Result<Rs, LdkServerError>) {
202+
async fn list_n_payments(
203+
client: LdkServerClient, number_of_payments: Option<u64>,
204+
) -> Result<Vec<Payment>, LdkServerError> {
205+
let mut payments = Vec::new();
206+
let mut page_token: Option<PageToken> = None;
207+
// If no count is specified, just list the first page.
208+
let target_count = number_of_payments.unwrap_or(0);
209+
210+
loop {
211+
let response = client.list_payments(ListPaymentsRequest { page_token }).await?;
212+
213+
payments.extend(response.payments);
214+
if payments.len() >= target_count as usize || response.next_page_token.is_none() {
215+
break;
216+
}
217+
page_token = response.next_page_token;
218+
}
219+
Ok(payments)
220+
}
221+
222+
fn handle_response_result<Rs: Debug>(response: Result<Rs, LdkServerError>) {
196223
match response {
197224
Ok(response) => {
198225
println!("Success: {:?}", response);

ldk-server-protos/src/api.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,16 @@ pub struct GetPaymentDetailsResponse {
331331
/// See more: <https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_payments>
332332
#[allow(clippy::derive_partial_eq_without_eq)]
333333
#[derive(Clone, PartialEq, ::prost::Message)]
334-
pub struct ListPaymentsRequest {}
334+
pub struct ListPaymentsRequest {
335+
/// `page_token` is a pagination token.
336+
///
337+
/// To query for the first page, `page_token` must not be specified.
338+
///
339+
/// For subsequent pages, use the value that was returned as `next_page_token` in the previous
340+
/// page's response.
341+
#[prost(message, optional, tag = "1")]
342+
pub page_token: ::core::option::Option<super::types::PageToken>,
343+
}
335344
/// The response `content` for the `ListPayments` API, when HttpStatusCode is OK (200).
336345
/// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`.
337346
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -340,6 +349,21 @@ pub struct ListPaymentsResponse {
340349
/// List of payments.
341350
#[prost(message, repeated, tag = "1")]
342351
pub payments: ::prost::alloc::vec::Vec<super::types::Payment>,
352+
/// `next_page_token` is a pagination token, used to retrieve the next page of results.
353+
/// Use this value to query for next-page of paginated operation, by specifying
354+
/// this value as the `page_token` in the next request.
355+
///
356+
/// If `next_page_token` is `None`, then the "last page" of results has been processed and
357+
/// there is no more data to be retrieved.
358+
///
359+
/// If `next_page_token` is not `None`, it does not necessarily mean that there is more data in the
360+
/// result set. The only way to know when you have reached the end of the result set is when
361+
/// `next_page_token` is `None`.
362+
///
363+
/// **Caution**: Clients must not assume a specific number of records to be present in a page for
364+
/// paginated response.
365+
#[prost(message, optional, tag = "2")]
366+
pub next_page_token: ::core::option::Option<super::types::PageToken>,
343367
}
344368
/// Retrieves list of all forwarded payments.
345369
/// See more: <https://docs.rs/ldk-node/latest/ldk_node/enum.Event.html#variant.PaymentForwarded>

ldk-server-protos/src/proto/api.proto

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,13 +314,36 @@ message GetPaymentDetailsResponse {
314314

315315
// Retrieves list of all payments.
316316
// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_payments
317-
message ListPaymentsRequest {}
317+
message ListPaymentsRequest {
318+
// `page_token` is a pagination token.
319+
//
320+
// To query for the first page, `page_token` must not be specified.
321+
//
322+
// For subsequent pages, use the value that was returned as `next_page_token` in the previous
323+
// page's response.
324+
optional types.PageToken page_token = 1;
325+
}
318326

319327
// The response `content` for the `ListPayments` API, when HttpStatusCode is OK (200).
320328
// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`.
321329
message ListPaymentsResponse {
322330
// List of payments.
323331
repeated types.Payment payments = 1;
332+
333+
// `next_page_token` is a pagination token, used to retrieve the next page of results.
334+
// Use this value to query for next-page of paginated operation, by specifying
335+
// this value as the `page_token` in the next request.
336+
//
337+
// If `next_page_token` is `None`, then the "last page" of results has been processed and
338+
// there is no more data to be retrieved.
339+
//
340+
// If `next_page_token` is not `None`, it does not necessarily mean that there is more data in the
341+
// result set. The only way to know when you have reached the end of the result set is when
342+
// `next_page_token` is `None`.
343+
//
344+
// **Caution**: Clients must not assume a specific number of records to be present in a page for
345+
// paginated response.
346+
optional types.PageToken next_page_token = 2;
324347
}
325348

326349
// Retrieves list of all forwarded payments.

ldk-server/src/api/list_payments.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,54 @@
11
use crate::api::error::LdkServerError;
2+
use crate::api::error::LdkServerErrorCode::InternalServerError;
3+
use crate::io::{PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE};
24
use crate::service::Context;
3-
use crate::util::proto_adapter::payment_to_proto;
5+
use bytes::Bytes;
46
use ldk_server_protos::api::{ListPaymentsRequest, ListPaymentsResponse};
7+
use ldk_server_protos::types::{PageToken, Payment};
8+
use prost::Message;
59

610
pub(crate) const LIST_PAYMENTS_PATH: &str = "ListPayments";
711

812
pub(crate) fn handle_list_payments_request(
9-
context: Context, _request: ListPaymentsRequest,
13+
context: Context, request: ListPaymentsRequest,
1014
) -> Result<ListPaymentsResponse, LdkServerError> {
11-
let payments = context.node.list_payments().into_iter().map(|p| payment_to_proto(p)).collect();
15+
let page_token = request.page_token.map(|p| (p.token, p.index));
16+
let list_response = context
17+
.paginated_kv_store
18+
.list(
19+
PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE,
20+
PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE,
21+
page_token,
22+
)
23+
.map_err(|e| {
24+
LdkServerError::new(InternalServerError, format!("Failed to list payments: {}", e))
25+
})?;
1226

13-
let response = ListPaymentsResponse { payments };
27+
let mut payments: Vec<Payment> = Vec::with_capacity(list_response.keys.len());
28+
for key in list_response.keys {
29+
let payment_bytes = context
30+
.paginated_kv_store
31+
.read(
32+
PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE,
33+
PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE,
34+
&key,
35+
)
36+
.map_err(|e| {
37+
LdkServerError::new(
38+
InternalServerError,
39+
format!("Failed to read payment data: {}", e),
40+
)
41+
})?;
42+
let payment = Payment::decode(Bytes::from(payment_bytes)).map_err(|e| {
43+
LdkServerError::new(InternalServerError, format!("Failed to decode payment: {}", e))
44+
})?;
45+
payments.push(payment);
46+
}
47+
let response = ListPaymentsResponse {
48+
payments,
49+
next_page_token: list_response
50+
.next_page_token
51+
.map(|(token, index)| PageToken { token, index }),
52+
};
1453
Ok(response)
1554
}

ldk-server/src/io/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ pub(crate) mod utils;
55
/// The forwarded payments will be persisted under this prefix.
66
pub(crate) const FORWARDED_PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE: &str = "forwarded_payments";
77
pub(crate) const FORWARDED_PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE: &str = "";
8+
9+
/// The payments will be persisted under this prefix.
10+
pub(crate) const PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE: &str = "payments";
11+
pub(crate) const PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE: &str = "";

ldk-server/src/main.rs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod util;
55

66
use crate::service::NodeService;
77

8-
use ldk_node::{Builder, Event};
8+
use ldk_node::{Builder, Event, Node};
99

1010
use tokio::net::TcpListener;
1111
use tokio::signal::unix::SignalKind;
@@ -17,12 +17,14 @@ use crate::io::paginated_kv_store::PaginatedKVStore;
1717
use crate::io::sqlite_store::SqliteStore;
1818
use crate::io::{
1919
FORWARDED_PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE,
20-
FORWARDED_PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE,
20+
FORWARDED_PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE,
21+
PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE,
2122
};
2223
use crate::util::config::load_config;
23-
use crate::util::proto_adapter::forwarded_payment_to_proto;
24+
use crate::util::proto_adapter::{forwarded_payment_to_proto, payment_to_proto};
2425
use hex::DisplayHex;
2526
use ldk_node::config::Config;
27+
use ldk_node::lightning::ln::channelmanager::PaymentId;
2628
use ldk_node::logger::LogLevel;
2729
use prost::Message;
2830
use rand::Rng;
@@ -146,7 +148,19 @@ fn main() {
146148
"PAYMENT_RECEIVED: with id {:?}, hash {}, amount_msat {}",
147149
payment_id, payment_hash, amount_msat
148150
);
149-
event_node.event_handled();
151+
let payment_id = payment_id.expect("PaymentId expected for ldk-server >=0.1");
152+
upsert_payment_details(&event_node, Arc::clone(&paginated_store) as Arc<dyn PaginatedKVStore>, &payment_id);
153+
},
154+
Event::PaymentSuccessful {payment_id, ..} => {
155+
let payment_id = payment_id.expect("PaymentId expected for ldk-server >=0.1");
156+
upsert_payment_details(&event_node, Arc::clone(&paginated_store) as Arc<dyn PaginatedKVStore>, &payment_id);
157+
},
158+
Event::PaymentFailed {payment_id, ..} => {
159+
let payment_id = payment_id.expect("PaymentId expected for ldk-server >=0.1");
160+
upsert_payment_details(&event_node, Arc::clone(&paginated_store) as Arc<dyn PaginatedKVStore>, &payment_id);
161+
},
162+
Event::PaymentClaimable {payment_id, ..} => {
163+
upsert_payment_details(&event_node, Arc::clone(&paginated_store) as Arc<dyn PaginatedKVStore>, &payment_id);
150164
},
151165
Event::PaymentForwarded {
152166
prev_channel_id,
@@ -234,3 +248,31 @@ fn main() {
234248
node.stop().expect("Shutdown should always succeed.");
235249
println!("Shutdown complete..");
236250
}
251+
252+
fn upsert_payment_details(
253+
event_node: &Node, paginated_store: Arc<dyn PaginatedKVStore>, payment_id: &PaymentId,
254+
) {
255+
if let Some(payment_details) = event_node.payment(payment_id) {
256+
let payment = payment_to_proto(payment_details);
257+
let time =
258+
SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs()
259+
as i64;
260+
261+
match paginated_store.write(
262+
PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE,
263+
PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE,
264+
&payment_id.0.to_lower_hex_string(),
265+
time,
266+
&payment.encode_to_vec(),
267+
) {
268+
Ok(_) => {
269+
event_node.event_handled();
270+
},
271+
Err(e) => {
272+
eprintln!("Failed to write payment to persistence: {}", e);
273+
},
274+
}
275+
} else {
276+
eprintln!("Unable to find payment with paymentId: {}", payment_id.0.to_lower_hex_string());
277+
}
278+
}

0 commit comments

Comments
 (0)