Skip to content

Commit deac94d

Browse files
committed
gl-plugin: Skip JIT channel negotiation when sufficient capacity exists
Before creating an LSPS2 invoice that requires JIT channel negotiation, check if the node already has sufficient incoming capacity to receive the payment directly. When the receivable capacity (with a 5% buffer for fees) exceeds the invoice amount, create a regular invoice instead of negotiating with the LSP. This avoids unnecessary LSP fees and channel opening costs when the node can already receive the payment. The capacity check: - Sums receivable_msat across all CHANNELD_NORMAL channels with connected peers - Applies a 5% buffer to account for routing fees - Falls back to JIT negotiation if capacity is insufficient or for 'any amount' invoices
1 parent 5e009ad commit deac94d

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

libs/gl-plugin/src/node/mod.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,71 @@ impl Node for PluginNodeServer {
193193

194194
let mut rpc = rpc_arc.lock().await;
195195

196+
// Check if we have sufficient incoming capacity to skip JIT channel negotiation.
197+
// We require capacity + 5% buffer to account for fees and routing.
198+
// Only check for specific amounts (not "any" amount invoices).
199+
if req.amount_msat > 0 {
200+
let receivable = self
201+
.get_receivable_capacity(&mut rpc)
202+
.await
203+
.unwrap_or(0);
204+
205+
// Add 5% buffer: capacity * 1.05 >= amount
206+
// Equivalent to: capacity * 105 >= amount * 100
207+
let has_sufficient_capacity = receivable
208+
.saturating_mul(105)
209+
.checked_div(100)
210+
.map(|buffered| buffered >= req.amount_msat)
211+
.unwrap_or(false);
212+
213+
if has_sufficient_capacity {
214+
log::info!(
215+
"Sufficient incoming capacity ({} msat) for invoice amount ({} msat), creating regular invoice",
216+
receivable,
217+
req.amount_msat
218+
);
219+
220+
// Create a regular invoice without JIT channel negotiation
221+
let invreq = crate::requests::Invoice {
222+
amount_msat: cln_rpc::primitives::AmountOrAny::Amount(
223+
cln_rpc::primitives::Amount::from_msat(req.amount_msat),
224+
),
225+
description: req.description.clone(),
226+
label: req.label.clone(),
227+
expiry: None,
228+
fallbacks: None,
229+
preimage: None,
230+
cltv: Some(144),
231+
deschashonly: None,
232+
exposeprivatechannels: None,
233+
dev_routes: None,
234+
};
235+
236+
let res: crate::responses::Invoice = rpc
237+
.call_raw("invoice", &invreq)
238+
.await
239+
.map_err(|e| Status::new(Code::Internal, e.to_string()))?;
240+
241+
return Ok(Response::new(pb::LspInvoiceResponse {
242+
bolt11: res.bolt11,
243+
created_index: 0, // Not available in our Invoice response
244+
expires_at: res.expiry_time,
245+
payment_hash: hex::decode(&res.payment_hash)
246+
.map_err(|e| Status::new(Code::Internal, format!("Invalid payment_hash: {}", e)))?,
247+
payment_secret: res
248+
.payment_secret
249+
.map(|s| hex::decode(&s).unwrap_or_default())
250+
.unwrap_or_default(),
251+
}));
252+
}
253+
254+
log::info!(
255+
"Insufficient incoming capacity ({} msat) for invoice amount ({} msat), negotiating JIT channel",
256+
receivable,
257+
req.amount_msat
258+
);
259+
}
260+
196261
// Get the CLN version to determine which RPC method to use
197262
let version = rpc
198263
.call_typed(&cln_rpc::model::requests::GetinfoRequest {})
@@ -783,6 +848,29 @@ impl PluginNodeServer {
783848
Ok(res)
784849
}
785850

851+
/// Get the total receivable capacity across all active channels.
852+
///
853+
/// Returns the sum of `receivable_msat` for all channels in
854+
/// `CHANNELD_NORMAL` state with a connected peer.
855+
async fn get_receivable_capacity(&self, rpc: &mut cln_rpc::ClnRpc) -> Result<u64, Error> {
856+
use cln_rpc::primitives::ChannelState;
857+
858+
let res = rpc
859+
.call_typed(&cln_rpc::model::requests::ListpeerchannelsRequest { id: None })
860+
.await?;
861+
862+
let total: u64 = res
863+
.channels
864+
.into_iter()
865+
.filter(|c| c.peer_connected && c.state == ChannelState::CHANNELD_NORMAL)
866+
.filter_map(|c| c.receivable_msat)
867+
.map(|a| a.msat())
868+
.sum();
869+
870+
log::debug!("Total receivable capacity: {} msat", total);
871+
Ok(total)
872+
}
873+
786874
async fn get_reconnect_peers(
787875
&self,
788876
) -> Result<Vec<cln_rpc::model::requests::ConnectRequest>, Error> {

0 commit comments

Comments
 (0)