diff --git a/Cargo.lock b/Cargo.lock index dd2f162f..c7e0aa59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,6 +401,18 @@ dependencies = [ "mime", ] +[[package]] +name = "axum-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6293dae2ec708e679da6736e857cf8532886ef258e92930f38279c12641628b8" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "backtrace" version = "0.3.65" @@ -5482,6 +5494,7 @@ dependencies = [ "argh", "axum", "axum-client-ip", + "axum-macros", "counter", "dashmap", "derive_more", diff --git a/web3_proxy/Cargo.toml b/web3_proxy/Cargo.toml index ad812de3..b7c16995 100644 --- a/web3_proxy/Cargo.toml +++ b/web3_proxy/Cargo.toml @@ -21,6 +21,7 @@ arc-swap = "1.5.1" argh = "0.1.8" axum = { version = "0.5.15", features = ["headers", "serde_json", "tokio-tungstenite", "ws"] } axum-client-ip = "0.2.0" +axum-macros = "*" counter = "0.5.6" dashmap = "5.3.4" derive_more = "0.99.17" diff --git a/web3_proxy/src/frontend/errors.rs b/web3_proxy/src/frontend/errors.rs index 0d77091f..23d25b5c 100644 --- a/web3_proxy/src/frontend/errors.rs +++ b/web3_proxy/src/frontend/errors.rs @@ -1,11 +1,21 @@ +use crate::jsonrpc::JsonRpcForwardedResponse; use axum::{ http::StatusCode, response::{IntoResponse, Response}, Json, }; use serde_json::value::RawValue; +use std::error::Error; -use crate::jsonrpc::JsonRpcForwardedResponse; +pub struct ErrorResponse { + pub inner: Box, +} + +impl IntoResponse for ErrorResponse { + fn into_response(self) -> Response { + todo!("into_response based on the error type") + } +} pub async fn handler_404() -> Response { let err = anyhow::anyhow!("nothing to see here"); @@ -13,6 +23,7 @@ pub async fn handler_404() -> Response { anyhow_error_into_response(Some(StatusCode::NOT_FOUND), None, err) } +/// TODO: generic error? /// handle errors by converting them into something that implements `IntoResponse` /// TODO: use this. i can't get to work /// TODO: i think we want a custom result type instead. put the anyhow result inside. then `impl IntoResponse for CustomResult` diff --git a/web3_proxy/src/frontend/http.rs b/web3_proxy/src/frontend/http.rs index 815941a3..9f443c90 100644 --- a/web3_proxy/src/frontend/http.rs +++ b/web3_proxy/src/frontend/http.rs @@ -1,9 +1,8 @@ +use crate::app::Web3ProxyApp; use axum::{http::StatusCode, response::IntoResponse, Extension, Json}; use serde_json::json; use std::sync::Arc; -use crate::app::Web3ProxyApp; - /// Health check page for load balancers to use pub async fn health(Extension(app): Extension>) -> impl IntoResponse { if app.balanced_rpcs.synced() { diff --git a/web3_proxy/src/frontend/http_proxy.rs b/web3_proxy/src/frontend/http_proxy.rs index 498f0155..5b769481 100644 --- a/web3_proxy/src/frontend/http_proxy.rs +++ b/web3_proxy/src/frontend/http_proxy.rs @@ -1,3 +1,7 @@ +use super::errors::anyhow_error_into_response; +use super::rate_limit::RateLimitResult; +use crate::stats::{Protocol, ProxyRequestLabels}; +use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum}; use axum::extract::Path; use axum::response::Response; use axum::{http::StatusCode, response::IntoResponse, Extension, Json}; @@ -6,11 +10,6 @@ use std::sync::Arc; use tracing::{error_span, Instrument}; use uuid::Uuid; -use super::errors::anyhow_error_into_response; -use super::rate_limit::RateLimitResult; -use crate::stats::{Protocol, ProxyRequestLabels}; -use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum}; - pub async fn public_proxy_web3_rpc( Json(payload): Json, Extension(app): Extension>, diff --git a/web3_proxy/src/frontend/mod.rs b/web3_proxy/src/frontend/mod.rs index ab9e3f5a..7b0376cc 100644 --- a/web3_proxy/src/frontend/mod.rs +++ b/web3_proxy/src/frontend/mod.rs @@ -6,10 +6,13 @@ mod rate_limit; mod users; mod ws_proxy; -use ::http::Request; +use crate::app::Web3ProxyApp; +use ::http::{Request, StatusCode}; use axum::{ body::Body, + error_handling::HandleError, handler::Handler, + response::Response, routing::{get, post}, Extension, Router, }; @@ -19,7 +22,15 @@ use tower_http::trace::TraceLayer; use tower_request_id::{RequestId, RequestIdLayer}; use tracing::{error_span, info}; -use crate::app::Web3ProxyApp; +// handle errors by converting them into something that implements +// `IntoResponse` +async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) { + // TODO: i dont like this, but lets see if it works. need to moved to the errors module and replace the version that is there + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Something went wrong: {}", err), + ) +} /// http and websocket frontend for customers pub async fn serve(port: u16, proxy_app: Arc) -> anyhow::Result<()> { @@ -42,6 +53,12 @@ pub async fn serve(port: u16, proxy_app: Arc) -> anyhow::Result<() ) }); + // create user endpoint needs some work sothat + let some_fallible_service = tower::service_fn(|_req| async { + // thing_that_might_fail().await?; + Ok::<_, anyhow::Error>(Response::new(Body::empty())) + }); + // build our application with a route // order most to least common let app = Router::new() @@ -51,7 +68,18 @@ pub async fn serve(port: u16, proxy_app: Arc) -> anyhow::Result<() .route("/u/:user_key", get(ws_proxy::user_websocket_handler)) .route("/health", get(http::health)) .route("/status", get(http::status)) + // .route( + // "/login", + // HandleError::new( + // move |app| async { users::get_login(app).await }, + // handle_anyhow_error, + // ), + // ) .route("/users", post(users::create_user)) + .route( + "/foo", + HandleError::new(some_fallible_service, handle_anyhow_error), + ) .layer(Extension(proxy_app)) // create a unique id for each request and add it to our tracing logs .layer(request_tracing_layer) diff --git a/web3_proxy/src/frontend/rate_limit.rs b/web3_proxy/src/frontend/rate_limit.rs index b2509377..c6511376 100644 --- a/web3_proxy/src/frontend/rate_limit.rs +++ b/web3_proxy/src/frontend/rate_limit.rs @@ -1,3 +1,5 @@ +use super::errors::anyhow_error_into_response; +use crate::app::{UserCacheValue, Web3ProxyApp}; use axum::response::Response; use entities::user_keys; use redis_rate_limit::ThrottleResult; @@ -10,10 +12,6 @@ use tokio::time::Instant; use tracing::{debug, warn}; use uuid::Uuid; -use crate::app::{UserCacheValue, Web3ProxyApp}; - -use super::errors::anyhow_error_into_response; - pub enum RateLimitResult { AllowedIp(IpAddr), AllowedUser(u64), diff --git a/web3_proxy/src/frontend/users.rs b/web3_proxy/src/frontend/users.rs index a2d19153..d8bea264 100644 --- a/web3_proxy/src/frontend/users.rs +++ b/web3_proxy/src/frontend/users.rs @@ -7,11 +7,14 @@ // I wonder how we handle payment // probably have to do manual withdrawals +use super::{errors::anyhow_error_into_response, rate_limit::RateLimitResult}; +use crate::app::Web3ProxyApp; use axum::{ response::{IntoResponse, Response}, Extension, Json, }; use axum_client_ip::ClientIp; +use axum_macros::debug_handler; use entities::user; use ethers::{prelude::Address, types::Bytes}; use reqwest::StatusCode; @@ -19,9 +22,15 @@ use sea_orm::ActiveModelTrait; use serde::Deserialize; use std::sync::Arc; -use crate::app::Web3ProxyApp; +#[debug_handler] +pub async fn get_login(Extension(app): Extension>) -> Result { + // let redis: RedisPool = app...; + let redis_pool = app.redis_pool.as_ref().unwrap(); -use super::{errors::anyhow_error_into_response, rate_limit::RateLimitResult}; + let redis_conn = redis_pool.get().await.unwrap(); + + todo!("how should this work? probably keep stuff in redis ") +} pub async fn create_user( // this argument tells axum to parse the request body @@ -30,13 +39,14 @@ pub async fn create_user( Extension(app): Extension>, ClientIp(ip): ClientIp, ) -> Response { + // TODO: return a Result instead let _ip = match app.rate_limit_by_ip(ip).await { Ok(x) => match x.try_into_response().await { Ok(RateLimitResult::AllowedIp(x)) => x, Err(err_response) => return err_response, _ => unimplemented!(), }, - Err(err) => return anyhow_error_into_response(None, None, err).into_response(), + Err(err) => return anyhow_error_into_response(None, None, err), }; // TODO: check invite_code against the app's config or database @@ -65,7 +75,7 @@ pub async fn create_user( // TODO: proper error message let user = user.insert(db).await.unwrap(); - // + // TODO: create // TODO: do not expose user ids (StatusCode::CREATED, Json(user)).into_response() diff --git a/web3_proxy/src/jsonrpc.rs b/web3_proxy/src/jsonrpc.rs index 2533c003..83ca56b9 100644 --- a/web3_proxy/src/jsonrpc.rs +++ b/web3_proxy/src/jsonrpc.rs @@ -193,7 +193,7 @@ impl JsonRpcForwardedResponse { id, result: None, error: Some(JsonRpcErrorData { - // TODO: set this jsonrpc error code to match the http status code? or maybe the other way around? + // TODO: set this jsonrpc error code to match the http status code? or maybe the other way around? maybe take it as an arg code: -32099, // TODO: some errors should be included here. others should not. i think anyhow might not be the right choice // message: "internal server error".to_string(),