-
Notifications
You must be signed in to change notification settings - Fork 48
gl-plugin: make lsp_invoice RPC call store additional invoice metadata #742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,12 @@ | ||||||||||||||||||||||||||
| use crate::config::Config; | ||||||||||||||||||||||||||
| use crate::pb::{self, node_server::Node}; | ||||||||||||||||||||||||||
| use crate::storage::StateStore; | ||||||||||||||||||||||||||
| use crate::storage::{LspInvoiceMeta, 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::{ | ||||||||||||||||||||||||||
|
|
@@ -266,6 +266,16 @@ impl Node for PluginNodeServer { | |||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||
| .map_err(|e| Status::new(Code::Internal, e.to_string()))?; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let meta = LspInvoiceMeta { | ||||||||||||||||||||||||||
| label: req.label.clone(), | ||||||||||||||||||||||||||
| payment_hash: res.payment_hash.to_string(), | ||||||||||||||||||||||||||
| requested_amount_msat: req.amount_msat, | ||||||||||||||||||||||||||
| bolt11: res.bolt11.clone(), | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| write_lsp_invoice_meta(rpc, meta).await | ||||||||||||||||||||||||||
| .map_err(|e| Status::new(Code::Internal, e.to_string()))?; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return Ok(Response::new(pb::LspInvoiceResponse { | ||||||||||||||||||||||||||
| bolt11: res.bolt11, | ||||||||||||||||||||||||||
| created_index: res.created_index.unwrap_or(0) as u32, | ||||||||||||||||||||||||||
|
|
@@ -326,6 +336,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 +356,20 @@ 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 = LspInvoiceMeta { | ||||||||||||||||||||||||||
| label: invoice_label.clone(), | ||||||||||||||||||||||||||
| payment_hash: res.payment_hash.clone(), | ||||||||||||||||||||||||||
| requested_amount_msat, | ||||||||||||||||||||||||||
| bolt11: res.bolt11.clone(), | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| write_lsp_invoice_meta(rpc, meta).await | ||||||||||||||||||||||||||
| .map_err(|e| Status::new(Code::Internal, e.to_string()))?; | ||||||||||||||||||||||||||
|
Comment on lines
+370
to
+371
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really want to fail the whole RPC call in the (unexpected) case that we can't write the meta data? We already negotiated a jit-channel invoice and stored it on CLN. I think it's enough to just |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Ok(Response::new(res.into())) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -837,6 +864,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(mut rpc: tokio::sync::MutexGuard<'_, ClnRpc>, meta: LspInvoiceMeta) | ||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: The signature is a bit weird, compared to the other callers of |
||||||||||||||||||||||||||
| -> Result<()> { | ||||||||||||||||||||||||||
| let record_serialized = serde_json::to_string(&meta) | ||||||||||||||||||||||||||
| .map_err(|e| Status::new(Code::Internal, e.to_string()))?; | ||||||||||||||||||||||||||
|
Comment on lines
+871
to
+872
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: This function returns an
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let datastore_req = cln_rpc::model::requests::DatastoreRequest { | ||||||||||||||||||||||||||
| key: vec![ | ||||||||||||||||||||||||||
| "gl".to_string(), | ||||||||||||||||||||||||||
| "jit_channels".to_string(), | ||||||||||||||||||||||||||
| meta.label, | ||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||
|
Comment on lines
+874
to
+879
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending on what info the reading part actually has, I'd rather use the
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initially I thought of using payment hashes here but was aware of some ambiguity between invoices. Since the original and reduced invoices have different amounts, payment hashes will also be different. In other words, we're storing an invoice with the key containing the original invoice. On GL client side when we intercept the invoice payment, the payment hash corresponds to the reduced invoice. Therefore, we'll fail to retrieve the original invoice by the reduced payment hash. My intention about using invoice label was in it's nature - it is unique among all created invoices. That means, both original and reduced invoice should have the same label (uniqueness applies between original and reduced invoices because the original invoice invoice is not stored on client). It's indeed an ugly way, but I'm not sure about any other ways |
||||||||||||||||||||||||||
| 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)] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,22 @@ impl StateStore for SledStateStore { | |
| .map_err(|e| e.into()) | ||
| } | ||
| } | ||
|
|
||
| /// A structure that is used for storing invoices that are requested through | ||
| /// [Node::lsp_invoice](pb::node_server::Node::lsp_invoice) RPC call. lsp_invoice | ||
| /// call does not guarantee that the returned invoice is for requesting JIT | ||
| /// channel - if there is a channel with enough liquidity, a simple bolt11 invoice | ||
| /// is created. | ||
| /// | ||
| /// 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 to do so, that's why this workaround | ||
| /// exists. | ||
| #[derive(Serialize, Deserialize)] | ||
| pub struct LspInvoiceMeta { | ||
| pub label: String, | ||
| pub payment_hash: String, | ||
| pub requested_amount_msat: u64, | ||
| pub bolt11: String, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think we could also store the |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to store the invoice meta data for a regular invoice. It will just double what we already have. Storing it for a jit-channel invoice is enough to identify it as such: If it's in the datastore, it automatically is a jit-channel invoice.