Add BOLT11 underpaying send RPC - #242
Conversation
|
👋 Thanks for assigning @tankyleo as a reviewer! |
|
Nice addition @benthecarman — I wanted to understand this one end-to-end since From reading the ldk-node doc comment, this works by declaring the invoice's full amount as the MPP total_msat while only sending part of it from this node — so it only completes if something else supplies the rest. Testing that: I opened a channel from ldk-server to an LND node, created a 100,000 sat invoice on LND, and sent 50,000 sats via I might be missing an intended use case (e.g. something that pairs with an LSP or a second sender?) — is this meant to be used standalone, or does it assume some other piece that isn't part of this PR? |
|
@f3r10 yes its intented to be used with another node as well, one node pays half, another node pays another half. For thing like https://github.com/lightningdevkit/orange-sdk |
f3r10
left a comment
There was a problem hiding this comment.
One thing I noticed: there's no test in e2e-tests/ covering this yet, even though test_cli_bolt11_send already has a nice 2-node pattern to build on there. Since the interesting behavior here specifically needs two independent payers, I put together a 3-node version (two payers, each with their own direct channel into one receiver, each sending half via bolt11-send-underpaying, asserting the receiver only gets PaymentReceived once both halves land) and ran it locally against this branch — it passes:
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_cli_bolt11_send_underpaying_split_payment() {
let bitcoind = TestBitcoind::new();
let server_a = LdkServerHandle::start(&bitcoind).await; // payer 1
let server_b = LdkServerHandle::start(&bitcoind).await; // payer 2
let server_c = LdkServerHandle::start(&bitcoind).await; // receiver
// Subscribe to events on all three nodes before any payment is sent.
let mut events_a = server_a.client().subscribe_events().await.unwrap();
let mut events_b = server_b.client().subscribe_events().await.unwrap();
let mut events_c = server_c.client().subscribe_events().await.unwrap();
// Each payer gets its own direct channel into the receiver. Sized well above the
// 50,000 sat HTLC each payer will send, since LDK caps a channel's max HTLC size to a
// fraction of its capacity (a too-small channel here causes RouteNotFound).
setup_funded_channel(&bitcoind, &server_a, &server_c, 300_000).await;
setup_funded_channel(&bitcoind, &server_b, &server_c, 300_000).await;
// C creates a single invoice for the full amount the two payers will jointly cover.
let invoice_resp = server_c
.client()
.bolt11_receive(Bolt11ReceiveRequest {
amount_msat: Some(100_000_000), // 100,000 sats total
description: Some(Bolt11InvoiceDescription {
kind: Some(bolt11_invoice_description::Kind::Direct(
"split payment test".to_string(),
)),
}),
expiry_secs: 3600,
})
.await
.unwrap();
// Both payers independently underpay by half. They're sent back-to-back so the combined
// total reaches the invoice amount well within the receiver's MPP hold window.
let output_a =
run_cli(&server_a, &["bolt11-send-underpaying", &invoice_resp.invoice, "50000sat"]);
let output_b =
run_cli(&server_b, &["bolt11-send-underpaying", &invoice_resp.invoice, "50000sat"]);
assert!(!output_a["payment_id"].as_str().unwrap().is_empty());
assert!(!output_b["payment_id"].as_str().unwrap().is_empty());
// The receiver only sees PaymentReceived once *both* partial HTLCs have arrived and the
// combined total matches the invoice amount.
let event_c = wait_for_event(&mut events_c, |e| matches!(e, Event::PaymentReceived(_))).await;
assert!(matches!(&event_c.event, Some(Event::PaymentReceived(_))));
// Both payers should see their half complete successfully.
let event_a = wait_for_event(&mut events_a, |e| matches!(e, Event::PaymentSuccessful(_))).await;
assert!(matches!(&event_a.event, Some(Event::PaymentSuccessful(_))));
let event_b = wait_for_event(&mut events_b, |e| matches!(e, Event::PaymentSuccessful(_))).await;
assert!(matches!(&event_b.event, Some(Event::PaymentSuccessful(_))));
}(One gotcha along the way worth mentioning in case it saves someone else time: with 100,000-sat channels the test failed with RouteNotFound, because LDK caps a channel's max single-HTLC size to a fraction of its capacity — the 50,000-sat underpaying HTLC didn't fit. Bumping the channels to 300,000 sats fixed it.)
Expose Bolt11SendUnderpaying through the proto API, server handler, client, CLI, MCP tool registry, tests, and docs. AI-assisted-by: OpenAI Codex
|
@f3r10 thanks added |
Expose Bolt11SendUnderpaying through the proto API, server handler, client, CLI, MCP tool registry, tests, and docs.