Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion e2e-tests/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use ldk_node::lightning::offers::offer::Offer;
use ldk_node::lightning_invoice::Bolt11Invoice;
use ldk_server_client::client::EventStream;
use ldk_server_client::ldk_server_grpc::api::{
Bolt11ReceiveRequest, Bolt12ReceiveRequest, OnchainReceiveRequest, OpenChannelRequest,
Bolt11ReceiveRequest, Bolt12ReceiveRequest, ListPaymentsRequest, OnchainReceiveRequest,
OnchainSendRequest, OpenChannelRequest,
};
use ldk_server_client::ldk_server_grpc::events::event_envelope::Event;
use ldk_server_client::ldk_server_grpc::events::{
Expand Down Expand Up @@ -361,6 +362,54 @@ async fn test_cli_onchain_send() {
assert!(!output["txid"].as_str().unwrap().is_empty());
}

#[tokio::test]
async fn test_list_payments_includes_onchain_send() {
let bitcoind = TestBitcoind::new();
let server = LdkServerHandle::start(&bitcoind).await;

let addr = server.client().onchain_receive(OnchainReceiveRequest {}).await.unwrap().address;
bitcoind.fund_address(&addr, 1.0);
mine_and_sync(&bitcoind, &[&server], 6).await;
wait_for_onchain_balance(server.client(), Duration::from_secs(30)).await;

let dest_addr =
server.client().onchain_receive(OnchainReceiveRequest {}).await.unwrap().address;
let send_output = server
.client()
.onchain_send(OnchainSendRequest {
address: dest_addr,
amount_sats: Some(50_000),
send_all: None,
fee_rate_sat_per_vb: None,
})
.await
.unwrap();

let mut found_payment = false;
for _ in 0..30 {
let output =
server.client().list_payments(ListPaymentsRequest { page_token: None }).await.unwrap();
found_payment = output.payments.iter().any(|payment| {
matches!(
payment.kind.as_ref().and_then(|kind| kind.kind.as_ref()),
Some(ldk_server_grpc::types::payment_kind::Kind::Onchain(
onchain
)) if onchain.txid == send_output.txid
)
});
if found_payment {
break;
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
assert!(found_payment);

let output =
server.client().list_payments(ListPaymentsRequest { page_token: None }).await.unwrap();

assert_eq!(output.payments.len(), 2, "Expected two payments in list");
}

#[tokio::test]
async fn test_cli_connect_peer() {
let bitcoind = TestBitcoind::new();
Expand Down
36 changes: 36 additions & 0 deletions ldk-server/src/api/list_payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use std::sync::Arc;

use bytes::Bytes;
use ldk_node::payment::{PaymentDetails, PaymentKind};
use ldk_server_grpc::api::{ListPaymentsRequest, ListPaymentsResponse};
use ldk_server_grpc::types::{PageToken, Payment};
use prost::Message;
Expand All @@ -20,10 +21,16 @@ use crate::io::persist::{
PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE,
};
use crate::service::Context;
use crate::util::proto_adapter::payment_to_proto;

pub(crate) async fn handle_list_payments_request(
context: Arc<Context>, request: ListPaymentsRequest,
) -> Result<ListPaymentsResponse, LdkServerError> {
// TODO: Remove this backfill once LDK Node owns paginated payment listing. Today our
// paginated store is populated from Lightning events only, while on-chain payments live in
// LDK Node's payment store.
sync_onchain_payments_to_paginated_store(&context)?;

let page_token = request.page_token.map(|p| (p.token, p.index));
let list_response = context
.paginated_kv_store
Expand Down Expand Up @@ -64,3 +71,32 @@ pub(crate) async fn handle_list_payments_request(
};
Ok(response)
}

// TODO: Delete this temporary bridge when on-chain and Lightning payments are served from the
// same paginated LDK Node source.
fn sync_onchain_payments_to_paginated_store(context: &Context) -> Result<(), LdkServerError> {
for payment_details in context.node.list_payments().into_iter().filter(is_onchain_payment) {
let payment = payment_to_proto(payment_details);
context
.paginated_kv_store
.write(
PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE,
PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE,
&payment.id,
payment.latest_update_timestamp as i64,
&payment.encode_to_vec(),
)
.map_err(|e| {
LdkServerError::new(
InternalServerError,
format!("Failed to write on-chain payment data: {e}"),
)
})?;
}

Ok(())
}

fn is_onchain_payment(payment: &PaymentDetails) -> bool {
matches!(payment.kind, PaymentKind::Onchain { .. })
}