Skip to content

Commit 590ea22

Browse files
committed
feat: allow setting custom preimage in spontaneous-send
1 parent 4cf8167 commit 590ea22

7 files changed

Lines changed: 96 additions & 12 deletions

File tree

e2e-tests/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ use std::time::Duration;
1616
use corepc_node::Node;
1717
use hex_conservative::DisplayHex;
1818
use ldk_server_client::client::LdkServerClient;
19-
use serde_json::Value;
2019
use ldk_server_client::ldk_server_grpc::api::{GetNodeInfoRequest, GetNodeInfoResponse};
2120
use ldk_server_grpc::api::{
2221
GetBalancesRequest, ListChannelsRequest, OnchainReceiveRequest, OpenChannelRequest,
2322
};
23+
use serde_json::Value;
2424

2525
/// Wrapper around a managed bitcoind process for regtest.
2626
pub struct TestBitcoind {

e2e-tests/tests/e2e.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use ldk_server_client::ldk_server_grpc::events::{
3232
use ldk_server_client::ldk_server_grpc::types::{
3333
bolt11_invoice_description, Bolt11InvoiceDescription,
3434
};
35-
35+
use ldk_server_grpc::types::payment_kind;
3636
const EVENT_TIMEOUT: Duration = Duration::from_secs(15);
3737

3838
async fn wait_for_event(events: &mut EventStream, pred: impl Fn(&Event) -> bool) -> EventEnvelope {
@@ -1462,3 +1462,40 @@ async fn test_metrics_endpoint_with_auth() {
14621462
assert!(metrics.contains("ldk_server_total_anchor_channels_reserve_sats 0"));
14631463
assert!(metrics.contains("ldk_server_total_lightning_balance_sats 0"));
14641464
}
1465+
1466+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1467+
async fn test_cli_spontaneous_send_with_preimage() {
1468+
let bitcoind = TestBitcoind::new();
1469+
let server_a = LdkServerHandle::start(&bitcoind).await;
1470+
let server_b = LdkServerHandle::start(&bitcoind).await;
1471+
1472+
let mut events_b = server_b.client().subscribe_events().await.unwrap();
1473+
1474+
setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await;
1475+
1476+
// Generate a known preimage and compute its payment hash
1477+
let preimage_bytes = [43u8; 32];
1478+
let preimage_hex = preimage_bytes.to_lower_hex_string();
1479+
let payment_hash = sha256::Hash::hash(&preimage_bytes);
1480+
let payment_hash_hex = payment_hash.to_byte_array().to_lower_hex_string();
1481+
1482+
let output = run_cli(
1483+
&server_a,
1484+
&["spontaneous-send", server_b.node_id(), "10000sat", "--preimage", &preimage_hex],
1485+
);
1486+
1487+
assert!(!output["payment_id"].as_str().unwrap().is_empty());
1488+
1489+
// The receiver must observe in PaymentReceived for checking on Spontaneous payment.
1490+
let event_b = wait_for_event(&mut events_b, |e| matches!(e, Event::PaymentReceived(_))).await;
1491+
let Some(Event::PaymentReceived(pr)) = event_b.event else {
1492+
panic!("expected PaymentReceived");
1493+
};
1494+
1495+
let payment = pr.payment.unwrap();
1496+
1497+
let Some(payment_kind::Kind::Spontaneous(spont)) = payment.kind.unwrap().kind else {
1498+
panic!("expected spontaneous kind");
1499+
};
1500+
assert_eq!(spont.hash, payment_hash_hex);
1501+
}

ldk-server-cli/src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ enum Commands {
320320
help = "Custom TLV record to attach, format: <type_num>:<hex_value>. Repeatable. type_num must be >= 65536."
321321
)]
322322
custom_tlvs: Vec<(u64, Vec<u8>)>,
323+
#[arg(long, help = "An optional hex-encoded 32-byte payment preimage. If provided, it will be used instead of generating a random one.")]
324+
preimage: Option<String>,
323325
},
324326
#[command(
325327
about = "Pay a BIP 21 URI, BIP 353 Human-Readable Name, BOLT11 invoice, or BOLT12 offer"
@@ -817,6 +819,7 @@ async fn main() {
817819
max_path_count,
818820
max_channel_saturation_power_of_half,
819821
custom_tlvs,
822+
preimage,
820823
} => {
821824
let amount_msat = amount.to_msat();
822825
let max_total_routing_fee_msat = max_total_routing_fee.map(|a| a.to_msat());
@@ -841,6 +844,7 @@ async fn main() {
841844
node_id,
842845
route_parameters: Some(route_parameters),
843846
custom_tlvs: proto_custom_tlvs,
847+
preimage,
844848
})
845849
.await,
846850
);

