From 439ac49495e1bafc4cca7a97ad668cfbe8ed9640 Mon Sep 17 00:00:00 2001 From: Bryan Stitt Date: Mon, 24 Jul 2023 16:25:48 -0700 Subject: [PATCH] jsonrpc formatted errors for JsonRejection --- web3_proxy/src/app/mod.rs | 1 + web3_proxy/src/errors.rs | 208 +++++++++++++--------- web3_proxy/src/frontend/authorization.rs | 2 +- web3_proxy/src/frontend/rpc_proxy_http.rs | 28 ++- 4 files changed, 146 insertions(+), 93 deletions(-) diff --git a/web3_proxy/src/app/mod.rs b/web3_proxy/src/app/mod.rs index 3258cd42..b58e07e8 100644 --- a/web3_proxy/src/app/mod.rs +++ b/web3_proxy/src/app/mod.rs @@ -1500,6 +1500,7 @@ impl Web3ProxyApp { // emit transaction count stats // TODO: use this cache to avoid sending duplicate transactions? + // TODO: different salt for ips and transactions? if let Some(ref salt) = self.config.public_recent_ips_salt { if let JsonRpcResponseEnum::Result { value, .. } = &response { let now = Utc::now().timestamp(); diff --git a/web3_proxy/src/errors.rs b/web3_proxy/src/errors.rs index 2ef5853c..e08a8c1f 100644 --- a/web3_proxy/src/errors.rs +++ b/web3_proxy/src/errors.rs @@ -4,6 +4,7 @@ use crate::frontend::authorization::Authorization; use crate::jsonrpc::{JsonRpcErrorData, JsonRpcForwardedResponse}; use crate::response_cache::JsonRpcResponseEnum; use crate::rpcs::provider::EthersHttpProvider; +use axum::extract::rejection::JsonRejection; use axum::extract::ws::Message; use axum::{ headers, @@ -23,6 +24,7 @@ use redis_rate_limiter::RedisPoolError; use reqwest::header::ToStrError; use rust_decimal::Error as DecimalError; use serde::Serialize; +use serde_json::json; use serde_json::value::RawValue; use siwe::VerificationError; use std::sync::Arc; @@ -92,6 +94,7 @@ pub enum Web3ProxyError { #[from(ignore)] IpNotAllowed(IpAddr), JoinError(JoinError), + JsonRejection(JsonRejection), #[display(fmt = "{:?}", _0)] #[error(ignore)] JsonRpcErrorData(JsonRpcErrorData), @@ -174,6 +177,8 @@ pub enum Web3ProxyError { } impl Web3ProxyError { + /// turn the error into an axum response. + /// pub fn as_response_parts(&self) -> (StatusCode, JsonRpcResponseEnum) { // TODO: include a unique request id in the data let (code, err): (StatusCode, JsonRpcErrorData) = match self { @@ -182,9 +187,9 @@ impl Web3ProxyError { ( StatusCode::INTERNAL_SERVER_ERROR, JsonRpcErrorData { - message: err.to_string().into(), + message: "abi error".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -208,7 +213,7 @@ impl Web3ProxyError { // TODO: is it safe to expose all of our anyhow strings? message: "INTERNAL SERVER ERROR".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -221,9 +226,9 @@ impl Web3ProxyError { ( StatusCode::BAD_REQUEST, JsonRpcErrorData { - message: format!("bad request: {}", err).into(), + message: "bad request".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -233,9 +238,9 @@ impl Web3ProxyError { ( StatusCode::INTERNAL_SERVER_ERROR, JsonRpcErrorData { - message: format!("bad response: {}", err).into(), + message: "bad response".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -255,9 +260,9 @@ impl Web3ProxyError { ( StatusCode::INTERNAL_SERVER_ERROR, JsonRpcErrorData { - message: format!("contract error: {}", err).into(), + message: "contract error".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -268,7 +273,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: "database error!".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -279,7 +284,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: "database (arc) error!".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -288,9 +293,9 @@ impl Web3ProxyError { ( StatusCode::BAD_REQUEST, JsonRpcErrorData { - message: format!("decimal error: {}", err).into(), + message: "decimal error".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -305,7 +310,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: "ethers http client error".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -321,7 +326,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: "ethers provider error".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -337,7 +342,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: "ethers ws client error".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -359,9 +364,9 @@ impl Web3ProxyError { ( StatusCode::INTERNAL_SERVER_ERROR, JsonRpcErrorData { - message: err.to_string().into(), + message: "hdr record error".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -370,9 +375,9 @@ impl Web3ProxyError { ( StatusCode::BAD_REQUEST, JsonRpcErrorData { - message: err.to_string().into(), + message: "headers error".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -381,9 +386,9 @@ impl Web3ProxyError { ( StatusCode::BAD_REQUEST, JsonRpcErrorData { - message: err.to_string().into(), + message: "header to string error".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -394,7 +399,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: err.to_string().into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -406,7 +411,7 @@ impl Web3ProxyError { // TODO: is it safe to expose these error strings? message: err.to_string().into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -418,7 +423,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: "influxdb2 error!".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -427,13 +432,12 @@ impl Web3ProxyError { ( StatusCode::BAD_REQUEST, JsonRpcErrorData { - message: format!( - "Invalid blocks bounds requested. min ({}) > max ({})", - min, max - ) - .into(), + message: "Invalid blocks bounds requested".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(json!({ + "min": min, + "max": max, + })), }, ) } @@ -453,9 +457,9 @@ impl Web3ProxyError { ( StatusCode::FORBIDDEN, JsonRpcErrorData { - message: format!("IP ({}) is not allowed!", ip).into(), + message: "IP is not allowed!".into(), code: StatusCode::FORBIDDEN.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(ip.to_string())), }, ) } @@ -464,9 +468,9 @@ impl Web3ProxyError { ( StatusCode::BAD_REQUEST, JsonRpcErrorData { - message: err.to_string().into(), + message: "invalid header value".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -497,10 +501,10 @@ impl Web3ProxyError { ( StatusCode::INTERNAL_SERVER_ERROR, JsonRpcErrorData { - // TODO: is it safe to expose our io error strings? - message: format!("std io error: {}", err).into(), + message: "std io".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + // TODO: is it safe to expose our io error strings? + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -574,7 +578,32 @@ impl Web3ProxyError { // TODO: different messages of cancelled or not? message: "Unable to complete request".into(), code: code.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), + }, + ) + } + Self::JsonRejection(err) => { + trace!(?err, "JsonRejection"); + + let (message, code): (&str, _) = match err { + JsonRejection::JsonDataError(_) => ("Invalid Request", -32600), + JsonRejection::JsonSyntaxError(_) => ("Parse error", -32700), + JsonRejection::MissingJsonContentType(_) => ("Invalid Request", -32600), + JsonRejection::BytesRejection(_) => ("Invalid Request", -32600), + x => { + warn!(?x, "what? isn't that all of them?"); + // TODO: what code should this be? + ("Parse error", -32700) + } + }; + + // TODO: i feel like this should be a 401, but the spec seems to say its a 200 + ( + StatusCode::OK, + JsonRpcErrorData { + message: message.into(), + code: code.into(), + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -587,9 +616,9 @@ impl Web3ProxyError { ( StatusCode::INTERNAL_SERVER_ERROR, JsonRpcErrorData { - message: format!("msgpack encode error: {}", err).into(), + message: "msgpack encode error".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -694,13 +723,12 @@ impl Web3ProxyError { ( StatusCode::BAD_GATEWAY, JsonRpcErrorData { - message: format!( - "not enough soft limit available {}/{}", - available, needed - ) - .into(), + message: "not enough soft limit available".into(), code: StatusCode::BAD_GATEWAY.as_u16().into(), - data: None, + data: Some(json!({ + "available": available, + "needed": needed, + })), }, ) } @@ -747,20 +775,25 @@ impl Web3ProxyError { ( StatusCode::FORBIDDEN, JsonRpcErrorData { - message: format!("Origin ({}) is not allowed!", origin).into(), + message: "Origin is not allowed!".into(), code: StatusCode::FORBIDDEN.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(origin.to_string())), }, ) } Self::ParseBytesError(err) => { trace!(?err, "ParseBytesError"); + + let data = err + .as_ref() + .map(|x| serde_json::Value::String(x.to_string())); + ( StatusCode::BAD_REQUEST, JsonRpcErrorData { message: "parse bytes error!".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data, }, ) } @@ -771,7 +804,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: "parse message error!".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -825,6 +858,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: msg.into(), code: StatusCode::TOO_MANY_REQUESTS.as_u16().into(), + // TODO: use a generic message with details in the data data: None, }, ) @@ -836,7 +870,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: "redis error!".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -846,9 +880,9 @@ impl Web3ProxyError { StatusCode::INTERNAL_SERVER_ERROR, JsonRpcErrorData { // TODO: is it safe to expose our io error strings? - message: err.to_string().into(), + message: "redis pool error".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -868,9 +902,9 @@ impl Web3ProxyError { ( StatusCode::FORBIDDEN, JsonRpcErrorData { - message: format!("Referer ({:?}) is not allowed", referer).into(), + message: "Referer is not allowed".into(), code: StatusCode::FORBIDDEN.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(format!("{:?}", referer))), }, ) } @@ -882,7 +916,7 @@ impl Web3ProxyError { // TODO: is it safe to expose all of our anyhow strings? message: "semaphore acquire error".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -891,9 +925,9 @@ impl Web3ProxyError { ( StatusCode::BAD_REQUEST, JsonRpcErrorData { - message: format!("de/serialization error! {}", err).into(), + message: "de/serialization error!".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -902,9 +936,9 @@ impl Web3ProxyError { ( StatusCode::UNAUTHORIZED, JsonRpcErrorData { - message: format!("siwe verification error: {}", err).into(), + message: "siwe verification error".into(), code: StatusCode::UNAUTHORIZED.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -931,31 +965,35 @@ impl Web3ProxyError { ( StatusCode::BAD_REQUEST, JsonRpcErrorData { - message: format!("stripe webhook error: {}", err).into(), + message: "stripe webhook error".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), // TODO: include the stripe signature? anything else? - data: None, + data: Some(serde_json::Value::String(err.to_string())), + }, + ) + } + Self::Timeout(x) => { + let data = x.as_ref().map(|x| json!(x.as_secs_f32())); + + ( + StatusCode::REQUEST_TIMEOUT, + JsonRpcErrorData { + message: "request timed out".into(), + code: StatusCode::REQUEST_TIMEOUT.as_u16().into(), + // TODO: prettier message + // TODO: include the actual id! + data, }, ) } - Self::Timeout(x) => ( - StatusCode::REQUEST_TIMEOUT, - JsonRpcErrorData { - // TODO: prettier message - message: format!("request timed out: {:?}", x).into(), - code: StatusCode::REQUEST_TIMEOUT.as_u16().into(), - // TODO: include the actual id! - data: None, - }, - ), Self::UlidDecode(err) => { trace!(?err, "UlidDecodeError"); ( StatusCode::BAD_REQUEST, JsonRpcErrorData { - message: format!("ulid decode error: {}", err).into(), + message: "ulid decode error".into(), code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } @@ -964,9 +1002,11 @@ impl Web3ProxyError { ( StatusCode::OK, JsonRpcErrorData { - message: format!("block {} not found", hash).into(), + message: "block hash not found".into(), code: -32000, - data: None, + data: Some(json!({ + "hash": hash, + })), }, ) } @@ -975,10 +1015,12 @@ impl Web3ProxyError { ( StatusCode::OK, JsonRpcErrorData { - message: format!("block #{} not found. best known is #{}", unknown, known) - .into(), + message: "block number not found".into(), code: -32000, - data: None, + data: Some(json!({ + "unknown": unknown, + "known": known, + })), }, ) } @@ -1017,9 +1059,9 @@ impl Web3ProxyError { ( StatusCode::FORBIDDEN, JsonRpcErrorData { - message: format!("User agent ({}) is not allowed!", ua).into(), + message: "User agent is not allowed!".into(), code: StatusCode::FORBIDDEN.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(ua.to_string())), }, ) } @@ -1042,7 +1084,7 @@ impl Web3ProxyError { JsonRpcErrorData { message: "watch recv error!".into(), code: StatusCode::INTERNAL_SERVER_ERROR.as_u16().into(), - data: None, + data: Some(serde_json::Value::String(err.to_string())), }, ) } diff --git a/web3_proxy/src/frontend/authorization.rs b/web3_proxy/src/frontend/authorization.rs index e36b137c..da93ff50 100644 --- a/web3_proxy/src/frontend/authorization.rs +++ b/web3_proxy/src/frontend/authorization.rs @@ -830,7 +830,7 @@ pub async fn ip_is_authorized( x => unimplemented!("rate_limit_by_ip shouldn't ever see these: {:?}", x), }; - // in the background, add the ip to a recent_users map + // in the background, add the hashed ip to a recent_users map if app.config.public_recent_ips_salt.is_some() { let app = app.clone(); let ip = *ip; diff --git a/web3_proxy/src/frontend/rpc_proxy_http.rs b/web3_proxy/src/frontend/rpc_proxy_http.rs index 86b697e0..7bd43894 100644 --- a/web3_proxy/src/frontend/rpc_proxy_http.rs +++ b/web3_proxy/src/frontend/rpc_proxy_http.rs @@ -4,6 +4,7 @@ use super::authorization::{ip_is_authorized, key_is_authorized}; use super::rpc_proxy_ws::ProxyMode; use crate::errors::Web3ProxyError; use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum}; +use axum::extract::rejection::JsonRejection; use axum::extract::Path; use axum::headers::{Origin, Referer, UserAgent}; use axum::response::Response; @@ -25,7 +26,7 @@ pub async fn proxy_web3_rpc( Extension(app): Extension>, InsecureClientIp(ip): InsecureClientIp, origin: Option>, - Json(payload): Json, + payload: Result, JsonRejection>, ) -> Result { _proxy_web3_rpc(app, &ip, origin.as_deref(), payload, ProxyMode::Best).await } @@ -35,7 +36,7 @@ pub async fn fastest_proxy_web3_rpc( Extension(app): Extension>, InsecureClientIp(ip): InsecureClientIp, origin: Option>, - Json(payload): Json, + payload: Result, JsonRejection>, ) -> Result { // TODO: read the fastest number from params // TODO: check that the app allows this without authentication @@ -47,7 +48,7 @@ pub async fn versus_proxy_web3_rpc( Extension(app): Extension>, InsecureClientIp(ip): InsecureClientIp, origin: Option>, - Json(payload): Json, + payload: Result, JsonRejection>, ) -> Result { _proxy_web3_rpc(app, &ip, origin.as_deref(), payload, ProxyMode::Versus).await } @@ -56,9 +57,14 @@ async fn _proxy_web3_rpc( app: Arc, ip: &IpAddr, origin: Option<&Origin>, - payload: JsonRpcRequestEnum, + payload: Result, JsonRejection>, proxy_mode: ProxyMode, ) -> Result { + // TODO: create a stat if they error. (but we haven't parsed rpc_key yet, so it needs some thought) + let payload = payload + .map_err(|e| Web3ProxyError::from(e).into_response_with_id(None))? + .0; + let first_id = payload.first_id(); let (authorization, _semaphore) = ip_is_authorized(&app, ip, origin, proxy_mode) @@ -126,7 +132,7 @@ pub async fn proxy_web3_rpc_with_key( referer: Option>, user_agent: Option>, Path(rpc_key): Path, - Json(payload): Json, + payload: Result, JsonRejection>, ) -> Result { _proxy_web3_rpc_with_key( app, @@ -152,7 +158,7 @@ pub async fn debug_proxy_web3_rpc_with_key( user_agent: Option>, request_headers: HeaderMap, Path(rpc_key): Path, - Json(payload): Json, + payload: Result, JsonRejection>, ) -> Result { let mut response = match _proxy_web3_rpc_with_key( app, @@ -195,7 +201,7 @@ pub async fn fastest_proxy_web3_rpc_with_key( referer: Option>, user_agent: Option>, Path(rpc_key): Path, - Json(payload): Json, + payload: Result, JsonRejection>, ) -> Result { _proxy_web3_rpc_with_key( app, @@ -218,7 +224,7 @@ pub async fn versus_proxy_web3_rpc_with_key( referer: Option>, user_agent: Option>, Path(rpc_key): Path, - Json(payload): Json, + payload: Result, JsonRejection>, ) -> Result { _proxy_web3_rpc_with_key( app, @@ -241,10 +247,14 @@ async fn _proxy_web3_rpc_with_key( referer: Option<&Referer>, user_agent: Option<&UserAgent>, rpc_key: String, - payload: JsonRpcRequestEnum, + payload: Result, JsonRejection>, proxy_mode: ProxyMode, ) -> Result { // TODO: DRY w/ proxy_web3_rpc + // TODO: create a stat if they error. (but we haven't parsed rpc_key yet, so it needs some thought) + let payload = payload + .map_err(|e| Web3ProxyError::from(e).into_response_with_id(None))? + .0; let first_id = payload.first_id();