2022-10-18 00:47:58 +03:00
|
|
|
//! Take a user's HTTP JSON-RPC requests and either respond from local data or proxy the request to a backend rpc server.
|
|
|
|
|
2022-10-20 23:26:14 +03:00
|
|
|
use super::authorization::{ip_is_authorized, key_is_authorized};
|
2022-08-21 12:44:53 +03:00
|
|
|
use super::errors::FrontendResult;
|
2023-01-17 09:54:40 +03:00
|
|
|
use super::rpc_proxy_ws::ProxyMode;
|
2022-08-16 22:29:00 +03:00
|
|
|
use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum};
|
2022-09-22 22:57:21 +03:00
|
|
|
use axum::extract::Path;
|
2022-10-20 23:26:14 +03:00
|
|
|
use axum::headers::{Origin, Referer, UserAgent};
|
2022-09-09 00:01:36 +03:00
|
|
|
use axum::TypedHeader;
|
2022-09-07 06:54:16 +03:00
|
|
|
use axum::{response::IntoResponse, Extension, Json};
|
2023-02-06 20:55:27 +03:00
|
|
|
use axum_client_ip::InsecureClientIp;
|
2022-10-20 01:26:33 +03:00
|
|
|
use axum_macros::debug_handler;
|
2022-12-20 08:37:12 +03:00
|
|
|
use itertools::Itertools;
|
2022-06-05 22:58:47 +03:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
2022-10-18 00:47:58 +03:00
|
|
|
/// POST /rpc -- Public entrypoint for HTTP JSON-RPC requests. Web3 wallets use this.
|
|
|
|
/// Defaults to rate limiting by IP address, but can also read the Authorization header for a bearer token.
|
|
|
|
/// If possible, please use a WebSocket instead.
|
2022-10-20 01:26:33 +03:00
|
|
|
#[debug_handler]
|
2022-09-24 07:31:06 +03:00
|
|
|
pub async fn proxy_web3_rpc(
|
2022-07-07 06:22:09 +03:00
|
|
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
2023-02-06 20:55:27 +03:00
|
|
|
ip: InsecureClientIp,
|
2023-01-17 09:54:40 +03:00
|
|
|
origin: Option<TypedHeader<Origin>>,
|
|
|
|
Json(payload): Json<JsonRpcRequestEnum>,
|
|
|
|
) -> FrontendResult {
|
|
|
|
_proxy_web3_rpc(app, ip, origin, payload, ProxyMode::Best).await
|
|
|
|
}
|
|
|
|
|
|
|
|
#[debug_handler]
|
|
|
|
pub async fn fastest_proxy_web3_rpc(
|
|
|
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
2023-02-06 20:55:27 +03:00
|
|
|
ip: InsecureClientIp,
|
2023-01-17 09:54:40 +03:00
|
|
|
origin: Option<TypedHeader<Origin>>,
|
|
|
|
Json(payload): Json<JsonRpcRequestEnum>,
|
|
|
|
) -> FrontendResult {
|
|
|
|
// TODO: read the fastest number from params
|
|
|
|
// TODO: check that the app allows this without authentication
|
|
|
|
_proxy_web3_rpc(app, ip, origin, payload, ProxyMode::Fastest(0)).await
|
|
|
|
}
|
|
|
|
|
|
|
|
#[debug_handler]
|
|
|
|
pub async fn versus_proxy_web3_rpc(
|
|
|
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
2023-02-06 20:55:27 +03:00
|
|
|
ip: InsecureClientIp,
|
2022-10-21 23:59:05 +03:00
|
|
|
origin: Option<TypedHeader<Origin>>,
|
2022-09-09 00:01:36 +03:00
|
|
|
Json(payload): Json<JsonRpcRequestEnum>,
|
2023-01-17 09:54:40 +03:00
|
|
|
) -> FrontendResult {
|
|
|
|
_proxy_web3_rpc(app, ip, origin, payload, ProxyMode::Versus).await
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn _proxy_web3_rpc(
|
|
|
|
app: Arc<Web3ProxyApp>,
|
2023-02-06 20:55:27 +03:00
|
|
|
InsecureClientIp(ip): InsecureClientIp,
|
2023-01-17 09:54:40 +03:00
|
|
|
origin: Option<TypedHeader<Origin>>,
|
|
|
|
payload: JsonRpcRequestEnum,
|
|
|
|
proxy_mode: ProxyMode,
|
2022-08-21 12:39:38 +03:00
|
|
|
) -> FrontendResult {
|
2022-12-28 06:43:02 +03:00
|
|
|
// TODO: benchmark spawning this
|
|
|
|
// TODO: do we care about keeping the TypedHeader wrapper?
|
|
|
|
let origin = origin.map(|x| x.0);
|
2022-12-20 21:54:13 +03:00
|
|
|
|
2023-03-03 04:39:50 +03:00
|
|
|
let (authorization, semaphore) = ip_is_authorized(&app, ip, origin, proxy_mode).await?;
|
2022-09-22 23:27:14 +03:00
|
|
|
|
2022-12-28 06:43:02 +03:00
|
|
|
let authorization = Arc::new(authorization);
|
2022-09-22 23:27:14 +03:00
|
|
|
|
2022-12-28 09:11:18 +03:00
|
|
|
let (response, rpcs, _semaphore) = app
|
2023-03-03 04:39:50 +03:00
|
|
|
.proxy_web3_rpc(authorization, payload)
|
2022-12-28 06:43:02 +03:00
|
|
|
.await
|
|
|
|
.map(|(x, y)| (x, y, semaphore))?;
|
2022-08-21 12:39:38 +03:00
|
|
|
|
2022-12-20 08:37:12 +03:00
|
|
|
let mut response = Json(&response).into_response();
|
|
|
|
|
|
|
|
let headers = response.headers_mut();
|
|
|
|
|
2022-12-28 06:43:02 +03:00
|
|
|
// TODO: this might be slow. think about this more
|
2022-12-20 08:37:12 +03:00
|
|
|
// TODO: special string if no rpcs were used (cache hit)?
|
2023-03-01 22:23:59 +03:00
|
|
|
let mut backup_used = false;
|
|
|
|
|
|
|
|
let rpcs: String = rpcs
|
|
|
|
.into_iter()
|
|
|
|
.map(|x| {
|
|
|
|
if x.backup {
|
|
|
|
backup_used = true;
|
|
|
|
}
|
|
|
|
x.name.clone()
|
|
|
|
})
|
|
|
|
.join(",");
|
2022-12-20 08:37:12 +03:00
|
|
|
|
|
|
|
headers.insert(
|
2023-03-01 22:23:59 +03:00
|
|
|
"X-W3P-BACKEND-RPCS",
|
2022-12-20 22:01:34 +03:00
|
|
|
rpcs.parse().expect("W3P-BACKEND-RPCS should always parse"),
|
2022-12-20 08:37:12 +03:00
|
|
|
);
|
|
|
|
|
2023-03-01 22:23:59 +03:00
|
|
|
headers.insert(
|
|
|
|
"X-W3P-BACKUP-RPC",
|
|
|
|
backup_used
|
|
|
|
.to_string()
|
|
|
|
.parse()
|
|
|
|
.expect("W3P-BACKEND-RPCS should always parse"),
|
|
|
|
);
|
|
|
|
|
2022-12-20 08:37:12 +03:00
|
|
|
Ok(response)
|
2022-08-04 04:10:27 +03:00
|
|
|
}
|
2022-07-07 06:22:09 +03:00
|
|
|
|
2022-10-18 00:47:58 +03:00
|
|
|
/// Authenticated entrypoint for HTTP JSON-RPC requests. Web3 wallets use this.
|
|
|
|
/// Rate limit and billing based on the api key in the url.
|
|
|
|
/// Can optionally authorized based on origin, referer, or user agent.
|
|
|
|
/// If possible, please use a WebSocket instead.
|
2022-10-20 01:26:33 +03:00
|
|
|
#[debug_handler]
|
2022-09-24 07:31:06 +03:00
|
|
|
pub async fn proxy_web3_rpc_with_key(
|
2022-08-04 04:10:27 +03:00
|
|
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
2023-02-06 20:55:27 +03:00
|
|
|
ip: InsecureClientIp,
|
2022-09-23 08:22:33 +03:00
|
|
|
origin: Option<TypedHeader<Origin>>,
|
2022-09-22 02:50:55 +03:00
|
|
|
referer: Option<TypedHeader<Referer>>,
|
2022-09-09 00:01:36 +03:00
|
|
|
user_agent: Option<TypedHeader<UserAgent>>,
|
2022-10-27 03:12:42 +03:00
|
|
|
Path(rpc_key): Path<String>,
|
2022-12-14 05:13:23 +03:00
|
|
|
Json(payload): Json<JsonRpcRequestEnum>,
|
2023-01-17 09:54:40 +03:00
|
|
|
) -> FrontendResult {
|
|
|
|
_proxy_web3_rpc_with_key(
|
|
|
|
app,
|
|
|
|
ip,
|
|
|
|
origin,
|
|
|
|
referer,
|
|
|
|
user_agent,
|
|
|
|
rpc_key,
|
|
|
|
payload,
|
|
|
|
ProxyMode::Best,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
2023-03-03 04:39:50 +03:00
|
|
|
#[debug_handler]
|
|
|
|
pub async fn debug_proxy_web3_rpc_with_key(
|
|
|
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
|
|
|
ip: InsecureClientIp,
|
|
|
|
origin: Option<TypedHeader<Origin>>,
|
|
|
|
referer: Option<TypedHeader<Referer>>,
|
|
|
|
user_agent: Option<TypedHeader<UserAgent>>,
|
|
|
|
Path(rpc_key): Path<String>,
|
|
|
|
Json(payload): Json<JsonRpcRequestEnum>,
|
|
|
|
) -> FrontendResult {
|
|
|
|
_proxy_web3_rpc_with_key(
|
|
|
|
app,
|
|
|
|
ip,
|
|
|
|
origin,
|
|
|
|
referer,
|
|
|
|
user_agent,
|
|
|
|
rpc_key,
|
|
|
|
payload,
|
|
|
|
ProxyMode::Debug,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
2023-01-17 09:54:40 +03:00
|
|
|
#[debug_handler]
|
|
|
|
pub async fn fastest_proxy_web3_rpc_with_key(
|
|
|
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
2023-02-06 20:55:27 +03:00
|
|
|
ip: InsecureClientIp,
|
2023-01-17 09:54:40 +03:00
|
|
|
origin: Option<TypedHeader<Origin>>,
|
|
|
|
referer: Option<TypedHeader<Referer>>,
|
|
|
|
user_agent: Option<TypedHeader<UserAgent>>,
|
|
|
|
Path(rpc_key): Path<String>,
|
|
|
|
Json(payload): Json<JsonRpcRequestEnum>,
|
|
|
|
) -> FrontendResult {
|
|
|
|
_proxy_web3_rpc_with_key(
|
|
|
|
app,
|
|
|
|
ip,
|
|
|
|
origin,
|
|
|
|
referer,
|
|
|
|
user_agent,
|
|
|
|
rpc_key,
|
|
|
|
payload,
|
|
|
|
ProxyMode::Fastest(0),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
|
|
|
#[debug_handler]
|
|
|
|
pub async fn versus_proxy_web3_rpc_with_key(
|
|
|
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
2023-02-06 20:55:27 +03:00
|
|
|
ip: InsecureClientIp,
|
2023-01-17 09:54:40 +03:00
|
|
|
origin: Option<TypedHeader<Origin>>,
|
|
|
|
referer: Option<TypedHeader<Referer>>,
|
|
|
|
user_agent: Option<TypedHeader<UserAgent>>,
|
|
|
|
Path(rpc_key): Path<String>,
|
|
|
|
Json(payload): Json<JsonRpcRequestEnum>,
|
|
|
|
) -> FrontendResult {
|
|
|
|
_proxy_web3_rpc_with_key(
|
|
|
|
app,
|
|
|
|
ip,
|
|
|
|
origin,
|
|
|
|
referer,
|
|
|
|
user_agent,
|
|
|
|
rpc_key,
|
|
|
|
payload,
|
|
|
|
ProxyMode::Versus,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
async fn _proxy_web3_rpc_with_key(
|
|
|
|
app: Arc<Web3ProxyApp>,
|
2023-02-06 20:55:27 +03:00
|
|
|
InsecureClientIp(ip): InsecureClientIp,
|
2023-01-17 09:54:40 +03:00
|
|
|
origin: Option<TypedHeader<Origin>>,
|
|
|
|
referer: Option<TypedHeader<Referer>>,
|
|
|
|
user_agent: Option<TypedHeader<UserAgent>>,
|
|
|
|
rpc_key: String,
|
|
|
|
payload: JsonRpcRequestEnum,
|
|
|
|
proxy_mode: ProxyMode,
|
2022-08-21 12:39:38 +03:00
|
|
|
) -> FrontendResult {
|
2022-12-20 21:54:13 +03:00
|
|
|
// TODO: DRY w/ proxy_web3_rpc
|
|
|
|
// the request can take a while, so we spawn so that we can start serving another request
|
2022-12-28 06:43:02 +03:00
|
|
|
let rpc_key = rpc_key.parse()?;
|
|
|
|
|
|
|
|
let (authorization, semaphore) = key_is_authorized(
|
|
|
|
&app,
|
|
|
|
rpc_key,
|
|
|
|
ip,
|
|
|
|
origin.map(|x| x.0),
|
2023-03-03 04:39:50 +03:00
|
|
|
proxy_mode,
|
2022-12-28 06:43:02 +03:00
|
|
|
referer.map(|x| x.0),
|
|
|
|
user_agent.map(|x| x.0),
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
let authorization = Arc::new(authorization);
|
|
|
|
|
2023-03-01 22:23:59 +03:00
|
|
|
let rpc_secret_key_id = authorization.checks.rpc_secret_key_id;
|
|
|
|
|
2022-12-28 09:11:18 +03:00
|
|
|
let (response, rpcs, _semaphore) = app
|
2023-03-03 04:39:50 +03:00
|
|
|
.proxy_web3_rpc(authorization, payload)
|
2022-12-28 06:43:02 +03:00
|
|
|
.await
|
|
|
|
.map(|(x, y)| (x, y, semaphore))?;
|
2022-09-22 23:27:14 +03:00
|
|
|
|
2022-12-20 21:54:13 +03:00
|
|
|
let mut response = Json(&response).into_response();
|
2022-08-16 07:56:01 +03:00
|
|
|
|
2022-12-20 21:54:13 +03:00
|
|
|
let headers = response.headers_mut();
|
2022-08-21 12:39:38 +03:00
|
|
|
|
2023-03-01 22:23:59 +03:00
|
|
|
let mut backup_used = false;
|
|
|
|
|
2023-01-04 23:07:53 +03:00
|
|
|
// TODO: special string if no rpcs were used (cache hit)? or is an empty string fine? maybe the rpc name + "cached"
|
2023-03-01 22:23:59 +03:00
|
|
|
let rpcs: String = rpcs
|
|
|
|
.into_iter()
|
|
|
|
.map(|x| {
|
|
|
|
if x.backup {
|
|
|
|
backup_used = true;
|
|
|
|
}
|
|
|
|
x.name.clone()
|
|
|
|
})
|
|
|
|
.join(",");
|
2022-12-20 21:54:13 +03:00
|
|
|
|
|
|
|
headers.insert(
|
2023-03-01 22:23:59 +03:00
|
|
|
"X-W3P-BACKEND-RPCs",
|
2022-12-20 22:01:34 +03:00
|
|
|
rpcs.parse().expect("W3P-BACKEND-RPCS should always parse"),
|
2022-12-20 21:54:13 +03:00
|
|
|
);
|
|
|
|
|
2023-03-01 22:23:59 +03:00
|
|
|
headers.insert(
|
|
|
|
"X-W3P-BACKUP-RPC",
|
|
|
|
backup_used
|
|
|
|
.to_string()
|
|
|
|
.parse()
|
|
|
|
.expect("W3P-BACKEND-RPCS should always parse"),
|
|
|
|
);
|
|
|
|
|
|
|
|
if let Some(rpc_secret_key_id) = rpc_secret_key_id {
|
|
|
|
headers.insert(
|
|
|
|
"X-W3P-KEY-ID",
|
|
|
|
rpc_secret_key_id
|
|
|
|
.to_string()
|
|
|
|
.parse()
|
|
|
|
.expect("X-CLIENT-IP should always parse"),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-20 21:54:13 +03:00
|
|
|
Ok(response)
|
2022-06-05 22:58:47 +03:00
|
|
|
}
|