Skip to content

Commit c41f7a9

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

7 files changed

Lines changed: 103 additions & 10 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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +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+
use ldk_server_grpc::types::payment_kind;
3536

3637
const EVENT_TIMEOUT: Duration = Duration::from_secs(15);
3738

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

ldk-server-cli/src/main.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,11 @@ 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(
324+
long,
325+
help = "An optional hex-encoded 32-byte payment preimage. If provided, it will be used instead of generating a random one."
326+
)]
327+
preimage: Option<String>,
323328
},
324329
#[command(
325330
about = "Pay a BIP 21 URI, BIP 353 Human-Readable Name, BOLT11 invoice, or BOLT12 offer"
@@ -817,6 +822,7 @@ async fn main() {
817822
max_path_count,
818823
max_channel_saturation_power_of_half,
819824
custom_tlvs,
825+
preimage,
820826
} => {
821827
let amount_msat = amount.to_msat();
822828
let max_total_routing_fee_msat = max_total_routing_fee.map(|a| a.to_msat());
@@ -841,6 +847,7 @@ async fn main() {
841847
node_id,
842848
route_parameters: Some(route_parameters),
843849
custom_tlvs: proto_custom_tlvs,
850+
preimage,
844851
})
845852
.await,
846853
);

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: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
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;
16+
use ldk_node::CustomTlvRecord;
1417
use ldk_server_grpc::api::{SpontaneousSendRequest, SpontaneousSendResponse};
1518

1619
use crate::api::error::LdkServerError;
@@ -27,19 +30,52 @@ pub(crate) async fn handle_spontaneous_send_request(
2730

2831
let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?;
2932

30-
let payment_id = if request.custom_tlvs.is_empty() {
31-
context.node.spontaneous_payment().send(request.amount_msat, node_id, route_parameters)?
33+
let preimage = request
34+
.preimage
35+
.map(|p| {
36+
<[u8; 32]>::from_hex(&p).map(PaymentPreimage).map_err(|_| {
37+
LdkServerError::new(
38+
InvalidRequestError,
39+
"Invalid preimage, must be a 32-byte hex string.".to_string(),
40+
)
41+
})
42+
})
43+
.transpose()?;
44+
45+
let custom_tlvs: Option<Vec<CustomTlvRecord>> = if request.custom_tlvs.is_empty() {
46+
None
3247
} 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(
48+
Some(request.custom_tlvs.iter().map(proto_to_node_custom_tlv).collect())
49+
};
50+
51+
let payment_id = match (preimage, custom_tlvs) {
52+
(None, None) => context.node.spontaneous_payment().send(
53+
request.amount_msat,
54+
node_id,
55+
route_parameters,
56+
)?,
57+
(None, Some(custom_tlvs)) => context.node.spontaneous_payment().send_with_custom_tlvs(
3658
request.amount_msat,
3759
node_id,
3860
route_parameters,
3961
custom_tlvs,
40-
)?
62+
)?,
63+
(Some(preimage), None) => context.node.spontaneous_payment().send_with_preimage(
64+
request.amount_msat,
65+
node_id,
66+
preimage,
67+
route_parameters,
68+
)?,
69+
(Some(preimage), Some(custom_tlvs)) => {
70+
context.node.spontaneous_payment().send_with_preimage_and_custom_tlvs(
71+
request.amount_msat,
72+
node_id,
73+
custom_tlvs,
74+
preimage,
75+
route_parameters,
76+
)?
77+
},
4178
};
4279

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

0 commit comments

Comments
 (0)