plugins: lsps: refactor lsps2 and lsps0 error enums
What changed, and why it matters
This is a routine code cleanup in Core Lightning's LSPS (Lightning Service Provider Specification) plugin. It reorganizes how error codes are defined and generated, moving from hand-written helper functions to a macro-generated trait pattern. There is no security fix or behavior change visible in the diff.
No security action required. Treat as normal refactoring review; verify downstream tests pass and that no LSPS2 `ClientRejected` callers were missed during the move to lsps0.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors JSON-RPC error construction in the lsps-plugin crate. It introduces the paste crate and a rpc_error_methods! macro that generates RpcError::{method} and RpcError::{method}_with_data helpers via a trait (RpcErrorExt). LSPS0 and LSPS2 protocol-specific error codes are split into their own modules and traits (LSPS0RpcErrorExt, LSPS2RpcErrorExt). The lsps2::Error enum loses the ClientRejected variant (now handled in lsps0), and its From<Error> implementation uses the new trait methods. Call sites import the traits with RpcErrorExt as _.
Changed components
plugins/lsps-plugin/src/proto/jsonrpc.rsplugins/lsps-plugin/src/proto/lsps0.rsplugins/lsps-plugin/src/proto/lsps2.rsplugins/lsps-plugin/src/jsonrpc/server.rsplugins/lsps-plugin/src/lsps2/handler.rsplugins/lsps-plugin/Cargo.tomlCargo.lockInspect captured patch +92 / −106
diff --git a/Cargo.lock b/Cargo.lock
index 1f87d148..bf4678ef 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -553,6 +553,7 @@ dependencies = [
"cln-rpc",
"hex",
"log",
+ "paste",
"rand 0.9.2",
"serde",
"serde_json",
@@ -1763,6 +1764,12 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
[[package]]
name = "pem"
version = "3.0.5"
diff --git a/plugins/lsps-plugin/Cargo.toml b/plugins/lsps-plugin/Cargo.toml
index 5ef5fa42..028aee20 100644
--- a/plugins/lsps-plugin/Cargo.toml
+++ b/plugins/lsps-plugin/Cargo.toml
@@ -20,6 +20,7 @@ cln-plugin = { workspace = true }
cln-rpc = { workspace = true }
hex = "0.4"
log = "0.4"
+paste = "1.0.15"
rand = "0.9"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
diff --git a/plugins/lsps-plugin/src/jsonrpc/server.rs b/plugins/lsps-plugin/src/jsonrpc/server.rs
index a9ada766..a012f6fa 100644
--- a/plugins/lsps-plugin/src/jsonrpc/server.rs
+++ b/plugins/lsps-plugin/src/jsonrpc/server.rs
@@ -1,4 +1,4 @@
-use crate::proto::jsonrpc::{Result, RpcError};
+use crate::proto::jsonrpc::{Result, RpcError, RpcErrorExt as _};
use async_trait::async_trait;
use log::{debug, trace};
use std::{collections::HashMap, sync::Arc};
diff --git a/plugins/lsps-plugin/src/lsps2/handler.rs b/plugins/lsps-plugin/src/lsps2/handler.rs
index 4177c695..4624bbc3 100644
--- a/plugins/lsps-plugin/src/lsps2/handler.rs
+++ b/plugins/lsps-plugin/src/lsps2/handler.rs
@@ -5,7 +5,7 @@ use crate::{
DS_MAIN_KEY, DS_SUB_KEY,
},
proto::{
- jsonrpc::{JsonRpcResponse as _, RequestObject, RpcError},
+ jsonrpc::{JsonRpcResponse, RequestObject, RpcError, RpcErrorExt as _},
lsps0::{Msat, ShortChannelId},
lsps2::{
compute_opening_fee,
diff --git a/plugins/lsps-plugin/src/proto/jsonrpc.rs b/plugins/lsps-plugin/src/proto/jsonrpc.rs
index 79f1d4ac..afc21140 100644
--- a/plugins/lsps-plugin/src/proto/jsonrpc.rs
+++ b/plugins/lsps-plugin/src/proto/jsonrpc.rs
@@ -205,6 +205,38 @@ where
}
}
+/// Macro to generate RpcError helper methods for protocol-specific error codes
+///
+/// This generates two methods for each error code:
+/// - `method_name(message)` - Creates error without data
+/// - `method_name_with_data(message, data)` - Creates error with data
+macro_rules! rpc_error_methods {
+ ($($method:ident => $code:expr),* $(,)?) => {
+ $(
+ paste::paste! {
+ fn $method<T: std::fmt::Display>(message: T) -> $crate::proto::jsonrpc::RpcError {
+ $crate::proto::jsonrpc::RpcError {
+ code: $code,
+ message: message.to_string(),
+ data: None,
+ }
+ }
+
+ fn [<$method _with_data>]<T: std::fmt::Display>(
+ message: T,
+ data: serde_json::Value,
+ ) -> $crate::proto::jsonrpc::RpcError {
+ $crate::proto::jsonrpc::RpcError {
+ code: $code,
+ message: message.to_string(),
+ data: Some(data),
+ }
+ }
+ }
+ )*
+ };
+}
+
/// # RpcError
///
/// Represents an error object in a JSON-RPC 2.0 Response object (section 5.1).
@@ -255,75 +287,20 @@ impl RpcError {
data: Some(data),
}
}
+}
- /// Invalid JSON was received by the server.
- /// An error occurred on the server while parsing the JSON text.
- pub fn parse_error<T: core::fmt::Display>(message: T) -> Self {
- Self::custom_error(PARSE_ERROR, message)
- }
-
- /// Invalid JSON was received by the server.
- /// An error occurred on the server while parsing the JSON text.
- pub fn parse_error_with_data<T: core::fmt::Display>(
- message: T,
- data: serde_json::Value,
- ) -> Self {
- Self::custom_error_with_data(PARSE_ERROR, message, data)
- }
-
- /// The JSON sent is not a valid Request object.
- pub fn invalid_request<T: core::fmt::Display>(message: T) -> Self {
- Self::custom_error(INVALID_REQUEST, message)
- }
-
- /// The JSON sent is not a valid Request object.
- pub fn invalid_request_with_data<T: core::fmt::Display>(
- message: T,
- data: serde_json::Value,
- ) -> Self {
- Self::custom_error_with_data(INVALID_REQUEST, message, data)
- }
-
- /// The method does not exist / is not available.
- pub fn method_not_found<T: core::fmt::Display>(message: T) -> Self {
- Self::custom_error(METHOD_NOT_FOUND, message)
- }
-
- /// The method does not exist / is not available.
- pub fn method_not_found_with_data<T: core::fmt::Display>(
- message: T,
- data: serde_json::Value,
- ) -> Self {
- Self::custom_error_with_data(METHOD_NOT_FOUND, message, data)
- }
-
- /// Invalid method parameter(s).
- pub fn invalid_params<T: core::fmt::Display>(message: T) -> Self {
- Self::custom_error(INVALID_PARAMS, message)
- }
-
- /// Invalid method parameter(s).
- pub fn invalid_params_with_data<T: core::fmt::Display>(
- message: T,
- data: serde_json::Value,
- ) -> Self {
- Self::custom_error_with_data(INVALID_PARAMS, message, data)
- }
-
- /// Internal JSON-RPC error.
- pub fn internal_error<T: core::fmt::Display>(message: T) -> Self {
- Self::custom_error(INTERNAL_ERROR, message)
- }
-
- /// Internal JSON-RPC error.
- pub fn internal_error_with_data<T: core::fmt::Display>(
- message: T,
- data: serde_json::Value,
- ) -> Self {
- Self::custom_error_with_data(INTERNAL_ERROR, message, data)
+pub trait RpcErrorExt {
+ rpc_error_methods! {
+ parse_error => PARSE_ERROR,
+ internal_error => INTERNAL_ERROR,
+ invalid_params => INVALID_PARAMS,
+ method_not_found => METHOD_NOT_FOUND,
+ invalid_request => INVALID_REQUEST,
}
}
+impl RpcErrorExt for RpcError {}
+
impl fmt::Display for RpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
diff --git a/plugins/lsps-plugin/src/proto/lsps0.rs b/plugins/lsps-plugin/src/proto/lsps0.rs
index a315c563..831a8c2e 100644
--- a/plugins/lsps-plugin/src/proto/lsps0.rs
+++ b/plugins/lsps-plugin/src/proto/lsps0.rs
@@ -1,4 +1,4 @@
-use crate::proto::jsonrpc::JsonRpcRequest;
+use crate::proto::jsonrpc::{JsonRpcRequest, RpcError};
use core::fmt;
use serde::{
de::{self},
@@ -13,19 +13,20 @@ pub const LSP_FEATURE_BIT: usize = 729;
// Required message type for BOLT8 transport.
pub const LSPS0_MESSAGE_TYPE: u16 = 37913;
-// Lsps0 error definitions. Are in the range 00000 to 00099.
-pub const CLIENT_REJECTED: i64 = 1;
-
-pub enum Error {
- ClientRejected(String),
+// Lsps0 specific error codes defined in BLIP-50.
+// Are in the range 00000 to 00099.
+pub mod error_codes {
+ pub const CLIENT_REJECTED: i64 = 001;
}
-impl Error {
- pub fn client_rejected(msg: String) -> Error {
- Self::ClientRejected(msg)
+pub trait LSPS0RpcErrorExt {
+ rpc_error_methods! {
+ client_rejected => error_codes::CLIENT_REJECTED,
}
}
+impl LSPS0RpcErrorExt for RpcError {}
+
/// Represents a monetary amount as defined in LSPS0.msat. Is converted to a
/// `String` in json messages with a suffix `_msat` or `_sat` and internally
/// represented as Millisatoshi `u64`.
diff --git a/plugins/lsps-plugin/src/proto/lsps2.rs b/plugins/lsps-plugin/src/proto/lsps2.rs
index 194c1ff4..888e9301 100644
--- a/plugins/lsps-plugin/src/proto/lsps2.rs
+++ b/plugins/lsps-plugin/src/proto/lsps2.rs
@@ -1,6 +1,6 @@
-use crate::{
- proto::jsonrpc::{JsonRpcRequest, RpcError},
- proto::lsps0::{DateTime, Msat, Ppm, ShortChannelId},
+use crate::proto::{
+ jsonrpc::{JsonRpcRequest, RpcError},
+ lsps0::{DateTime, Msat, Ppm, ShortChannelId},
};
use bitcoin::hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine};
use chrono::Utc;
@@ -12,55 +12,54 @@ pub mod failure_codes {
pub const UNKNOWN_NEXT_PEER: &'static str = "4010";
}
+// Lsps2 specific error codes defined in BLIP-52.
+// Are in the range 00200 to 00299.
+pub mod error_codes {
+ pub const INVALID_OPENING_FEE_PARAMS: i64 = 201;
+ pub const PAYMENT_SIZE_TOO_SMALL: i64 = 202;
+ pub const PAYMENT_SIZE_TOO_LARGE: i64 = 203;
+}
+
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
InvalidOpeningFeeParams,
PaymentSizeTooSmall,
PaymentSizeTooLarge,
- ClientRejected,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let err_str = match self {
- Error::InvalidOpeningFeeParams => "invalid opening fee params",
- Error::PaymentSizeTooSmall => "payment size too small",
- Error::PaymentSizeTooLarge => "payment size too large",
- Error::ClientRejected => "client rejected",
+ Error::InvalidOpeningFeeParams => "Invalid opening fee params",
+ Error::PaymentSizeTooSmall => "Payment size too small",
+ Error::PaymentSizeTooLarge => "Payment size too large",
};
write!(f, "{}", &err_str)
}
}
impl From<Error> for RpcError {
- fn from(value: Error) -> Self {
- match value {
- Error::InvalidOpeningFeeParams => RpcError {
- code: 201,
- message: "invalid opening fee params".to_string(),
- data: None,
- },
- Error::PaymentSizeTooSmall => RpcError {
- code: 202,
- message: "payment size too small".to_string(),
- data: None,
- },
- Error::PaymentSizeTooLarge => RpcError {
- code: 203,
- message: "payment size too large".to_string(),
- data: None,
- },
- Error::ClientRejected => RpcError {
- code: 001,
- message: "client rejected".to_string(),
- data: None,
- },
+ fn from(error: Error) -> Self {
+ match error {
+ Error::InvalidOpeningFeeParams => RpcError::invalid_opening_fee_params(error),
+ Error::PaymentSizeTooSmall => RpcError::payment_size_too_small(error),
+ Error::PaymentSizeTooLarge => RpcError::payment_size_too_large(error),
}
}
}
impl core::error::Error for Error {}
+pub trait LSPS2RpcErrorExt {
+ rpc_error_methods! {
+ invalid_opening_fee_params => error_codes::INVALID_OPENING_FEE_PARAMS,
+ payment_size_too_small => error_codes::PAYMENT_SIZE_TOO_SMALL,
+ payment_size_too_large => error_codes::PAYMENT_SIZE_TOO_LARGE
+ }
+}
+
+impl LSPS2RpcErrorExt for RpcError {}
+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Lsps2GetInfoRequest {
#[serde(skip_serializing_if = "Option::is_none")]
diff --git a/plugins/lsps-plugin/src/proto/mod.rs b/plugins/lsps-plugin/src/proto/mod.rs
index e2bdc161..cd967793 100644
--- a/plugins/lsps-plugin/src/proto/mod.rs
+++ b/plugins/lsps-plugin/src/proto/mod.rs
@@ -1,3 +1,4 @@
+#[macro_use]
pub mod jsonrpc;
pub mod lsps0;
pub mod lsps2;
Why this scored 14/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.