lsp_plugin: pass-through invoice params
What changed, and why it matters
This commit changes a Core Lightning plugin so that when a user requests a just-in-time Lightning channel, the label and description they provide are passed through to the generated invoice instead of being replaced with placeholder or auto-generated values. It also removes an unused label generator and simplifies how payment sizes are handled. There is no direct security bug being fixed here; it is mostly an API usability and correctness improvement.
No immediate security action required. Treat as a normal code-quality/API-correctness change. Reviewers may want to confirm that amount_msat, description, and label are validated or sanitized by the underlying invoice RPC, since the plugin now forwards them directly.
Security signals we found
Hardcoded placeholder values replaced with user-supplied parameters
Removed unused gen_label helper
RPC parameter pass-through refactor
Evidence from the diff
The patch modifies plugins/lsps-plugin/src/client.rs and tests/test_cln_lsps.py. In on_lsps_lsps2_buy, the manual conversion from AmountOrAny to Msat is removed and the raw req.payment_size_msat is forwarded. In on_lsps_jitchannel, the Request struct is changed to accept amount_msat, description, and label directly from the caller, and those values are passed into the subsequent lsps-lsps2-buy and invoice RPC calls. Previously the description was hardcoded as “TODO” and the label was generated with a timestamp. The gen_label helper and its import are removed. The test is updated to supply the new required parameters. No cryptographic, authorization, or input-validation changes are visible.
Changed components
plugins/lsps-plugin/src/client.rstests/test_cln_lsps.pylsps_jitchannel RPC methodlsps_lsps2_buy RPC methodInspect captured patch +24 / −39
diff --git a/plugins/lsps-plugin/src/client.rs b/plugins/lsps-plugin/src/client.rs
index 484facc2..1037bf55 100644
--- a/plugins/lsps-plugin/src/client.rs
+++ b/plugins/lsps-plugin/src/client.rs
@@ -167,19 +167,8 @@ async fn on_lsps_lsps2_buy(
.context("Failed to create Bolt8Transport")?;
let client = JsonRpcClient::new(transport);
- // Convert from AmountOrAny to Msat.
- let payment_size_msat = if let Some(payment_size) = req.payment_size_msat {
- match payment_size {
- AmountOrAny::Amount(amount) => Some(Msat::from_msat(amount.msat())),
- AmountOrAny::Any => None,
- }
- } else {
- None
- };
-
let selected_params = req.opening_fee_params;
-
- if let Some(payment_size) = payment_size_msat {
+ if let Some(payment_size) = req.payment_size_msat {
if payment_size < selected_params.min_payment_size_msat {
return Err(anyhow!(
"Requested payment size {}msat is below minimum {}msat required by LSP",
@@ -224,7 +213,7 @@ async fn on_lsps_lsps2_buy(
debug!("Calling lsps2.buy for peer {}", req.lsp_id);
let buy_req = Lsps2BuyRequest {
opening_fee_params: selected_params, // Pass the chosen params back
- payment_size_msat,
+ payment_size_msat: req.payment_size_msat,
};
let buy_res: Lsps2BuyResponse = client
.call_typed(buy_req)
@@ -270,16 +259,18 @@ async fn on_lsps_jitchannel(
#[derive(Deserialize)]
struct Request {
lsp_id: String,
- // Optional: for fixed-amount invoices
- payment_size_msat: Option<AmountOrAny>,
// Optional: for discounts/API keys
token: Option<String>,
+ // Pass-through of cln invoice rpc params
+ pub amount_msat: cln_rpc::primitives::AmountOrAny,
+ pub description: String,
+ pub label: String,
}
let req: Request = serde_json::from_value(v).context("Failed to parse request JSON")?;
debug!(
"Handling lsps-buy-jit-channel request for peer {} with payment_size {:?} and token {:?}",
- req.lsp_id, req.payment_size_msat, req.token
+ req.lsp_id, req.amount_msat, req.token
);
let dir = p.configuration().lightning_dir;
@@ -322,13 +313,18 @@ async fn on_lsps_jitchannel(
info!("Selected fee parameters: {:?}", selected_params);
+ let payment_size_msat = match req.amount_msat {
+ AmountOrAny::Amount(amount) => Some(Msat::from_msat(amount.msat())),
+ AmountOrAny::Any => None,
+ };
+
// 3. Request channel from LSP.
let buy_res: Lsps2BuyResponse = cln_client
.call_raw(
"lsps-lsps2-buy",
&ClnRpcLsps2BuyRequest {
lsp_id: req.lsp_id.clone(),
- payment_size_msat: req.payment_size_msat,
+ payment_size_msat,
opening_fee_params: selected_params.clone(),
},
)
@@ -356,20 +352,14 @@ async fn on_lsps_jitchannel(
cltv_expiry_delta: u16::try_from(buy_res.lsp_cltv_expiry_delta)?,
};
- let amount_msat = if let Some(payment_size) = req.payment_size_msat {
- payment_size
- } else {
- AmountOrAny::Any
- };
-
let inv: cln_rpc::model::responses::InvoiceResponse = cln_client
.call_raw(
"invoice",
&InvoiceRequest {
- amount_msat,
+ amount_msat: req.amount_msat,
dev_routes: Some(vec![vec![hint]]),
- description: String::from("TODO"), // TODO: Pass down description from rpc call
- label: gen_label(None), // TODO: Pass down label from rpc call
+ description: req.description,
+ label: req.label,
expiry: Some(expiry as u64),
cltv: Some(u32::try_from(6 + 2)?), // TODO: FETCH REAL VALUE!
deschashonly: None,
@@ -619,15 +609,6 @@ async fn ensure_lsp_connected(cln_client: &mut ClnRpc, lsp_id: &str) -> Result<(
Ok(())
}
-/// Generates a unique label from an optional `String`. The given label is
-/// appended by a timestamp (now).
-fn gen_label(label: Option<&str>) -> String {
- let now = Utc::now();
- let millis = now.timestamp_millis();
- let l = label.unwrap_or_else(|| "lsps2.buy");
- format!("{}_{}", l, millis)
-}
-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct LspsBuyJitChannelResponse {
bolt11: String,
@@ -668,8 +649,7 @@ pub struct RoutehintHopDev {
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ClnRpcLsps2BuyRequest {
lsp_id: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- payment_size_msat: Option<AmountOrAny>,
+ payment_size_msat: Option<Msat>,
opening_fee_params: OpeningFeeParams,
}
diff --git a/tests/test_cln_lsps.py b/tests/test_cln_lsps.py
index 15f1df33..a7b66eb4 100644
--- a/tests/test_cln_lsps.py
+++ b/tests/test_cln_lsps.py
@@ -86,7 +86,7 @@ def test_lsps2_buy(node_factory):
res = l1.rpc.lsps_lsps2_getinfo(lsp_id=l2.info['id'])
params = res["opening_fee_params_menu"][0]
- res = l1.rpc.lsps_lsps2_buy(lsp_id=l2.info['id'], payment_size_msat=None, opening_fee_params=params)
+ res = l1.rpc.lsps_lsps2_buy(lsp_id=l2.info['id'], opening_fee_params=params)
assert res
@@ -133,7 +133,12 @@ def test_lsps2_buyjitchannel_no_mpp_var_invoice(node_factory, bitcoind):
chanid = only_one(l3.rpc.listpeerchannels(l2.info['id'])['channels'])['short_channel_id']
- inv = l1.rpc.lsps_jitchannel(lsp_id=l2.info['id'])
+ inv = l1.rpc.lsps_jitchannel(
+ lsp_id=l2.info['id'],
+ amount_msat="any",
+ description="lsp-jit-channel-0",
+ label="lsp-jit-channel-0"
+ )
assert inv
dec = l3.rpc.decode(inv['bolt11'])
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.