use crate::app::Web3ProxyApp; use crate::errors::Web3ProxyError; use crate::frontend::authorization::{Authorization, RequestMetadata, RequestOrMethod}; use crate::response_cache::JsonRpcResponseEnum; use axum::response::Response; use derive_more::From; use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor}; use serde::{Deserialize, Serialize}; use serde_json::json; use serde_json::value::{to_raw_value, RawValue}; use std::borrow::Cow; use std::fmt; use std::sync::{atomic, Arc}; use std::time::Duration; use tokio::time::sleep; pub trait JsonRpcParams = fmt::Debug + serde::Serialize + Send + Sync + 'static; pub trait JsonRpcResultData = serde::Serialize + serde::de::DeserializeOwned + fmt::Debug + Send; // TODO: &str here instead of String should save a lot of allocations // TODO: generic type for params? #[derive(Clone, Deserialize, Serialize)] pub struct JsonRpcRequest { pub jsonrpc: String, /// id could be a stricter type, but many rpcs do things against the spec pub id: Box, pub method: String, /// TODO: skip serializing if serde_json::Value::Null pub params: serde_json::Value, } #[derive(From)] pub enum JsonRpcId { None, Number(u64), String(String), } impl JsonRpcId { pub fn to_raw_value(self) -> Box { // TODO: is this a good way to do this? we should probably use references match self { Self::None => Default::default(), Self::Number(x) => { serde_json::from_value(json!(x)).expect("number id should always work") } Self::String(x) => serde_json::from_str(&x).expect("string id should always work"), } } } impl JsonRpcRequest { // TODO: Web3ProxyResult? can this even fail? pub fn new(id: JsonRpcId, method: String, params: serde_json::Value) -> anyhow::Result { let x = Self { jsonrpc: "2.0".to_string(), id: id.to_raw_value(), method, params, }; Ok(x) } pub fn validate_method(&self) -> bool { self.method .chars() .all(|x| x.is_ascii_alphanumeric() || x == '_' || x == '(' || x == ')') } } impl fmt::Debug for JsonRpcRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // TODO: the default formatter takes forever to write. this is too quiet though // TODO: how should we include params in this? maybe just the length? f.debug_struct("JsonRpcRequest") .field("id", &self.id) .field("method", &self.method) .field("params", &self.params) .finish() } } /// Requests can come in multiple formats #[derive(Debug, From, Serialize)] pub enum JsonRpcRequestEnum { Batch(Vec), Single(JsonRpcRequest), } impl JsonRpcRequestEnum { pub fn first_id(&self) -> Option> { match self { Self::Batch(x) => x.first().map(|x| x.id.clone()), Self::Single(x) => Some(x.id.clone()), } } /// returns the id of the first invalid result (if any). None is good pub fn validate(&self) -> Option> { match self { Self::Batch(x) => x .iter() .find_map(|x| (!x.validate_method()).then_some(x.id.clone())), Self::Single(x) => { if x.validate_method() { None } else { Some(x.id.clone()) } } } } /// returns the id of the first invalid result (if any). None is good pub async fn tarpit_invalid( &self, app: &Web3ProxyApp, authorization: &Arc, duration: Duration, ) -> Result<(), Response> { let err_id = match self.validate() { None => return Ok(()), Some(x) => x, }; let size = serde_json::to_string(&self) .expect("JsonRpcRequestEnum should always serialize") .len(); let request = RequestOrMethod::Method("invalid_method", size); // TODO: create a stat so we can penalize // TODO: what request size let metadata = RequestMetadata::new(app, authorization.clone(), request, None).await; metadata .user_error_response .store(true, atomic::Ordering::Release); let response = Web3ProxyError::BadRequest("request failed validation".into()); metadata.add_response(&response); let response = response.into_response_with_id(Some(err_id)); // TODO: variable duration depending on the IP sleep(duration).await; let _ = metadata.try_send_arc_stat(); Err(response) } } impl<'de> Deserialize<'de> for JsonRpcRequestEnum { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(field_identifier, rename_all = "lowercase")] enum Field { JsonRpc, Id, Method, Params, } struct JsonRpcBatchVisitor; impl<'de> Visitor<'de> for JsonRpcBatchVisitor { type Value = JsonRpcRequestEnum; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("JsonRpcRequestEnum") } fn visit_seq(self, mut seq: V) -> Result where V: SeqAccess<'de>, { // TODO: what size should we use as the default? let mut batch: Vec = Vec::with_capacity(seq.size_hint().unwrap_or(10)); while let Ok(Some(s)) = seq.next_element::() { batch.push(s); } Ok(JsonRpcRequestEnum::Batch(batch)) } fn visit_map(self, mut map: A) -> Result where A: MapAccess<'de>, { // TODO: i feel like this should be easier let mut jsonrpc = None; let mut id = None; let mut method = None; let mut params = None; while let Some(key) = map.next_key()? { match key { Field::JsonRpc => { // throw away the value // TODO: should we check that it's 2.0? // TODO: how do we skip over this value entirely? jsonrpc = Some(map.next_value()?); } Field::Id => { if id.is_some() { return Err(de::Error::duplicate_field("id")); } id = Some(map.next_value()?); } Field::Method => { if method.is_some() { return Err(de::Error::duplicate_field("method")); } method = Some(map.next_value()?); } Field::Params => { if params.is_some() { return Err(de::Error::duplicate_field("params")); } params = Some(map.next_value()?); } } } // some providers don't follow the spec and dont include the jsonrpc key // i think "2.0" should be a fine default to handle these incompatible clones let jsonrpc = jsonrpc.unwrap_or_else(|| "2.0".to_string()); // TODO: Errors returned by the try operator get shown in an ugly way let id = id.ok_or_else(|| de::Error::missing_field("id"))?; let method = method.ok_or_else(|| de::Error::missing_field("method"))?; let single = JsonRpcRequest { jsonrpc, id, method, params: params.unwrap_or_default(), }; Ok(JsonRpcRequestEnum::Single(single)) } } let batch_visitor = JsonRpcBatchVisitor {}; deserializer.deserialize_any(batch_visitor) } } // TODO: impl Error on this? /// All jsonrpc errors use this structure #[derive(Debug, Deserialize, Serialize, Clone)] pub struct JsonRpcErrorData { /// The error code pub code: i64, /// The error message pub message: Cow<'static, str>, /// Additional data #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, } impl From<&'static str> for JsonRpcErrorData { fn from(value: &'static str) -> Self { Self { code: -32000, message: value.into(), data: None, } } } impl From for JsonRpcErrorData { fn from(value: String) -> Self { Self { code: -32000, message: value.into(), data: None, } } } /// A complete response /// TODO: better Debug response #[derive(Clone, Debug, Deserialize, Serialize)] pub struct JsonRpcForwardedResponse { // TODO: jsonrpc a &str? pub jsonrpc: &'static str, pub id: Box, #[serde(skip_serializing_if = "Option::is_none")] pub result: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, } impl JsonRpcRequest { pub fn num_bytes(&self) -> usize { // TODO: not sure how to do this without wasting a ton of allocations serde_json::to_string(self) .expect("this should always be valid json") .len() } } impl JsonRpcForwardedResponse { pub fn from_anyhow_error( err: anyhow::Error, code: Option, id: Option>, ) -> Self { let message = format!("{:?}", err); Self::from_string(message, code, id) } pub fn from_str(message: &str, code: Option, id: Option>) -> Self { Self::from_string(message.to_string(), code, id) } pub fn from_string(message: String, code: Option, id: Option>) -> Self { // TODO: this is too verbose. plenty of errors are valid, like users giving an invalid address. no need to log that // TODO: can we somehow get the initial request here? if we put that into a tracing span, will things slow down a ton? JsonRpcForwardedResponse { jsonrpc: "2.0", id: id.unwrap_or_default(), result: None, error: Some(JsonRpcErrorData { code: code.unwrap_or(-32099), message: message.into(), // TODO: accept data as an argument data: None, }), } } pub fn from_raw_response(result: Arc, id: Box) -> Self { JsonRpcForwardedResponse { jsonrpc: "2.0", id, // TODO: since we only use the result here, should that be all we return from try_send_request? result: Some(result), error: None, } } pub fn from_value(result: serde_json::Value, id: Box) -> Self { let partial_response = to_raw_value(&result).expect("Value to RawValue should always work"); // TODO: an Arc is a waste here. change JsonRpcForwardedResponse to take an enum? let partial_response = partial_response.into(); JsonRpcForwardedResponse { jsonrpc: "2.0", id, result: Some(partial_response), error: None, } } pub fn from_response_data(data: JsonRpcResponseEnum>, id: Box) -> Self { match data { JsonRpcResponseEnum::NullResult => { let x: Box = Default::default(); Self::from_raw_response(x.into(), id) } JsonRpcResponseEnum::Result { value, .. } => Self::from_raw_response(value, id), JsonRpcResponseEnum::RpcError { error_data: value, .. } => JsonRpcForwardedResponse { jsonrpc: "2.0", id, result: None, error: Some(value), }, } } } /// JSONRPC Responses can include one or many response objects. #[derive(Clone, Debug, From, Serialize)] #[serde(untagged)] pub enum JsonRpcForwardedResponseEnum { Single(JsonRpcForwardedResponse), Batch(Vec), } #[cfg(test)] mod tests { use super::*; #[test] fn this_deserialize_single() { let input = r#"{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}"#; // test deserializing it directly to a single request object let output: JsonRpcRequest = serde_json::from_str(input).unwrap(); assert_eq!(output.id.to_string(), "1"); assert_eq!(output.method, "eth_blockNumber"); assert_eq!(output.params.to_string(), "[]"); // test deserializing it into an enum let output: JsonRpcRequestEnum = serde_json::from_str(input).unwrap(); assert!(matches!(output, JsonRpcRequestEnum::Single(_))); } #[test] fn this_deserialize_batch() { let input = r#"[{"jsonrpc":"2.0","method":"eth_getCode","params":["0x5ba1e12693dc8f9c48aad8770482f4739beed696","0xe0e6a4"],"id":27},{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0x5ba1e12693dc8f9c48aad8770482f4739beed696","0xe0e6a4"],"id":28},{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x5ba1e12693dc8f9c48aad8770482f4739beed696","0xe0e6a4"],"id":29}]"#; // test deserializing it directly to a batch of request objects let output: Vec = serde_json::from_str(input).unwrap(); assert_eq!(output.len(), 3); assert_eq!(output[0].id.to_string(), "27"); assert_eq!(output[0].method, "eth_getCode"); assert_eq!( output[0].params.to_string(), r#"["0x5ba1e12693dc8f9c48aad8770482f4739beed696","0xe0e6a4"]"# ); assert_eq!(output[1].id.to_string(), "28"); assert_eq!(output[2].id.to_string(), "29"); // test deserializing it into an enum let output: JsonRpcRequestEnum = serde_json::from_str(input).unwrap(); assert!(matches!(output, JsonRpcRequestEnum::Batch(_))); } }