plugins: lsps: move id generation into jsonrpcrequest

There is no need to generate the id in some client when we can actually
generate it on a lower layer as we know by LSPS0 how we have to generate
it.

Signed-off-by: Peter Neuroth <pet.v.ne@gmail.com>
This commit is contained in:
Peter Neuroth
2025-12-03 16:30:18 +01:00
committed by madelinevibes
parent 5ed743e10d
commit 2546642c32
4 changed files with 52 additions and 93 deletions

View File

@@ -2,9 +2,6 @@ use crate::proto::jsonrpc::{JsonRpcRequest, JsonRpcResponse, RequestObject};
use async_trait::async_trait;
use bitcoin::secp256k1::PublicKey;
use core::fmt::Debug;
use log::{debug, error};
use rand::rngs::OsRng;
use rand::TryRngCore;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value;
use std::sync::Arc;
@@ -23,6 +20,8 @@ pub enum Error {
#[source]
source: serde_json::Error,
},
#[error("request is missing id")]
MissingId,
}
impl From<serde_json::Error> for Error {
@@ -42,7 +41,6 @@ pub trait Transport: Send + Sync {
async fn send(&self, peer: &PublicKey, request: &str) -> Result<String>;
async fn notify(&self, peer: &PublicKey, request: &str) -> Result<()>;
}
}
/// A typed JSON-RPC client that works with any transport implementation.
///
@@ -67,19 +65,16 @@ impl<T: Transport> JsonRpcClient<T> {
peer_id: &PublicKey,
method: &str,
params: Option<Value>,
id: Option<String>,
) -> Result<JsonRpcResponse<Value>> {
let id = generate_random_id();
debug!("Preparing request: method={}, id={}", method, id);
let request = RequestObject {
jsonrpc: "2.0".into(),
method: method.into(),
params,
id: Some(id.clone().into()),
id,
};
let response: JsonRpcResponse<Value> =
self.send_request(peer_id, method, &request, id).await?;
let response: JsonRpcResponse<Value> = self.send_request(peer_id, &request).await?;
Ok(response)
}
@@ -97,13 +92,8 @@ impl<T: Transport> JsonRpcClient<T> {
RQ: JsonRpcRequest + Serialize + Send + Sync,
RS: DeserializeOwned + Serialize + Debug + Send + Sync,
{
let method = RQ::METHOD;
let id = generate_random_id();
debug!("Preparing request: method={}, id={}", method, id);
let request = request.into_request(Some(id.clone().into()));
let response: JsonRpcResponse<RS> =
self.send_request(peer_id, method, &request, id).await?;
let request = request.into_request();
let response: JsonRpcResponse<RS> = self.send_request(peer_id, &request).await?;
Ok(response)
}
@@ -114,14 +104,13 @@ impl<T: Transport> JsonRpcClient<T> {
method: &str,
params: Option<Value>,
) -> Result<()> {
debug!("Preparing notification: method={}", method);
let request = RequestObject {
jsonrpc: "2.0".into(),
method: method.into(),
params,
id: None,
};
Ok(self.send_notification(peer_id, method, &request).await?)
Ok(self.send_notification(peer_id, &request).await?)
}
/// Sends a typed notification (no response expected).
@@ -129,88 +118,34 @@ impl<T: Transport> JsonRpcClient<T> {
where
RQ: JsonRpcRequest + Serialize + Send + Sync,
{
let method = RQ::METHOD;
debug!("Preparing notification: method={}", method);
let request = request.into_request(None);
Ok(self.send_notification(peer_id, method, &request).await?)
let request = request.into_request();
Ok(self.send_notification(peer_id, &request).await?)
}
async fn send_request<RS, RP>(
&self,
peer: &PublicKey,
method: &str,
payload: &RP,
id: String,
) -> Result<JsonRpcResponse<RS>>
where
RP: Serialize + Send + Sync,
RS: DeserializeOwned + Serialize + Debug + Send + Sync,
{
let request_json = serde_json::to_string(&payload)?;
debug!(
"Sending request: method={}, id={}, request={:?}",
method, id, &request_json
);
let start = tokio::time::Instant::now();
let res_str = self.transport.send(peer, &request_json).await?;
let elapsed = start.elapsed();
debug!(
"Received response: method={}, id={}, response={}, elapsed={}ms",
method,
id,
&res_str,
elapsed.as_millis()
);
Ok(serde_json::from_str(&res_str)?)
}
async fn send_notification<RP>(
&self,
peer_id: &PublicKey,
method: &str,
payload: &RP,
) -> Result<()>
async fn send_notification<RP>(&self, peer_id: &PublicKey, payload: &RP) -> Result<()>
where
RP: Serialize + Send + Sync,
{
let request_json = serde_json::to_string(&payload)?;
debug!("Sending notification: method={}", method);
let start = tokio::time::Instant::now();
self.transport.notify(peer_id, &request_json).await?;
let elapsed = start.elapsed();
debug!(
"Sent notification: method={}, elapsed={}ms",
method,
elapsed.as_millis()
);
Ok(())
}
}
/// Generates a random ID for JSON-RPC requests.
///
/// Uses a secure random number generator to create a hex-encoded ID. Falls back
/// to a timestamp-based ID if random generation fails.
fn generate_random_id() -> String {
let mut bytes = [0u8; 10];
match OsRng.try_fill_bytes(&mut bytes) {
Ok(_) => hex::encode(bytes),
Err(e) => {
// Fallback to a timestamp-based ID if random generation fails
error!(
"Failed to generate random ID: {}, falling back to timestamp",
e
);
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
format!("fallback-{}", timestamp)
}
}
}
#[cfg(test)]
mod test_json_rpc {

View File

@@ -60,7 +60,7 @@ mod test {
lsps2_enabled: false,
};
let request = Lsps0listProtocolsRequest {}.into_request(Some("test-id".to_string()));
let request = Lsps0listProtocolsRequest {}.into_request();
let payload = create_wrapped_request(&request);
let result = handler.handle(&payload).await.unwrap();
@@ -77,7 +77,7 @@ mod test {
lsps2_enabled: true,
};
let request = Lsps0listProtocolsRequest {}.into_request(Some("test-id".to_string()));
let request = Lsps0listProtocolsRequest {}.into_request();
let payload = create_wrapped_request(&request);
let result = handler.handle(&payload).await.unwrap();

View File

@@ -1022,7 +1022,7 @@ mod tests {
*fake.lsps2_getpolicy_response.lock().unwrap() = Some(params);
let handler = Lsps2GetInfoHandler::new(fake, promise_secret);
let request = Lsps2GetInfoRequest { token: None }.into_request(Some("test-id".to_string()));
let request = Lsps2GetInfoRequest { token: None }.into_request();
let payload = create_wrapped_request(&request);
let result = handler.handle(&payload).await.unwrap();
@@ -1053,7 +1053,7 @@ mod tests {
data: None,
});
let handler = Lsps2GetInfoHandler::new(fake, [0; 32]);
let request = Lsps2GetInfoRequest { token: None }.into_request(Some("test-id".to_string()));
let request = Lsps2GetInfoRequest { token: None }.into_request();
let payload = create_wrapped_request(&request);
let result = handler.handle(&payload).await;
@@ -1084,7 +1084,7 @@ mod tests {
opening_fee_params: buy,
payment_size_msat: Some(Msat(2_000_000)),
}
.into_request(Some("ok-fixed".into()));
.into_request();
let payload = create_wrapped_request(&req);
let out = handler.handle(&payload).await.unwrap();
@@ -1116,7 +1116,7 @@ mod tests {
opening_fee_params: buy,
payment_size_msat: None,
}
.into_request(Some("ok-var".into()));
.into_request();
let payload = create_wrapped_request(&req);
let out = handler.handle(&payload).await.unwrap();
@@ -1136,7 +1136,7 @@ mod tests {
opening_fee_params: buy_wrong,
payment_size_msat: Some(Msat(2_000_000)),
}
.into_request(Some("bad-promise".into()));
.into_request();
let err1 = handler
.handle(&create_wrapped_request(&req_wrong))
.await
@@ -1150,7 +1150,7 @@ mod tests {
opening_fee_params: buy_past,
payment_size_msat: Some(Msat(2_000_000)),
}
.into_request(Some("past-valid".into()));
.into_request();
let err2 = handler
.handle(&create_wrapped_request(&req_past))
.await
@@ -1190,7 +1190,7 @@ mod tests {
opening_fee_params: buy,
payment_size_msat: Some(Msat(9_999)), // strictly less than min_fee => opening_fee >= payment_size
}
.into_request(Some("too-small".into()));
.into_request();
let err = handler
.handle(&create_wrapped_request(&req))
@@ -1232,7 +1232,7 @@ mod tests {
opening_fee_params: buy,
payment_size_msat: Some(Msat(u64::MAX / 2)),
}
.into_request(Some("overflow".into()));
.into_request();
let err = handler
.handle(&create_wrapped_request(&req))

View File

@@ -1,3 +1,4 @@
use rand::{rngs::OsRng, TryRngCore as _};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{self, Value};
use std::fmt;
@@ -16,7 +17,14 @@ pub const INTERNAL_ERROR: i64 = -32603;
/// request format.
pub trait JsonRpcRequest: Serialize {
const METHOD: &'static str;
fn into_request(self, id: Option<String>) -> RequestObject<Self>
fn into_request(self) -> RequestObject<Self>
where
Self: Sized,
{
Self::into_request_with_id(self, Some(generate_random_id()))
}
fn into_request_with_id(self, id: Option<String>) -> RequestObject<Self>
where
Self: Sized,
{
@@ -29,6 +37,25 @@ pub trait JsonRpcRequest: Serialize {
}
}
/// Generates a random ID for JSON-RPC requests.
///
/// Uses a secure random number generator to create a hex-encoded ID. Falls back
/// to a timestamp-based ID if random generation fails.
fn generate_random_id() -> String {
let mut bytes = [0u8; 10];
match OsRng.try_fill_bytes(&mut bytes) {
Ok(_) => hex::encode(bytes),
Err(_) => {
// Fallback to a timestamp-based ID if random generation fails
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
format!("fallback-{}", timestamp)
}
}
}
/// # RequestObject
///
/// Represents a JSON-RPC 2.0 Request object, as defined in section 4 of the
@@ -41,10 +68,7 @@ pub trait JsonRpcRequest: Serialize {
/// to allow it to be encoded as JSON. Typically this will be a struct
/// implementing the `JsonRpcRequest` trait.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RequestObject<T>
where
T: Serialize,
{
pub struct RequestObject<T> {
/// **REQUIRED**. MUST be `"2.0"`.
pub jsonrpc: String,
/// **REQUIRED**. The method to be invoked.
@@ -395,7 +419,7 @@ mod test_message_serialization {
impl JsonRpcRequest for SayHelloRequest {
const METHOD: &'static str = "say_hello";
}
let rpc_request = SayHelloRequest.into_request(Some("unique-id-123".into()));
let rpc_request = SayHelloRequest.into_request();
assert!(!serde_json::to_string(&rpc_request)
.expect("could not convert to json")
.contains("\"params\""));
@@ -416,7 +440,7 @@ mod test_message_serialization {
name: "Satoshi".to_string(),
age: 99,
}
.into_request(Some("unique-id-123".into()));
.into_request_with_id(Some("unique-id-123".into()));
let json_value: serde_json::Value = serde_json::to_value(&rpc_request).unwrap();
let expected_value: serde_json::Value = serde_json::json!({