diff --git a/libs/gl-plugin/src/node/mod.rs b/libs/gl-plugin/src/node/mod.rs index 0ac92c773..c2c37e67f 100644 --- a/libs/gl-plugin/src/node/mod.rs +++ b/libs/gl-plugin/src/node/mod.rs @@ -1,12 +1,12 @@ use crate::config::Config; use crate::pb::{self, node_server::Node}; -use crate::storage::StateStore; +use crate::storage::{JitRequestMeta, StateStore}; use crate::{messages, Event}; use crate::{stager, tramp}; -use anyhow::{Context, Error, Result}; +use anyhow::{anyhow, Context, Error, Result}; use base64::{engine::general_purpose, Engine as _}; use bytes::BufMut; -use cln_rpc::Notification; +use cln_rpc::{ClnRpc, Notification}; use gl_client::metrics::{savings_percent, signer_state_request_wire_bytes}; use gl_client::persist::{State, StateSketch}; use governor::{ @@ -326,6 +326,9 @@ impl Node for PluginNodeServer { .div_ceil(1_000_000); std::cmp::max(min_fee, proportional_fee) }; + + let requested_amount_msat = req.amount_msat.clone(); + let invoice_label = req.label.clone(); // Use the new RPC method name for versions > v25.05gl1 let mut res = if *version > *"v25.05gl1" { @@ -343,6 +346,23 @@ impl Node for PluginNodeServer { }; res.opening_fee_msat = opening_fee_msat; + + // A JIT channel has now been negotiated with the LSP for this + // invoice. So, we're storing some data with the original requested + // amount. + let meta = JitRequestMeta { + label: invoice_label.clone(), + payment_hash: res.payment_hash.clone(), + requested_amount_msat, + expected_amount_msat: requested_amount_msat.saturating_sub(opening_fee_msat), + bolt11: res.bolt11.clone(), + lsp_id, + }; + + if let Err(e) = write_lsp_invoice_meta(&mut rpc, meta).await { + warn!("Failed to write LSP invoice meta: {}", e); + } + Ok(Response::new(res.into())) } @@ -837,6 +857,33 @@ impl Node for PluginNodeServer { } } +/// Writes `LspInvoiceMeta` using datastore request. `LspInvoiceMeta` is useful for defining +/// some additional information regarding invoice being requested trough Greenlight. +async fn write_lsp_invoice_meta(rpc: &mut ClnRpc, meta: JitRequestMeta) + -> Result<()> { + let record_serialized = + serde_json::to_string(&meta).context("failed to serialize LspInvoiceMeta")?; + + let datastore_req = cln_rpc::model::requests::DatastoreRequest { + key: vec![ + "gl".to_string(), + "jit_channels".to_string(), + meta.label, + ], + string: Some(record_serialized), + hex: None, + mode: Some(cln_rpc::model::requests::DatastoreMode::CREATE_OR_REPLACE), + generation: None, + }; + + rpc.call_typed(&datastore_req).await.map_err( + |e| + anyhow!("Failed to store JIT channel negotiation data in datastore: {}", e) + )?; + + Ok(()) +} + use cln_grpc::pb::node_server::NodeServer; #[derive(Clone, Debug)] diff --git a/libs/gl-plugin/src/storage.rs b/libs/gl-plugin/src/storage.rs index d360f6926..ea0c41861 100644 --- a/libs/gl-plugin/src/storage.rs +++ b/libs/gl-plugin/src/storage.rs @@ -2,6 +2,7 @@ pub use gl_client::persist::State; use log::debug; +use serde::{Deserialize, Serialize}; use thiserror::Error; use tonic::async_trait; @@ -66,3 +67,31 @@ impl StateStore for SledStateStore { .map_err(|e| e.into()) } } + +/// A structure that is used for storing JIT channel requests metadata that is +/// requested through [Node::lsp_invoice](pb::node_server::Node::lsp_invoice) +/// RPC call. +/// +/// This structure is stored in CLN datastore. The reason of why do we need this +/// structure instead of querying invoices table is that we want to distinguish +/// incomming payments whether they were for JIT channel opening or just a simple +/// payment. Currently, CLN does not allow this, that's why this workaround +/// exists. +#[derive(Serialize, Deserialize)] +pub struct JitRequestMeta { + /// A label of the requested invoice. + pub label: String, + /// Payment hash of the requested invoice. + pub payment_hash: String, + /// The requested amount of msats. + pub requested_amount_msat: u64, + /// The expected (reduced) amount if msats. + /// + /// Note that expected_amount_msat <= requested_amount_msat since + /// expected_amount_msat = requested_amount_msat + lsp_fee. + pub expected_amount_msat: u64, + /// Original Bolt11 invoice which includes requested_amount_msat. + pub bolt11: String, + /// ID of the LSP through which the invoice was requested. + pub lsp_id: String, +}