diff --git a/plugins/lsps-plugin/src/core/transport.rs b/plugins/lsps-plugin/src/core/transport.rs index 0d86afbff..96aaea1d5 100644 --- a/plugins/lsps-plugin/src/core/transport.rs +++ b/plugins/lsps-plugin/src/core/transport.rs @@ -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 for Error { @@ -42,7 +41,6 @@ pub trait Transport: Send + Sync { async fn send(&self, peer: &PublicKey, request: &str) -> Result; 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 JsonRpcClient { peer_id: &PublicKey, method: &str, params: Option, + id: Option, ) -> Result> { - 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 = - self.send_request(peer_id, method, &request, id).await?; + let response: JsonRpcResponse = self.send_request(peer_id, &request).await?; Ok(response) } @@ -97,13 +92,8 @@ impl JsonRpcClient { 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 = - self.send_request(peer_id, method, &request, id).await?; + let request = request.into_request(); + let response: JsonRpcResponse = self.send_request(peer_id, &request).await?; Ok(response) } @@ -114,14 +104,13 @@ impl JsonRpcClient { method: &str, params: Option, ) -> 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 JsonRpcClient { 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( &self, peer: &PublicKey, - method: &str, payload: &RP, - id: String, ) -> Result> 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( - &self, - peer_id: &PublicKey, - method: &str, - payload: &RP, - ) -> Result<()> + async fn send_notification(&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 { diff --git a/plugins/lsps-plugin/src/lsps0/handler.rs b/plugins/lsps-plugin/src/lsps0/handler.rs index e6a5a8386..286c01882 100644 --- a/plugins/lsps-plugin/src/lsps0/handler.rs +++ b/plugins/lsps-plugin/src/lsps0/handler.rs @@ -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(); diff --git a/plugins/lsps-plugin/src/lsps2/handler.rs b/plugins/lsps-plugin/src/lsps2/handler.rs index 250fd8b0d..cea15f2d1 100644 --- a/plugins/lsps-plugin/src/lsps2/handler.rs +++ b/plugins/lsps-plugin/src/lsps2/handler.rs @@ -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)) diff --git a/plugins/lsps-plugin/src/proto/jsonrpc.rs b/plugins/lsps-plugin/src/proto/jsonrpc.rs index 7c4f94fcc..24b307131 100644 --- a/plugins/lsps-plugin/src/proto/jsonrpc.rs +++ b/plugins/lsps-plugin/src/proto/jsonrpc.rs @@ -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) -> RequestObject + fn into_request(self) -> RequestObject + where + Self: Sized, + { + Self::into_request_with_id(self, Some(generate_random_id())) + } + + fn into_request_with_id(self, id: Option) -> RequestObject 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 -where - T: Serialize, -{ +pub struct RequestObject { /// **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!({