ldk-server-grpc/src/api.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,10 @@ pub struct SpontaneousSendRequest {
473473
/// Custom TLV records to attach to the outgoing payment.
474474
#[prost(message, repeated, tag = "4")]
475475
pub custom_tlvs: ::prost::alloc::vec::Vec<super::types::CustomTlvRecord>,
476+
/// An optional hex-encoded 32-byte payment preimage. If provided, it will be used instead of
477+
/// generating a random one. The payment hash will be the SHA256 of this value.
478+
#[prost(string, optional, tag = "5")]
479+
pub preimage: ::core::option::Option<::prost::alloc::string::String>,
476480
}
477481
/// The response for the `SpontaneousSend` RPC. On failure, a gRPC error status is returned.
478482
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ message SpontaneousSendRequest {
371371

372372
// Custom TLV records to attach to the outgoing payment.
373373
repeated types.CustomTlvRecord custom_tlvs = 4;
374+
375+
// An optional hex-encoded 32-byte payment preimage. If provided, it will be used instead of
376+
// generating a random one. The payment hash will be the SHA256 of this value.
377+
optional string preimage = 5;
374378
}
375379

376380
// The response for the `SpontaneousSend` RPC. On failure, a gRPC error status is returned.

ldk-server-mcp/src/tools/schema.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,11 @@ pub fn spontaneous_send_schema() -> Value {
374374
"type": "string",
375375
"description": "The hex-encoded public key of the destination node"
376376
},
377-
"route_parameters": route_parameters_config_schema()
377+
"route_parameters": route_parameters_config_schema(),
378+
"preimage": {
379+
"type": "string",
380+
"description": "The hex-encoded 32-byte payment preimage"
381+
}
378382
},
379383
"required": ["amount_msat", "node_id"]
380384
})

ldk-server/src/api/spontaneous_send.rs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
use std::str::FromStr;
1111
use std::sync::Arc;
1212

13+
use hex::FromHex;
1314
use ldk_node::bitcoin::secp256k1::PublicKey;
15+
use ldk_node::lightning_types::payment::PaymentPreimage;
1416
use ldk_server_grpc::api::{SpontaneousSendRequest, SpontaneousSendResponse};
1517

1618
use crate::api::error::LdkServerError;
@@ -27,19 +29,48 @@ pub(crate) async fn handle_spontaneous_send_request(
2729

2830
let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?;
2931

30-
let payment_id = if request.custom_tlvs.is_empty() {
31-
context.node.spontaneous_payment().send(request.amount_msat, node_id, route_parameters)?
32-
} else {
33-
let custom_tlvs: Vec<_> =
34-
request.custom_tlvs.iter().map(proto_to_node_custom_tlv).collect();
35-
context.node.spontaneous_payment().send_with_custom_tlvs(
32+
let preimage = request
33+
.preimage
34+
.map(|p| {
35+
<[u8; 32]>::from_hex(&p).map(PaymentPreimage).map_err(|_| {
36+
LdkServerError::new(
37+
InvalidRequestError,
38+
"Invalid preimage, must be a 32-byte hex string.".to_string(),
39+
)
40+
})
41+
})
42+
.transpose()?;
43+
44+
let custom_tlvs: Vec<_> = request.custom_tlvs.iter().map(proto_to_node_custom_tlv).collect();
45+
46+
let payment_id = match (preimage, custom_tlvs.is_empty()) {
47+
(None, true) => context.node.spontaneous_payment().send(
48+
request.amount_msat,
49+
node_id,
50+
route_parameters,
51+
)?,
52+
(None, false) => context.node.spontaneous_payment().send_with_custom_tlvs(
3653
request.amount_msat,
3754
node_id,
3855
route_parameters,
3956
custom_tlvs,
40-
)?
57+
)?,
58+
(Some(preimage), true) => context.node.spontaneous_payment().send_with_preimage(
59+
request.amount_msat,
60+
node_id,
61+
preimage,
62+
route_parameters,
63+
)?,
64+
(Some(preimage), false) => {
65+
context.node.spontaneous_payment().send_with_preimage_and_custom_tlvs(
66+
request.amount_msat,
67+
node_id,
68+
custom_tlvs,
69+
preimage,
70+
route_parameters,
71+
)?
72+
},
4173
};
4274

43-
let response = SpontaneousSendResponse { payment_id: payment_id.to_string() };
44-
Ok(response)
75+
Ok(SpontaneousSendResponse { payment_id: payment_id.to_string() })
4576
}

0 commit comments

Comments
 (0)