2022-10-18 00:47:58 +03:00
|
|
|
//! `frontend` contains HTTP and websocket endpoints for use by users and admins.
|
2023-02-06 20:55:27 +03:00
|
|
|
//!
|
|
|
|
//! Important reading about axum extractors: https://docs.rs/axum/latest/axum/extract/index.html#the-order-of-extractors
|
2022-10-18 00:47:58 +03:00
|
|
|
|
2023-01-17 22:12:40 +03:00
|
|
|
pub mod admin;
|
2022-09-22 23:27:14 +03:00
|
|
|
pub mod authorization;
|
2022-11-04 07:32:09 +03:00
|
|
|
pub mod errors;
|
2022-10-18 00:47:58 +03:00
|
|
|
// TODO: these are only public so docs are generated. What's a better way to do this?
|
|
|
|
pub mod rpc_proxy_http;
|
|
|
|
pub mod rpc_proxy_ws;
|
|
|
|
pub mod status;
|
|
|
|
pub mod users;
|
2022-07-07 06:22:09 +03:00
|
|
|
|
2022-08-16 22:29:00 +03:00
|
|
|
use crate::app::Web3ProxyApp;
|
2022-06-05 22:58:47 +03:00
|
|
|
use axum::{
|
2022-10-31 23:51:06 +03:00
|
|
|
routing::{get, post, put},
|
2022-06-05 22:58:47 +03:00
|
|
|
Extension, Router,
|
|
|
|
};
|
2022-09-25 19:35:01 +03:00
|
|
|
use http::header::AUTHORIZATION;
|
2022-11-12 11:24:32 +03:00
|
|
|
use log::info;
|
2022-11-16 23:17:33 +03:00
|
|
|
use moka::future::Cache;
|
2022-08-07 23:44:56 +03:00
|
|
|
use std::net::SocketAddr;
|
2022-06-05 22:58:47 +03:00
|
|
|
use std::sync::Arc;
|
2022-11-16 23:17:33 +03:00
|
|
|
use std::{iter::once, time::Duration};
|
2022-09-25 07:26:13 +03:00
|
|
|
use tower_http::cors::CorsLayer;
|
2022-09-25 19:35:01 +03:00
|
|
|
use tower_http::sensitive_headers::SetSensitiveRequestHeadersLayer;
|
2022-06-15 04:02:26 +03:00
|
|
|
|
2022-11-16 23:17:33 +03:00
|
|
|
#[derive(Clone, Hash, PartialEq, Eq)]
|
|
|
|
pub enum FrontendResponseCaches {
|
|
|
|
Status,
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: what should this cache's value be?
|
|
|
|
pub type FrontendResponseCache =
|
|
|
|
Cache<FrontendResponseCaches, Arc<serde_json::Value>, hashbrown::hash_map::DefaultHashBuilder>;
|
2023-02-03 00:58:04 +03:00
|
|
|
pub type FrontendHealthCache = Cache<(), bool, hashbrown::hash_map::DefaultHashBuilder>;
|
2022-08-16 03:33:26 +03:00
|
|
|
|
2022-11-16 23:17:33 +03:00
|
|
|
/// Start the frontend server.
|
2022-11-12 11:24:32 +03:00
|
|
|
pub async fn serve(port: u16, proxy_app: Arc<Web3ProxyApp>) -> anyhow::Result<()> {
|
2022-11-16 23:17:33 +03:00
|
|
|
// setup caches for whatever the frontend needs
|
|
|
|
// TODO: a moka cache is probably way overkill for this.
|
|
|
|
// no need for max items. only expire because of time to live
|
|
|
|
let response_cache: FrontendResponseCache = Cache::builder()
|
2023-02-03 00:58:04 +03:00
|
|
|
.time_to_live(Duration::from_secs(2))
|
|
|
|
.build_with_hasher(hashbrown::hash_map::DefaultHashBuilder::default());
|
|
|
|
|
|
|
|
let health_cache: FrontendHealthCache = Cache::builder()
|
|
|
|
.time_to_live(Duration::from_millis(100))
|
2022-11-16 23:17:33 +03:00
|
|
|
.build_with_hasher(hashbrown::hash_map::DefaultHashBuilder::default());
|
|
|
|
|
2023-01-18 00:34:33 +03:00
|
|
|
// TODO: read config for if fastest/versus should be available publicly. default off
|
|
|
|
|
2022-08-21 11:18:57 +03:00
|
|
|
// build our axum Router
|
2022-06-05 22:58:47 +03:00
|
|
|
let app = Router::new()
|
2023-01-17 09:54:40 +03:00
|
|
|
// TODO: i think these routes could be done a lot better
|
|
|
|
//
|
|
|
|
// HTTP RPC (POST)
|
|
|
|
//
|
|
|
|
// public
|
2022-10-22 00:12:05 +03:00
|
|
|
.route("/", post(rpc_proxy_http::proxy_web3_rpc))
|
2023-01-17 09:54:40 +03:00
|
|
|
// authenticated with and without trailing slash
|
|
|
|
.route(
|
|
|
|
"/rpc/:rpc_key/",
|
|
|
|
post(rpc_proxy_http::proxy_web3_rpc_with_key),
|
|
|
|
)
|
2023-01-07 02:58:52 +03:00
|
|
|
.route(
|
2022-10-27 03:12:42 +03:00
|
|
|
"/rpc/:rpc_key",
|
2022-09-24 07:31:06 +03:00
|
|
|
post(rpc_proxy_http::proxy_web3_rpc_with_key),
|
|
|
|
)
|
2023-03-03 04:39:50 +03:00
|
|
|
// authenticated debug route with and without trailing slash
|
|
|
|
.route(
|
|
|
|
"/debug/:rpc_key/",
|
|
|
|
post(rpc_proxy_http::debug_proxy_web3_rpc_with_key),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/debug/:rpc_key",
|
|
|
|
post(rpc_proxy_http::debug_proxy_web3_rpc_with_key),
|
|
|
|
)
|
2023-01-17 09:54:40 +03:00
|
|
|
// public fastest with and without trailing slash
|
|
|
|
.route("/fastest/", post(rpc_proxy_http::fastest_proxy_web3_rpc))
|
|
|
|
.route("/fastest", post(rpc_proxy_http::fastest_proxy_web3_rpc))
|
|
|
|
// authenticated fastest with and without trailing slash
|
|
|
|
.route(
|
|
|
|
"/fastest/:rpc_key/",
|
|
|
|
post(rpc_proxy_http::fastest_proxy_web3_rpc_with_key),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/fastest/:rpc_key",
|
|
|
|
post(rpc_proxy_http::fastest_proxy_web3_rpc_with_key),
|
|
|
|
)
|
|
|
|
// public versus
|
|
|
|
.route("/versus/", post(rpc_proxy_http::versus_proxy_web3_rpc))
|
|
|
|
.route("/versus", post(rpc_proxy_http::versus_proxy_web3_rpc))
|
|
|
|
// authenticated versus with and without trailing slash
|
|
|
|
.route(
|
|
|
|
"/versus/:rpc_key/",
|
|
|
|
post(rpc_proxy_http::versus_proxy_web3_rpc_with_key),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/versus/:rpc_key",
|
|
|
|
post(rpc_proxy_http::versus_proxy_web3_rpc_with_key),
|
|
|
|
)
|
|
|
|
//
|
|
|
|
// Websocket RPC (GET)
|
|
|
|
// If not an RPC, this will redirect to configurable urls
|
|
|
|
//
|
|
|
|
// public
|
|
|
|
.route("/", get(rpc_proxy_ws::websocket_handler))
|
|
|
|
// authenticated with and without trailing slash
|
2023-01-07 02:58:52 +03:00
|
|
|
.route(
|
|
|
|
"/rpc/:rpc_key/",
|
2023-01-17 09:54:40 +03:00
|
|
|
get(rpc_proxy_ws::websocket_handler_with_key),
|
2023-01-07 02:58:52 +03:00
|
|
|
)
|
|
|
|
.route(
|
2022-10-27 03:12:42 +03:00
|
|
|
"/rpc/:rpc_key",
|
2022-09-24 08:53:45 +03:00
|
|
|
get(rpc_proxy_ws::websocket_handler_with_key),
|
|
|
|
)
|
2023-03-03 04:39:50 +03:00
|
|
|
// debug with and without trailing slash
|
|
|
|
.route(
|
|
|
|
"/debug/:rpc_key/",
|
|
|
|
get(rpc_proxy_ws::websocket_handler_with_key),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/debug/:rpc_key",
|
|
|
|
get(rpc_proxy_ws::websocket_handler_with_key),
|
|
|
|
) // public fastest with and without trailing slash
|
2023-01-17 09:54:40 +03:00
|
|
|
.route("/fastest/", get(rpc_proxy_ws::fastest_websocket_handler))
|
|
|
|
.route("/fastest", get(rpc_proxy_ws::fastest_websocket_handler))
|
|
|
|
// authenticated fastest with and without trailing slash
|
2023-01-07 02:58:52 +03:00
|
|
|
.route(
|
2023-01-17 09:54:40 +03:00
|
|
|
"/fastest/:rpc_key/",
|
|
|
|
get(rpc_proxy_ws::fastest_websocket_handler_with_key),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/fastest/:rpc_key",
|
|
|
|
get(rpc_proxy_ws::fastest_websocket_handler_with_key),
|
|
|
|
)
|
|
|
|
// public versus
|
|
|
|
.route(
|
|
|
|
"/versus/",
|
|
|
|
get(rpc_proxy_ws::versus_websocket_handler_with_key),
|
2023-01-07 02:58:52 +03:00
|
|
|
)
|
2023-01-17 09:54:40 +03:00
|
|
|
.route(
|
|
|
|
"/versus",
|
|
|
|
get(rpc_proxy_ws::versus_websocket_handler_with_key),
|
|
|
|
)
|
|
|
|
// authenticated versus with and without trailing slash
|
|
|
|
.route(
|
|
|
|
"/versus/:rpc_key/",
|
|
|
|
get(rpc_proxy_ws::versus_websocket_handler_with_key),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/versus/:rpc_key",
|
|
|
|
get(rpc_proxy_ws::versus_websocket_handler_with_key),
|
|
|
|
)
|
|
|
|
//
|
|
|
|
// System things
|
|
|
|
//
|
2022-09-25 19:37:45 +03:00
|
|
|
.route("/health", get(status::health))
|
2023-01-17 09:54:40 +03:00
|
|
|
.route("/status", get(status::status))
|
|
|
|
//
|
|
|
|
// User stuff
|
|
|
|
//
|
2022-10-18 00:47:58 +03:00
|
|
|
.route("/user/login/:user_address", get(users::user_login_get))
|
2022-09-24 00:46:27 +03:00
|
|
|
.route(
|
2022-09-25 19:37:45 +03:00
|
|
|
"/user/login/:user_address/:message_eip",
|
2022-10-18 00:47:58 +03:00
|
|
|
get(users::user_login_get),
|
2022-09-24 00:46:27 +03:00
|
|
|
)
|
2022-10-18 00:47:58 +03:00
|
|
|
.route("/user/login", post(users::user_login_post))
|
2022-10-26 00:10:05 +03:00
|
|
|
.route("/user", get(users::user_get))
|
|
|
|
.route("/user", post(users::user_post))
|
2022-10-18 00:47:58 +03:00
|
|
|
.route("/user/balance", get(users::user_balance_get))
|
|
|
|
.route("/user/balance/:txid", post(users::user_balance_post))
|
2022-10-27 03:12:42 +03:00
|
|
|
.route("/user/keys", get(users::rpc_keys_get))
|
2022-10-31 23:51:06 +03:00
|
|
|
.route("/user/keys", post(users::rpc_keys_management))
|
|
|
|
.route("/user/keys", put(users::rpc_keys_management))
|
2022-10-20 09:17:20 +03:00
|
|
|
.route("/user/revert_logs", get(users::user_revert_logs_get))
|
2022-10-19 03:56:57 +03:00
|
|
|
.route(
|
|
|
|
"/user/stats/aggregate",
|
2022-12-12 22:00:15 +03:00
|
|
|
get(users::user_stats_aggregated_get),
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
"/user/stats/aggregated",
|
|
|
|
get(users::user_stats_aggregated_get),
|
2022-10-19 03:56:57 +03:00
|
|
|
)
|
2022-10-20 02:02:34 +03:00
|
|
|
.route("/user/stats/detailed", get(users::user_stats_detailed_get))
|
2022-10-18 00:47:58 +03:00
|
|
|
.route("/user/logout", post(users::user_logout_post))
|
2023-02-19 23:34:39 +03:00
|
|
|
.route("/admin/modify_role", get(admin::admin_change_user_roles))
|
2023-03-03 04:39:50 +03:00
|
|
|
.route(
|
|
|
|
"/admin/imitate-login/:admin_address/:user_address",
|
|
|
|
get(admin::admin_login_get),
|
|
|
|
)
|
2023-02-19 23:34:39 +03:00
|
|
|
.route(
|
2023-02-15 17:20:16 +03:00
|
|
|
"/admin/imitate-login/:admin_address/:user_address/:message_eip",
|
2023-02-19 23:34:39 +03:00
|
|
|
get(admin::admin_login_get),
|
|
|
|
)
|
|
|
|
.route("/admin/imitate-login", post(admin::admin_login_post))
|
2023-02-27 12:56:06 +03:00
|
|
|
.route("/admin/imitate-logout", post(admin::admin_logout_post))
|
2023-01-17 09:54:40 +03:00
|
|
|
//
|
|
|
|
// Axum layers
|
2022-08-21 11:18:57 +03:00
|
|
|
// layers are ordered bottom up
|
|
|
|
// the last layer is first for requests and last for responses
|
2023-01-17 09:54:40 +03:00
|
|
|
//
|
2022-09-25 19:35:01 +03:00
|
|
|
// Mark the `Authorization` request header as sensitive so it doesn't show in logs
|
|
|
|
.layer(SetSensitiveRequestHeadersLayer::new(once(AUTHORIZATION)))
|
2022-09-25 07:26:13 +03:00
|
|
|
// handle cors
|
|
|
|
.layer(CorsLayer::very_permissive())
|
2022-09-25 19:35:01 +03:00
|
|
|
// application state
|
2022-10-21 01:51:56 +03:00
|
|
|
.layer(Extension(proxy_app.clone()))
|
2022-11-16 23:17:33 +03:00
|
|
|
// frontend caches
|
|
|
|
.layer(Extension(response_cache))
|
2023-02-03 00:58:04 +03:00
|
|
|
.layer(Extension(health_cache))
|
2022-08-07 22:35:24 +03:00
|
|
|
// 404 for any unknown routes
|
2022-12-14 05:13:23 +03:00
|
|
|
.fallback(errors::handler_404);
|
2022-06-05 22:58:47 +03:00
|
|
|
|
|
|
|
// run our app with hyper
|
2022-08-16 03:33:26 +03:00
|
|
|
// TODO: allow only listening on localhost? top_config.app.host.parse()?
|
2022-06-05 22:58:47 +03:00
|
|
|
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
2022-08-06 04:17:25 +03:00
|
|
|
info!("listening on port {}", port);
|
2022-08-11 05:57:01 +03:00
|
|
|
|
2022-08-26 20:26:17 +03:00
|
|
|
// TODO: into_make_service is enough if we always run behind a proxy. make into_make_service_with_connect_info optional?
|
2022-08-11 05:57:01 +03:00
|
|
|
/*
|
|
|
|
It sequentially looks for an IP in:
|
|
|
|
- x-forwarded-for header (de-facto standard)
|
|
|
|
- x-real-ip header
|
|
|
|
- forwarded header (new standard)
|
|
|
|
- axum::extract::ConnectInfo (if not behind proxy)
|
|
|
|
*/
|
|
|
|
let service = app.into_make_service_with_connect_info::<SocketAddr>();
|
|
|
|
|
2022-08-13 00:00:26 +03:00
|
|
|
// `axum::Server` is a re-export of `hyper::Server`
|
2022-06-05 22:58:47 +03:00
|
|
|
axum::Server::bind(&addr)
|
2022-08-07 09:48:57 +03:00
|
|
|
// TODO: option to use with_connect_info. we want it in dev, but not when running behind a proxy, but not
|
2022-08-11 05:57:01 +03:00
|
|
|
.serve(service)
|
2022-06-05 22:58:47 +03:00
|
|
|
.await
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|