Merge remote-tracking branch 'rorytrent/Web3ProxyError' into devel
This commit is contained in:
commit
1a160a8c7d
@ -1,8 +1,8 @@
|
||||
use crate::app::Web3ProxyApp;
|
||||
use crate::frontend::errors::FrontendErrorResponse;
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyResponse};
|
||||
use crate::http_params::get_user_id_from_params;
|
||||
use anyhow::Context;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{
|
||||
headers::{authorization::Bearer, Authorization},
|
||||
Json, TypedHeader,
|
||||
@ -24,25 +24,21 @@ pub async fn query_admin_modify_usertier<'a>(
|
||||
app: &'a Web3ProxyApp,
|
||||
bearer: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
params: &'a HashMap<String, String>,
|
||||
) -> Result<Response, FrontendErrorResponse> {
|
||||
) -> Web3ProxyResponse {
|
||||
// Quickly return if any of the input tokens are bad
|
||||
let user_address: Vec<u8> = params
|
||||
.get("user_address")
|
||||
.ok_or_else(|| {
|
||||
FrontendErrorResponse::BadRequest(
|
||||
"Unable to find user_address key in request".to_string(),
|
||||
)
|
||||
Web3ProxyError::BadRequest("Unable to find user_address key in request".to_string())
|
||||
})?
|
||||
.parse::<Address>()
|
||||
.map_err(|_| {
|
||||
FrontendErrorResponse::BadRequest(
|
||||
"Unable to parse user_address as an Address".to_string(),
|
||||
)
|
||||
Web3ProxyError::BadRequest("Unable to parse user_address as an Address".to_string())
|
||||
})?
|
||||
.to_fixed_bytes()
|
||||
.into();
|
||||
let user_tier_title = params.get("user_tier_title").ok_or_else(|| {
|
||||
FrontendErrorResponse::BadRequest(
|
||||
Web3ProxyError::BadRequest(
|
||||
"Unable to get the user_tier_title key from the request".to_string(),
|
||||
)
|
||||
})?;
|
||||
@ -78,7 +74,7 @@ pub async fn query_admin_modify_usertier<'a>(
|
||||
.filter(admin::Column::UserId.eq(caller_id))
|
||||
.one(&db_conn)
|
||||
.await?
|
||||
.ok_or(FrontendErrorResponse::AccessDenied)?;
|
||||
.ok_or(Web3ProxyError::AccessDenied)?;
|
||||
|
||||
// If we are here, that means an admin was found, and we can safely proceed
|
||||
|
||||
@ -87,7 +83,7 @@ pub async fn query_admin_modify_usertier<'a>(
|
||||
.filter(user::Column::Address.eq(user_address))
|
||||
.one(&db_conn)
|
||||
.await?
|
||||
.ok_or(FrontendErrorResponse::BadRequest(
|
||||
.ok_or(Web3ProxyError::BadRequest(
|
||||
"No user with this id found".to_string(),
|
||||
))?;
|
||||
// Return early if the target user_tier_id is the same as the original user_tier_id
|
||||
@ -101,7 +97,7 @@ pub async fn query_admin_modify_usertier<'a>(
|
||||
.filter(user_tier::Column::Title.eq(user_tier_title.clone()))
|
||||
.one(&db_conn)
|
||||
.await?
|
||||
.ok_or(FrontendErrorResponse::BadRequest(
|
||||
.ok_or(Web3ProxyError::BadRequest(
|
||||
"User Tier name was not found".to_string(),
|
||||
))?;
|
||||
|
||||
|
@ -4,12 +4,12 @@ mod ws;
|
||||
use crate::block_number::{block_needed, BlockNeeded};
|
||||
use crate::config::{AppConfig, TopConfig};
|
||||
use crate::frontend::authorization::{Authorization, RequestMetadata, RpcSecretKey};
|
||||
use crate::frontend::errors::FrontendErrorResponse;
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResult};
|
||||
use crate::frontend::rpc_proxy_ws::ProxyMode;
|
||||
use crate::jsonrpc::{
|
||||
JsonRpcForwardedResponse, JsonRpcForwardedResponseEnum, JsonRpcRequest, JsonRpcRequestEnum,
|
||||
};
|
||||
use crate::rpcs::blockchain::{BlocksByHashCache, Web3ProxyBlock};
|
||||
use crate::rpcs::blockchain::Web3ProxyBlock;
|
||||
use crate::rpcs::consensus::ConsensusWeb3Rpcs;
|
||||
use crate::rpcs::many::Web3Rpcs;
|
||||
use crate::rpcs::one::Web3Rpc;
|
||||
@ -18,6 +18,7 @@ use crate::stats::{AppStat, RpcQueryStats, StatBuffer};
|
||||
use crate::user_token::UserBearerToken;
|
||||
use anyhow::Context;
|
||||
use axum::headers::{Origin, Referer, UserAgent};
|
||||
use axum::http::StatusCode;
|
||||
use chrono::Utc;
|
||||
use deferred_rate_limiter::DeferredRateLimiter;
|
||||
use derive_more::From;
|
||||
@ -1051,7 +1052,7 @@ impl Web3ProxyApp {
|
||||
self: &Arc<Self>,
|
||||
authorization: Arc<Authorization>,
|
||||
request: JsonRpcRequestEnum,
|
||||
) -> Result<(JsonRpcForwardedResponseEnum, Vec<Arc<Web3Rpc>>), FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<(JsonRpcForwardedResponseEnum, Vec<Arc<Web3Rpc>>)> {
|
||||
// trace!(?request, "proxy_web3_rpc");
|
||||
|
||||
// even though we have timeouts on the requests to our backend providers,
|
||||
@ -1089,7 +1090,7 @@ impl Web3ProxyApp {
|
||||
self: &Arc<Self>,
|
||||
authorization: &Arc<Authorization>,
|
||||
requests: Vec<JsonRpcRequest>,
|
||||
) -> Result<(Vec<JsonRpcForwardedResponse>, Vec<Arc<Web3Rpc>>), FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<(Vec<JsonRpcForwardedResponse>, Vec<Arc<Web3Rpc>>)> {
|
||||
// TODO: we should probably change ethers-rs to support this directly. they pushed this off to v2 though
|
||||
let num_requests = requests.len();
|
||||
|
||||
@ -1097,12 +1098,11 @@ impl Web3ProxyApp {
|
||||
// TODO: improve flattening
|
||||
|
||||
// get the head block now so that any requests that need it all use the same block
|
||||
// TODO: FrontendErrorResponse that handles "no servers synced" in a consistent way
|
||||
// TODO: this still has an edge condition if there is a reorg in the middle of the request!!!
|
||||
let head_block_num = self
|
||||
.balanced_rpcs
|
||||
.head_block_num()
|
||||
.context(anyhow::anyhow!("no servers synced"))?;
|
||||
.ok_or(Web3ProxyError::NoServersSynced)?;
|
||||
|
||||
let responses = join_all(
|
||||
requests
|
||||
@ -1163,10 +1163,10 @@ impl Web3ProxyApp {
|
||||
authorization: &Arc<Authorization>,
|
||||
mut request: JsonRpcRequest,
|
||||
head_block_num: Option<U64>,
|
||||
) -> Result<(JsonRpcForwardedResponse, Vec<Arc<Web3Rpc>>), FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<(JsonRpcForwardedResponse, Vec<Arc<Web3Rpc>>)> {
|
||||
// trace!("Received request: {:?}", request);
|
||||
|
||||
let request_metadata = Arc::new(RequestMetadata::new(request.num_bytes())?);
|
||||
let request_metadata = Arc::new(RequestMetadata::new(request.num_bytes()));
|
||||
|
||||
let mut kafka_stuff = None;
|
||||
|
||||
@ -1347,10 +1347,7 @@ impl Web3ProxyApp {
|
||||
}
|
||||
None => {
|
||||
// TODO: what does geth do if this happens?
|
||||
// TODO: i think we want a 502 so that haproxy retries on another server
|
||||
return Err(
|
||||
anyhow::anyhow!("no servers synced. unknown eth_blockNumber").into(),
|
||||
);
|
||||
return Err(Web3ProxyError::UnknownBlockNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1377,7 +1374,7 @@ impl Web3ProxyApp {
|
||||
|
||||
let mut gas_estimate: U256 = if let Some(gas_estimate) = response.result.take() {
|
||||
serde_json::from_str(gas_estimate.get())
|
||||
.context("gas estimate result is not an U256")?
|
||||
.or(Err(Web3ProxyError::GasEstimateNotU256))?
|
||||
} else {
|
||||
// i think this is always an error response
|
||||
let rpcs = request_metadata.backend_requests.lock().clone();
|
||||
@ -1438,7 +1435,7 @@ impl Web3ProxyApp {
|
||||
|
||||
let head_block_num = head_block_num
|
||||
.or(self.balanced_rpcs.head_block_num())
|
||||
.ok_or_else(|| anyhow::anyhow!("no servers synced"))?;
|
||||
.ok_or_else(|| Web3ProxyError::NoServersSynced)?;
|
||||
|
||||
// TODO: error/wait if no head block!
|
||||
|
||||
@ -1467,15 +1464,15 @@ impl Web3ProxyApp {
|
||||
{
|
||||
let params = request
|
||||
.params
|
||||
.context("there must be params if we got this far")?;
|
||||
.web3_context("there must be params if we got this far")?;
|
||||
|
||||
let params = params
|
||||
.as_array()
|
||||
.context("there must be an array if we got this far")?
|
||||
.web3_context("there must be an array if we got this far")?
|
||||
.get(0)
|
||||
.context("there must be an item if we got this far")?
|
||||
.web3_context("there must be an item if we got this far")?
|
||||
.as_str()
|
||||
.context("there must be a string if we got this far")?;
|
||||
.web3_context("there must be a string if we got this far")?;
|
||||
|
||||
let params = Bytes::from_str(params)
|
||||
.expect("there must be Bytes if we got this far");
|
||||
@ -1599,11 +1596,12 @@ impl Web3ProxyApp {
|
||||
let param = Bytes::from_str(
|
||||
params[0]
|
||||
.as_str()
|
||||
.context("parsing params 0 into str then bytes")?,
|
||||
.ok_or(Web3ProxyError::ParseBytesError(None))
|
||||
.web3_context("parsing params 0 into str then bytes")?,
|
||||
)
|
||||
.map_err(|x| {
|
||||
trace!("bad request: {:?}", x);
|
||||
FrontendErrorResponse::BadRequest(
|
||||
Web3ProxyError::BadRequest(
|
||||
"param 0 could not be read as H256".to_string(),
|
||||
)
|
||||
})?;
|
||||
@ -1618,7 +1616,7 @@ impl Web3ProxyApp {
|
||||
return Ok((
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"invalid request",
|
||||
None,
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
Some(request_id),
|
||||
),
|
||||
vec![],
|
||||
@ -1640,7 +1638,7 @@ impl Web3ProxyApp {
|
||||
method => {
|
||||
if method.starts_with("admin_") {
|
||||
// TODO: emit a stat? will probably just be noise
|
||||
return Err(FrontendErrorResponse::AccessDenied);
|
||||
return Err(Web3ProxyError::AccessDenied);
|
||||
}
|
||||
|
||||
// emit stats
|
||||
@ -1648,7 +1646,7 @@ impl Web3ProxyApp {
|
||||
// TODO: if no servers synced, wait for them to be synced? probably better to error and let haproxy retry another server
|
||||
let head_block_num = head_block_num
|
||||
.or(self.balanced_rpcs.head_block_num())
|
||||
.context("no servers synced")?;
|
||||
.ok_or(Web3ProxyError::NoServersSynced)?;
|
||||
|
||||
// we do this check before checking caches because it might modify the request params
|
||||
// TODO: add a stat for archive vs full since they should probably cost different
|
||||
@ -1771,17 +1769,10 @@ impl Web3ProxyApp {
|
||||
// TODO: only cache the inner response
|
||||
// TODO: how are we going to stream this?
|
||||
// TODO: check response size. if its very large, return it in a custom Error type that bypasses caching? or will moka do that for us?
|
||||
Ok::<_, anyhow::Error>(response)
|
||||
Ok::<_, Web3ProxyError>(response)
|
||||
})
|
||||
.await
|
||||
// TODO: what is the best way to handle an Arc here?
|
||||
.map_err(|err| {
|
||||
// TODO: emit a stat for an error
|
||||
anyhow::anyhow!(
|
||||
"error while caching and forwarding response: {}",
|
||||
err
|
||||
)
|
||||
})?
|
||||
// TODO: add context (error while caching and forwarding response {})
|
||||
.await?
|
||||
} else {
|
||||
self.balanced_rpcs
|
||||
.try_proxy_connection(
|
||||
@ -1813,7 +1804,7 @@ impl Web3ProxyApp {
|
||||
stat_sender
|
||||
.send_async(response_stat.into())
|
||||
.await
|
||||
.context("stat_sender sending response_stat")?;
|
||||
.map_err(Web3ProxyError::SendAppStatError)?;
|
||||
}
|
||||
|
||||
return Ok((response, rpcs));
|
||||
@ -1836,7 +1827,7 @@ impl Web3ProxyApp {
|
||||
stat_sender
|
||||
.send_async(response_stat.into())
|
||||
.await
|
||||
.context("stat_sender sending response stat")?;
|
||||
.map_err(Web3ProxyError::SendAppStatError)?;
|
||||
}
|
||||
|
||||
if let Some((kafka_topic, kafka_key, kafka_headers)) = kafka_stuff {
|
||||
@ -1846,7 +1837,7 @@ impl Web3ProxyApp {
|
||||
.expect("if headers are set, producer must exist");
|
||||
|
||||
let response_bytes =
|
||||
rmp_serde::to_vec(&response).context("failed msgpack serialize response")?;
|
||||
rmp_serde::to_vec(&response).web3_context("failed msgpack serialize response")?;
|
||||
|
||||
let f = async move {
|
||||
let produce_future = kafka_producer.send(
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
use super::Web3ProxyApp;
|
||||
use crate::frontend::authorization::{Authorization, RequestMetadata};
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResult};
|
||||
use crate::jsonrpc::JsonRpcForwardedResponse;
|
||||
use crate::jsonrpc::JsonRpcRequest;
|
||||
use crate::rpcs::transactions::TxStatus;
|
||||
use crate::stats::RpcQueryStats;
|
||||
use anyhow::Context;
|
||||
use axum::extract::ws::Message;
|
||||
use ethers::prelude::U64;
|
||||
use futures::future::AbortHandle;
|
||||
@ -27,13 +27,13 @@ impl Web3ProxyApp {
|
||||
subscription_count: &'a AtomicUsize,
|
||||
// TODO: taking a sender for Message instead of the exact json we are planning to send feels wrong, but its easier for now
|
||||
response_sender: flume::Sender<Message>,
|
||||
) -> anyhow::Result<(AbortHandle, JsonRpcForwardedResponse)> {
|
||||
) -> Web3ProxyResult<(AbortHandle, JsonRpcForwardedResponse)> {
|
||||
// TODO: this is not efficient
|
||||
let request_bytes = serde_json::to_string(&request_json)
|
||||
.context("finding request size")?
|
||||
.web3_context("finding request size")?
|
||||
.len();
|
||||
|
||||
let request_metadata = Arc::new(RequestMetadata::new(request_bytes).unwrap());
|
||||
let request_metadata = Arc::new(RequestMetadata::new(request_bytes));
|
||||
|
||||
let (subscription_abort_handle, subscription_registration) = AbortHandle::new_pair();
|
||||
|
||||
@ -67,7 +67,7 @@ impl Web3ProxyApp {
|
||||
};
|
||||
|
||||
// TODO: what should the payload for RequestMetadata be?
|
||||
let request_metadata = Arc::new(RequestMetadata::new(0).unwrap());
|
||||
let request_metadata = Arc::new(RequestMetadata::new(0));
|
||||
|
||||
// TODO: make a struct for this? using our JsonRpcForwardedResponse won't work because it needs an id
|
||||
let response_json = json!({
|
||||
@ -133,7 +133,7 @@ impl Web3ProxyApp {
|
||||
// TODO: do something with this handle?
|
||||
tokio::spawn(async move {
|
||||
while let Some(Ok(new_tx_state)) = pending_tx_receiver.next().await {
|
||||
let request_metadata = Arc::new(RequestMetadata::new(0).unwrap());
|
||||
let request_metadata = Arc::new(RequestMetadata::new(0));
|
||||
|
||||
let new_tx = match new_tx_state {
|
||||
TxStatus::Pending(tx) => tx,
|
||||
@ -208,7 +208,7 @@ impl Web3ProxyApp {
|
||||
// TODO: do something with this handle?
|
||||
tokio::spawn(async move {
|
||||
while let Some(Ok(new_tx_state)) = pending_tx_receiver.next().await {
|
||||
let request_metadata = Arc::new(RequestMetadata::new(0).unwrap());
|
||||
let request_metadata = Arc::new(RequestMetadata::new(0));
|
||||
|
||||
let new_tx = match new_tx_state {
|
||||
TxStatus::Pending(tx) => tx,
|
||||
@ -284,7 +284,7 @@ impl Web3ProxyApp {
|
||||
// TODO: do something with this handle?
|
||||
tokio::spawn(async move {
|
||||
while let Some(Ok(new_tx_state)) = pending_tx_receiver.next().await {
|
||||
let request_metadata = Arc::new(RequestMetadata::new(0).unwrap());
|
||||
let request_metadata = Arc::new(RequestMetadata::new(0));
|
||||
|
||||
let new_tx = match new_tx_state {
|
||||
TxStatus::Pending(tx) => tx,
|
||||
@ -341,7 +341,7 @@ impl Web3ProxyApp {
|
||||
);
|
||||
});
|
||||
}
|
||||
_ => return Err(anyhow::anyhow!("unimplemented")),
|
||||
_ => return Err(Web3ProxyError::NotImplemented),
|
||||
}
|
||||
|
||||
// TODO: do something with subscription_join_handle?
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! Helper functions for turning ether's BlockNumber into numbers and updating incoming queries to match.
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyResult};
|
||||
use anyhow::Context;
|
||||
use ethers::{
|
||||
prelude::{BlockNumber, U64},
|
||||
@ -126,7 +127,7 @@ pub async fn block_needed(
|
||||
params: Option<&mut serde_json::Value>,
|
||||
head_block_num: U64,
|
||||
rpcs: &Web3Rpcs,
|
||||
) -> anyhow::Result<BlockNeeded> {
|
||||
) -> Web3ProxyResult<BlockNeeded> {
|
||||
// some requests have potentially very large responses
|
||||
// TODO: only skip caching if the response actually is large
|
||||
if method.starts_with("trace_") || method == "debug_traceTransaction" {
|
||||
@ -181,7 +182,7 @@ pub async fn block_needed(
|
||||
.get_mut(0)
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid format. no params"))?
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid format"))?;
|
||||
.ok_or_else(|| Web3ProxyError::BadRequest("invalid format".to_string()))?;
|
||||
|
||||
if obj.contains_key("blockHash") {
|
||||
return Ok(BlockNeeded::CacheSuccessForever);
|
||||
|
@ -1,13 +1,12 @@
|
||||
//! Handle admin helper logic
|
||||
|
||||
use super::authorization::login_is_authorized;
|
||||
use super::errors::FrontendResult;
|
||||
use super::errors::Web3ProxyResponse;
|
||||
use crate::admin_queries::query_admin_modify_usertier;
|
||||
use crate::app::Web3ProxyApp;
|
||||
use crate::frontend::errors::FrontendErrorResponse;
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyErrorContext};
|
||||
use crate::user_token::UserBearerToken;
|
||||
use crate::PostLogin;
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
headers::{authorization::Bearer, Authorization},
|
||||
@ -43,7 +42,7 @@ pub async fn admin_change_user_roles(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
bearer: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let response = query_admin_modify_usertier(&app, bearer, ¶ms).await?;
|
||||
|
||||
Ok(response)
|
||||
@ -58,7 +57,7 @@ pub async fn admin_login_get(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
InsecureClientIp(ip): InsecureClientIp,
|
||||
Path(mut params): Path<HashMap<String, String>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
// First check if the login is authorized
|
||||
login_is_authorized(&app, ip).await?;
|
||||
|
||||
@ -85,30 +84,22 @@ pub async fn admin_login_get(
|
||||
let admin_address: Address = params
|
||||
.get("admin_address")
|
||||
.ok_or_else(|| {
|
||||
FrontendErrorResponse::BadRequest(
|
||||
"Unable to find admin_address key in request".to_string(),
|
||||
)
|
||||
Web3ProxyError::BadRequest("Unable to find admin_address key in request".to_string())
|
||||
})?
|
||||
.parse::<Address>()
|
||||
.map_err(|_err| {
|
||||
FrontendErrorResponse::BadRequest(
|
||||
"Unable to parse admin_address as an Address".to_string(),
|
||||
)
|
||||
Web3ProxyError::BadRequest("Unable to parse admin_address as an Address".to_string())
|
||||
})?;
|
||||
|
||||
// Fetch the user_address parameter from the login string ... (as who we want to be logging in ...)
|
||||
let user_address: Vec<u8> = params
|
||||
.get("user_address")
|
||||
.ok_or_else(|| {
|
||||
FrontendErrorResponse::BadRequest(
|
||||
"Unable to find user_address key in request".to_string(),
|
||||
)
|
||||
Web3ProxyError::BadRequest("Unable to find user_address key in request".to_string())
|
||||
})?
|
||||
.parse::<Address>()
|
||||
.map_err(|_err| {
|
||||
FrontendErrorResponse::BadRequest(
|
||||
"Unable to parse user_address as an Address".to_string(),
|
||||
)
|
||||
Web3ProxyError::BadRequest("Unable to parse user_address as an Address".to_string())
|
||||
})?
|
||||
.to_fixed_bytes()
|
||||
.into();
|
||||
@ -147,10 +138,10 @@ pub async fn admin_login_get(
|
||||
|
||||
let admin_address: Vec<u8> = admin_address.to_fixed_bytes().into();
|
||||
|
||||
let db_conn = app.db_conn().context("login requires a database")?;
|
||||
let db_conn = app.db_conn().web3_context("login requires a database")?;
|
||||
let db_replica = app
|
||||
.db_replica()
|
||||
.context("login requires a replica database")?;
|
||||
.web3_context("login requires a replica database")?;
|
||||
|
||||
// Get the user that we want to imitate from the read-only database (their id ...)
|
||||
// TODO: Only get the id, not the whole user object ...
|
||||
@ -158,7 +149,7 @@ pub async fn admin_login_get(
|
||||
.filter(user::Column::Address.eq(user_address))
|
||||
.one(db_replica.conn())
|
||||
.await?
|
||||
.ok_or(FrontendErrorResponse::BadRequest(
|
||||
.ok_or(Web3ProxyError::BadRequest(
|
||||
"Could not find user in db".to_string(),
|
||||
))?;
|
||||
|
||||
@ -169,7 +160,7 @@ pub async fn admin_login_get(
|
||||
.filter(user::Column::Address.eq(admin_address))
|
||||
.one(db_replica.conn())
|
||||
.await?
|
||||
.ok_or(FrontendErrorResponse::BadRequest(
|
||||
.ok_or(Web3ProxyError::BadRequest(
|
||||
"Could not find admin in db".to_string(),
|
||||
))?;
|
||||
|
||||
@ -184,7 +175,7 @@ pub async fn admin_login_get(
|
||||
trail
|
||||
.save(&db_conn)
|
||||
.await
|
||||
.context("saving user's pending_login")?;
|
||||
.web3_context("saving user's pending_login")?;
|
||||
|
||||
// Can there be two login-sessions at the same time?
|
||||
// I supposed if the user logs in, the admin would be logged out and vice versa
|
||||
@ -209,7 +200,7 @@ pub async fn admin_login_get(
|
||||
user_pending_login
|
||||
.save(&db_conn)
|
||||
.await
|
||||
.context("saving an admin trail pre login")?;
|
||||
.web3_context("saving an admin trail pre login")?;
|
||||
|
||||
// there are multiple ways to sign messages and not all wallets support them
|
||||
// TODO: default message eip from config?
|
||||
@ -223,7 +214,7 @@ pub async fn admin_login_get(
|
||||
"eip4361" => message.to_string(),
|
||||
_ => {
|
||||
// TODO: custom error that is handled a 401
|
||||
return Err(anyhow::anyhow!("invalid message eip given").into());
|
||||
return Err(Web3ProxyError::InvalidEip);
|
||||
}
|
||||
};
|
||||
|
||||
@ -238,14 +229,14 @@ pub async fn admin_login_post(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
InsecureClientIp(ip): InsecureClientIp,
|
||||
Json(payload): Json<PostLogin>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
login_is_authorized(&app, ip).await?;
|
||||
|
||||
// Check for the signed bytes ..
|
||||
// TODO: this seems too verbose. how can we simply convert a String into a [u8; 65]
|
||||
let their_sig_bytes = Bytes::from_str(&payload.sig).context("parsing sig")?;
|
||||
let their_sig_bytes = Bytes::from_str(&payload.sig).web3_context("parsing sig")?;
|
||||
if their_sig_bytes.len() != 65 {
|
||||
return Err(anyhow::anyhow!("checking signature length").into());
|
||||
return Err(Web3ProxyError::InvalidSignatureLength);
|
||||
}
|
||||
let mut their_sig: [u8; 65] = [0; 65];
|
||||
for x in 0..65 {
|
||||
@ -255,17 +246,18 @@ pub async fn admin_login_post(
|
||||
// we can't trust that they didn't tamper with the message in some way. like some clients return it hex encoded
|
||||
// TODO: checking 0x seems fragile, but I think it will be fine. siwe message text shouldn't ever start with 0x
|
||||
let their_msg: Message = if payload.msg.starts_with("0x") {
|
||||
let their_msg_bytes = Bytes::from_str(&payload.msg).context("parsing payload message")?;
|
||||
let their_msg_bytes =
|
||||
Bytes::from_str(&payload.msg).web3_context("parsing payload message")?;
|
||||
|
||||
// TODO: lossy or no?
|
||||
String::from_utf8_lossy(their_msg_bytes.as_ref())
|
||||
.parse::<siwe::Message>()
|
||||
.context("parsing hex string message")?
|
||||
.web3_context("parsing hex string message")?
|
||||
} else {
|
||||
payload
|
||||
.msg
|
||||
.parse::<siwe::Message>()
|
||||
.context("parsing string message")?
|
||||
.web3_context("parsing string message")?
|
||||
};
|
||||
|
||||
// the only part of the message we will trust is their nonce
|
||||
@ -273,7 +265,9 @@ pub async fn admin_login_post(
|
||||
let login_nonce = UserBearerToken::from_str(&their_msg.nonce)?;
|
||||
|
||||
// fetch the message we gave them from our database
|
||||
let db_replica = app.db_replica().context("Getting database connection")?;
|
||||
let db_replica = app
|
||||
.db_replica()
|
||||
.web3_context("Getting database connection")?;
|
||||
|
||||
// massage type for the db
|
||||
let login_nonce_uuid: Uuid = login_nonce.clone().into();
|
||||
@ -283,30 +277,30 @@ pub async fn admin_login_post(
|
||||
.filter(pending_login::Column::Nonce.eq(login_nonce_uuid))
|
||||
.one(db_replica.conn())
|
||||
.await
|
||||
.context("database error while finding pending_login")?
|
||||
.context("login nonce not found")?;
|
||||
.web3_context("database error while finding pending_login")?
|
||||
.web3_context("login nonce not found")?;
|
||||
|
||||
let our_msg: siwe::Message = user_pending_login
|
||||
.message
|
||||
.parse()
|
||||
.context("parsing siwe message")?;
|
||||
.web3_context("parsing siwe message")?;
|
||||
|
||||
// default options are fine. the message includes timestamp and domain and nonce
|
||||
let verify_config = VerificationOpts::default();
|
||||
|
||||
let db_conn = app
|
||||
.db_conn()
|
||||
.context("deleting expired pending logins requires a db")?;
|
||||
.web3_context("deleting expired pending logins requires a db")?;
|
||||
|
||||
if let Err(err_1) = our_msg
|
||||
.verify(&their_sig, &verify_config)
|
||||
.await
|
||||
.context("verifying signature against our local message")
|
||||
.web3_context("verifying signature against our local message")
|
||||
{
|
||||
// verification method 1 failed. try eip191
|
||||
if let Err(err_191) = our_msg
|
||||
.verify_eip191(&their_sig)
|
||||
.context("verifying eip191 signature against our local message")
|
||||
.web3_context("verifying eip191 signature against our local message")
|
||||
{
|
||||
// delete ALL expired rows.
|
||||
let now = Utc::now();
|
||||
@ -318,18 +312,16 @@ pub async fn admin_login_post(
|
||||
// TODO: emit a stat? if this is high something weird might be happening
|
||||
debug!("cleared expired pending_logins: {:?}", delete_result);
|
||||
|
||||
return Err(anyhow::anyhow!(
|
||||
"both the primary and eip191 verification failed: {:#?}; {:#?}",
|
||||
err_1,
|
||||
err_191
|
||||
)
|
||||
.into());
|
||||
return Err(Web3ProxyError::EipVerificationFailed(
|
||||
Box::new(err_1),
|
||||
Box::new(err_191),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let imitating_user_id = user_pending_login
|
||||
.imitating_user
|
||||
.context("getting address of the imitating user")?;
|
||||
.web3_context("getting address of the imitating user")?;
|
||||
|
||||
// TODO: limit columns or load whole user?
|
||||
// TODO: Right now this loads the whole admin. I assume we might want to load the user though (?) figure this out as we go along...
|
||||
@ -337,13 +329,13 @@ pub async fn admin_login_post(
|
||||
.filter(user::Column::Address.eq(our_msg.address.as_ref()))
|
||||
.one(db_replica.conn())
|
||||
.await?
|
||||
.context("getting admin address")?;
|
||||
.web3_context("getting admin address")?;
|
||||
|
||||
let imitating_user = user::Entity::find()
|
||||
.filter(user::Column::Id.eq(imitating_user_id))
|
||||
.one(db_replica.conn())
|
||||
.await?
|
||||
.context("admin address was not found!")?;
|
||||
.web3_context("admin address was not found!")?;
|
||||
|
||||
// Add a message that the admin has logged in
|
||||
// Note that the admin is trying to log in as this user
|
||||
@ -357,7 +349,7 @@ pub async fn admin_login_post(
|
||||
trail
|
||||
.save(&db_conn)
|
||||
.await
|
||||
.context("saving an admin trail post login")?;
|
||||
.web3_context("saving an admin trail post login")?;
|
||||
|
||||
// I supposed we also get the rpc_key, whatever this is used for (?).
|
||||
// I think the RPC key should still belong to the admin though in this case ...
|
||||
@ -367,7 +359,7 @@ pub async fn admin_login_post(
|
||||
.filter(rpc_key::Column::UserId.eq(admin.id))
|
||||
.all(db_replica.conn())
|
||||
.await
|
||||
.context("failed loading user's key")?;
|
||||
.web3_context("failed loading user's key")?;
|
||||
|
||||
// create a bearer token for the user.
|
||||
let user_bearer_token = UserBearerToken::default();
|
||||
@ -408,7 +400,7 @@ pub async fn admin_login_post(
|
||||
user_login
|
||||
.save(&db_conn)
|
||||
.await
|
||||
.context("saving user login")?;
|
||||
.web3_context("saving user login")?;
|
||||
|
||||
if let Err(err) = user_pending_login
|
||||
.into_active_model()
|
||||
@ -427,10 +419,12 @@ pub async fn admin_login_post(
|
||||
pub async fn admin_logout_post(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let user_bearer = UserBearerToken::try_from(bearer)?;
|
||||
|
||||
let db_conn = app.db_conn().context("database needed for user logout")?;
|
||||
let db_conn = app
|
||||
.db_conn()
|
||||
.web3_context("database needed for user logout")?;
|
||||
|
||||
if let Err(err) = login::Entity::delete_many()
|
||||
.filter(login::Column::BearerToken.eq(user_bearer.uuid()))
|
||||
|
@ -1,11 +1,10 @@
|
||||
//! Utilities for authorization of logged in and anonymous users.
|
||||
|
||||
use super::errors::FrontendErrorResponse;
|
||||
use super::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResult};
|
||||
use super::rpc_proxy_ws::ProxyMode;
|
||||
use crate::app::{AuthorizationChecks, Web3ProxyApp, APP_USER_AGENT};
|
||||
use crate::rpcs::one::Web3Rpc;
|
||||
use crate::user_token::UserBearerToken;
|
||||
use anyhow::Context;
|
||||
use axum::headers::authorization::Bearer;
|
||||
use axum::headers::{Header, Origin, Referer, UserAgent};
|
||||
use chrono::Utc;
|
||||
@ -88,11 +87,11 @@ pub struct RequestMetadata {
|
||||
}
|
||||
|
||||
impl RequestMetadata {
|
||||
pub fn new(request_bytes: usize) -> anyhow::Result<Self> {
|
||||
pub fn new(request_bytes: usize) -> Self {
|
||||
// TODO: how can we do this without turning it into a string first. this is going to slow us down!
|
||||
let request_bytes = request_bytes as u64;
|
||||
|
||||
let new = Self {
|
||||
Self {
|
||||
start_instant: Instant::now(),
|
||||
request_bytes,
|
||||
archive_request: false.into(),
|
||||
@ -102,9 +101,7 @@ impl RequestMetadata {
|
||||
response_bytes: 0.into(),
|
||||
response_millis: 0.into(),
|
||||
response_from_backup_rpc: false.into(),
|
||||
};
|
||||
|
||||
Ok(new)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,7 +127,7 @@ impl Display for RpcSecretKey {
|
||||
}
|
||||
|
||||
impl FromStr for RpcSecretKey {
|
||||
type Err = anyhow::Error;
|
||||
type Err = Web3ProxyError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(ulid) = s.parse::<Ulid>() {
|
||||
@ -139,7 +136,7 @@ impl FromStr for RpcSecretKey {
|
||||
Ok(uuid.into())
|
||||
} else {
|
||||
// TODO: custom error type so that this shows as a 400
|
||||
Err(anyhow::anyhow!("UserKey was not a ULID or UUID"))
|
||||
Err(Web3ProxyError::InvalidUserKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,7 +172,7 @@ impl From<RpcSecretKey> for Uuid {
|
||||
}
|
||||
|
||||
impl Authorization {
|
||||
pub fn internal(db_conn: Option<DatabaseConnection>) -> anyhow::Result<Self> {
|
||||
pub fn internal(db_conn: Option<DatabaseConnection>) -> Web3ProxyResult<Self> {
|
||||
let authorization_checks = AuthorizationChecks {
|
||||
// any error logs on a local (internal) query are likely problems. log them all
|
||||
log_revert_chance: 1.0,
|
||||
@ -206,7 +203,7 @@ impl Authorization {
|
||||
proxy_mode: ProxyMode,
|
||||
referer: Option<Referer>,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> Web3ProxyResult<Self> {
|
||||
// some origins can override max_requests_per_period for anon users
|
||||
let max_requests_per_period = origin
|
||||
.as_ref()
|
||||
@ -244,13 +241,13 @@ impl Authorization {
|
||||
referer: Option<Referer>,
|
||||
user_agent: Option<UserAgent>,
|
||||
authorization_type: AuthorizationType,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> Web3ProxyResult<Self> {
|
||||
// check ip
|
||||
match &authorization_checks.allowed_ips {
|
||||
None => {}
|
||||
Some(allowed_ips) => {
|
||||
if !allowed_ips.iter().any(|x| x.contains(&ip)) {
|
||||
return Err(anyhow::anyhow!("IP ({}) is not allowed!", ip));
|
||||
return Err(Web3ProxyError::IpNotAllowed(ip));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,10 +256,10 @@ impl Authorization {
|
||||
match (&origin, &authorization_checks.allowed_origins) {
|
||||
(None, None) => {}
|
||||
(Some(_), None) => {}
|
||||
(None, Some(_)) => return Err(anyhow::anyhow!("Origin required")),
|
||||
(None, Some(_)) => return Err(Web3ProxyError::OriginRequired),
|
||||
(Some(origin), Some(allowed_origins)) => {
|
||||
if !allowed_origins.contains(origin) {
|
||||
return Err(anyhow::anyhow!("Origin ({}) is not allowed!", origin));
|
||||
return Err(Web3ProxyError::OriginNotAllowed(origin.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -271,10 +268,10 @@ impl Authorization {
|
||||
match (&referer, &authorization_checks.allowed_referers) {
|
||||
(None, None) => {}
|
||||
(Some(_), None) => {}
|
||||
(None, Some(_)) => return Err(anyhow::anyhow!("Referer required")),
|
||||
(None, Some(_)) => return Err(Web3ProxyError::RefererRequired),
|
||||
(Some(referer), Some(allowed_referers)) => {
|
||||
if !allowed_referers.contains(referer) {
|
||||
return Err(anyhow::anyhow!("Referer ({:?}) is not allowed!", referer));
|
||||
return Err(Web3ProxyError::RefererNotAllowed(referer.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -283,13 +280,10 @@ impl Authorization {
|
||||
match (&user_agent, &authorization_checks.allowed_user_agents) {
|
||||
(None, None) => {}
|
||||
(Some(_), None) => {}
|
||||
(None, Some(_)) => return Err(anyhow::anyhow!("User agent required")),
|
||||
(None, Some(_)) => return Err(Web3ProxyError::UserAgentRequired),
|
||||
(Some(user_agent), Some(allowed_user_agents)) => {
|
||||
if !allowed_user_agents.contains(user_agent) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"User agent ({}) is not allowed!",
|
||||
user_agent
|
||||
));
|
||||
return Err(Web3ProxyError::UserAgentNotAllowed(user_agent.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -308,14 +302,11 @@ impl Authorization {
|
||||
|
||||
/// rate limit logins only by ip.
|
||||
/// we want all origins and referers and user agents to count together
|
||||
pub async fn login_is_authorized(
|
||||
app: &Web3ProxyApp,
|
||||
ip: IpAddr,
|
||||
) -> Result<Authorization, FrontendErrorResponse> {
|
||||
pub async fn login_is_authorized(app: &Web3ProxyApp, ip: IpAddr) -> Web3ProxyResult<Authorization> {
|
||||
let authorization = match app.rate_limit_login(ip, ProxyMode::Best).await? {
|
||||
RateLimitResult::Allowed(authorization, None) => authorization,
|
||||
RateLimitResult::RateLimited(authorization, retry_at) => {
|
||||
return Err(FrontendErrorResponse::RateLimited(authorization, retry_at));
|
||||
return Err(Web3ProxyError::RateLimited(authorization, retry_at));
|
||||
}
|
||||
// TODO: don't panic. give the user an error
|
||||
x => unimplemented!("rate_limit_login shouldn't ever see these: {:?}", x),
|
||||
@ -330,7 +321,7 @@ pub async fn ip_is_authorized(
|
||||
ip: IpAddr,
|
||||
origin: Option<Origin>,
|
||||
proxy_mode: ProxyMode,
|
||||
) -> Result<(Authorization, Option<OwnedSemaphorePermit>), FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<(Authorization, Option<OwnedSemaphorePermit>)> {
|
||||
// TODO: i think we could write an `impl From` for this
|
||||
// TODO: move this to an AuthorizedUser extrator
|
||||
let (authorization, semaphore) = match app
|
||||
@ -345,7 +336,7 @@ pub async fn ip_is_authorized(
|
||||
RateLimitResult::Allowed(authorization, semaphore) => (authorization, semaphore),
|
||||
RateLimitResult::RateLimited(authorization, retry_at) => {
|
||||
// TODO: in the background, emit a stat (maybe simplest to use a channel?)
|
||||
return Err(FrontendErrorResponse::RateLimited(authorization, retry_at));
|
||||
return Err(Web3ProxyError::RateLimited(authorization, retry_at));
|
||||
}
|
||||
// TODO: don't panic. give the user an error
|
||||
x => unimplemented!("rate_limit_by_ip shouldn't ever see these: {:?}", x),
|
||||
@ -375,7 +366,7 @@ pub async fn ip_is_authorized(
|
||||
.await?;
|
||||
};
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
Ok::<_, Web3ProxyError>(())
|
||||
}
|
||||
.map_err(|err| {
|
||||
warn!("background update of recent_users:ip failed: {}", err);
|
||||
@ -389,7 +380,7 @@ pub async fn ip_is_authorized(
|
||||
Ok((authorization, semaphore))
|
||||
}
|
||||
|
||||
/// like app.rate_limit_by_rpc_key but converts to a FrontendErrorResponse;
|
||||
/// like app.rate_limit_by_rpc_key but converts to a Web3ProxyError;
|
||||
pub async fn key_is_authorized(
|
||||
app: &Arc<Web3ProxyApp>,
|
||||
rpc_key: RpcSecretKey,
|
||||
@ -398,7 +389,7 @@ pub async fn key_is_authorized(
|
||||
proxy_mode: ProxyMode,
|
||||
referer: Option<Referer>,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Result<(Authorization, Option<OwnedSemaphorePermit>), FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<(Authorization, Option<OwnedSemaphorePermit>)> {
|
||||
// check the rate limits. error if over the limit
|
||||
// TODO: i think this should be in an "impl From" or "impl Into"
|
||||
let (authorization, semaphore) = match app
|
||||
@ -407,9 +398,9 @@ pub async fn key_is_authorized(
|
||||
{
|
||||
RateLimitResult::Allowed(authorization, semaphore) => (authorization, semaphore),
|
||||
RateLimitResult::RateLimited(authorization, retry_at) => {
|
||||
return Err(FrontendErrorResponse::RateLimited(authorization, retry_at));
|
||||
return Err(Web3ProxyError::RateLimited(authorization, retry_at));
|
||||
}
|
||||
RateLimitResult::UnknownKey => return Err(FrontendErrorResponse::UnknownKey),
|
||||
RateLimitResult::UnknownKey => return Err(Web3ProxyError::UnknownKey),
|
||||
};
|
||||
|
||||
// TODO: DRY and maybe optimize the hashing
|
||||
@ -438,7 +429,7 @@ pub async fn key_is_authorized(
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
Ok::<_, Web3ProxyError>(())
|
||||
}
|
||||
.map_err(|err| {
|
||||
warn!("background update of recent_users:id failed: {}", err);
|
||||
@ -454,7 +445,7 @@ pub async fn key_is_authorized(
|
||||
|
||||
impl Web3ProxyApp {
|
||||
/// Limit the number of concurrent requests from the given ip address.
|
||||
pub async fn ip_semaphore(&self, ip: IpAddr) -> anyhow::Result<Option<OwnedSemaphorePermit>> {
|
||||
pub async fn ip_semaphore(&self, ip: IpAddr) -> Web3ProxyResult<Option<OwnedSemaphorePermit>> {
|
||||
if let Some(max_concurrent_requests) = self.config.public_max_concurrent_requests {
|
||||
let semaphore = self
|
||||
.ip_semaphores
|
||||
@ -482,12 +473,13 @@ impl Web3ProxyApp {
|
||||
pub async fn registered_user_semaphore(
|
||||
&self,
|
||||
authorization_checks: &AuthorizationChecks,
|
||||
) -> anyhow::Result<Option<OwnedSemaphorePermit>> {
|
||||
) -> Web3ProxyResult<Option<OwnedSemaphorePermit>> {
|
||||
if let Some(max_concurrent_requests) = authorization_checks.max_concurrent_requests {
|
||||
let user_id = authorization_checks
|
||||
.user_id
|
||||
.try_into()
|
||||
.context("user ids should always be non-zero")?;
|
||||
.or(Err(Web3ProxyError::UserIdZero))
|
||||
.web3_context("user ids should always be non-zero")?;
|
||||
|
||||
let semaphore = self
|
||||
.registered_user_semaphores
|
||||
@ -517,7 +509,7 @@ impl Web3ProxyApp {
|
||||
pub async fn bearer_is_authorized(
|
||||
&self,
|
||||
bearer: Bearer,
|
||||
) -> Result<(user::Model, OwnedSemaphorePermit), FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<(user::Model, OwnedSemaphorePermit)> {
|
||||
// get the user id for this bearer token
|
||||
let user_bearer_token = UserBearerToken::try_from(bearer)?;
|
||||
|
||||
@ -535,7 +527,7 @@ impl Web3ProxyApp {
|
||||
// get the attached address from the database for the given auth_token.
|
||||
let db_replica = self
|
||||
.db_replica()
|
||||
.context("checking if bearer token is authorized")?;
|
||||
.web3_context("checking if bearer token is authorized")?;
|
||||
|
||||
let user_bearer_uuid: Uuid = user_bearer_token.into();
|
||||
|
||||
@ -544,8 +536,8 @@ impl Web3ProxyApp {
|
||||
.filter(login::Column::BearerToken.eq(user_bearer_uuid))
|
||||
.one(db_replica.conn())
|
||||
.await
|
||||
.context("fetching user from db by bearer token")?
|
||||
.context("unknown bearer token")?;
|
||||
.web3_context("fetching user from db by bearer token")?
|
||||
.web3_context("unknown bearer token")?;
|
||||
|
||||
Ok((user, semaphore_permit))
|
||||
}
|
||||
@ -554,7 +546,7 @@ impl Web3ProxyApp {
|
||||
&self,
|
||||
ip: IpAddr,
|
||||
proxy_mode: ProxyMode,
|
||||
) -> anyhow::Result<RateLimitResult> {
|
||||
) -> Web3ProxyResult<RateLimitResult> {
|
||||
// TODO: dry this up with rate_limit_by_rpc_key?
|
||||
|
||||
// we don't care about user agent or origin or referer
|
||||
@ -611,7 +603,7 @@ impl Web3ProxyApp {
|
||||
ip: IpAddr,
|
||||
origin: Option<Origin>,
|
||||
proxy_mode: ProxyMode,
|
||||
) -> anyhow::Result<RateLimitResult> {
|
||||
) -> Web3ProxyResult<RateLimitResult> {
|
||||
// ip rate limits don't check referer or user agent
|
||||
// they do check origin because we can override rate limits for some origins
|
||||
let authorization = Authorization::external(
|
||||
@ -670,13 +662,15 @@ impl Web3ProxyApp {
|
||||
&self,
|
||||
proxy_mode: ProxyMode,
|
||||
rpc_secret_key: RpcSecretKey,
|
||||
) -> anyhow::Result<AuthorizationChecks> {
|
||||
let authorization_checks: Result<_, Arc<anyhow::Error>> = self
|
||||
) -> Web3ProxyResult<AuthorizationChecks> {
|
||||
let authorization_checks: Result<_, Arc<Web3ProxyError>> = self
|
||||
.rpc_secret_key_cache
|
||||
.try_get_with(rpc_secret_key.into(), async move {
|
||||
// trace!(?rpc_secret_key, "user cache miss");
|
||||
|
||||
let db_replica = self.db_replica().context("Getting database connection")?;
|
||||
let db_replica = self
|
||||
.db_replica()
|
||||
.web3_context("Getting database connection")?;
|
||||
|
||||
// TODO: join the user table to this to return the User? we don't always need it
|
||||
// TODO: join on secondary users
|
||||
@ -732,7 +726,11 @@ impl Web3ProxyApp {
|
||||
if let Some(allowed_referers) = rpc_key_model.allowed_referers {
|
||||
let x = allowed_referers
|
||||
.split(',')
|
||||
.map(|x| x.trim().parse::<Referer>())
|
||||
.map(|x| {
|
||||
x.trim()
|
||||
.parse::<Referer>()
|
||||
.or(Err(Web3ProxyError::InvalidReferer))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Some(x)
|
||||
@ -744,7 +742,11 @@ impl Web3ProxyApp {
|
||||
if let Some(allowed_user_agents) = rpc_key_model.allowed_user_agents {
|
||||
let x: Result<Vec<_>, _> = allowed_user_agents
|
||||
.split(',')
|
||||
.map(|x| x.trim().parse::<UserAgent>())
|
||||
.map(|x| {
|
||||
x.trim()
|
||||
.parse::<UserAgent>()
|
||||
.or(Err(Web3ProxyError::InvalidUserAgent))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(x?)
|
||||
@ -776,8 +778,7 @@ impl Web3ProxyApp {
|
||||
})
|
||||
.await;
|
||||
|
||||
// TODO: what's the best way to handle this arc? try_unwrap will not work
|
||||
authorization_checks.map_err(|err| anyhow::anyhow!(err))
|
||||
authorization_checks.map_err(Web3ProxyError::Arc)
|
||||
}
|
||||
|
||||
/// Authorized the ip/origin/referer/useragent and rate limit and concurrency
|
||||
@ -789,7 +790,7 @@ impl Web3ProxyApp {
|
||||
referer: Option<Referer>,
|
||||
rpc_key: RpcSecretKey,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> anyhow::Result<RateLimitResult> {
|
||||
) -> Web3ProxyResult<RateLimitResult> {
|
||||
let authorization_checks = self.authorization_checks(proxy_mode, rpc_key).await?;
|
||||
|
||||
// if no rpc_key_id matching the given rpc was found, then we can't rate limit by key
|
||||
@ -869,7 +870,7 @@ impl Authorization {
|
||||
pub async fn check_again(
|
||||
&self,
|
||||
app: &Arc<Web3ProxyApp>,
|
||||
) -> Result<(Arc<Self>, Option<OwnedSemaphorePermit>), FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<(Arc<Self>, Option<OwnedSemaphorePermit>)> {
|
||||
// TODO: we could probably do this without clones. but this is easy
|
||||
let (a, s) = if let Some(rpc_secret_key) = self.checks.rpc_secret_key {
|
||||
key_is_authorized(
|
||||
|
@ -2,51 +2,130 @@
|
||||
|
||||
use super::authorization::Authorization;
|
||||
use crate::jsonrpc::JsonRpcForwardedResponse;
|
||||
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
headers,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use derive_more::From;
|
||||
use derive_more::{Display, Error, From};
|
||||
use http::header::InvalidHeaderValue;
|
||||
use ipnet::AddrParseError;
|
||||
use log::{debug, error, trace, warn};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use migration::sea_orm::DbErr;
|
||||
use redis_rate_limiter::redis::RedisError;
|
||||
use reqwest::header::ToStrError;
|
||||
use tokio::{sync::AcquireError, task::JoinError, time::Instant};
|
||||
|
||||
pub type Web3ProxyResult<T> = Result<T, Web3ProxyError>;
|
||||
// TODO: take "IntoResponse" instead of Response?
|
||||
pub type FrontendResult = Result<Response, FrontendErrorResponse>;
|
||||
pub type Web3ProxyResponse = Web3ProxyResult<Response>;
|
||||
|
||||
// TODO:
|
||||
#[derive(Debug, From)]
|
||||
pub enum FrontendErrorResponse {
|
||||
#[derive(Debug, Display, Error, From)]
|
||||
pub enum Web3ProxyError {
|
||||
AccessDenied,
|
||||
#[error(ignore)]
|
||||
Anyhow(anyhow::Error),
|
||||
Arc(Arc<Web3ProxyError>),
|
||||
#[error(ignore)]
|
||||
#[from(ignore)]
|
||||
BadRequest(String),
|
||||
SemaphoreAcquireError(AcquireError),
|
||||
BadRouting,
|
||||
Database(DbErr),
|
||||
#[display(fmt = "{:#?}, {:#?}", _0, _1)]
|
||||
EipVerificationFailed(Box<Web3ProxyError>, Box<Web3ProxyError>),
|
||||
EthersHttpClientError(ethers::prelude::HttpClientError),
|
||||
EthersProviderError(ethers::prelude::ProviderError),
|
||||
EthersWsClientError(ethers::prelude::WsClientError),
|
||||
FlumeRecvError(flume::RecvError),
|
||||
GasEstimateNotU256,
|
||||
Headers(headers::Error),
|
||||
HeaderToString(ToStrError),
|
||||
InfluxDb2RequestError(influxdb2::RequestError),
|
||||
#[display(fmt = "{} > {}", min, max)]
|
||||
#[from(ignore)]
|
||||
InvalidBlockBounds {
|
||||
min: u64,
|
||||
max: u64,
|
||||
},
|
||||
InvalidHeaderValue(InvalidHeaderValue),
|
||||
InvalidEip,
|
||||
InvalidInviteCode,
|
||||
InvalidReferer,
|
||||
InvalidSignatureLength,
|
||||
InvalidUserAgent,
|
||||
InvalidUserKey,
|
||||
IpAddrParse(AddrParseError),
|
||||
#[error(ignore)]
|
||||
#[from(ignore)]
|
||||
IpNotAllowed(IpAddr),
|
||||
JoinError(JoinError),
|
||||
#[display(fmt = "{:?}", _0)]
|
||||
#[error(ignore)]
|
||||
JsonRpc(crate::jsonrpc::JsonRpcErrorData),
|
||||
MsgPackEncode(rmp_serde::encode::Error),
|
||||
NoBlockNumberOrHash,
|
||||
NoBlocksKnown,
|
||||
NoConsensusHeadBlock,
|
||||
NoHandleReady,
|
||||
NoServersSynced,
|
||||
#[display(fmt = "{}/{}", num_known, min_head_rpcs)]
|
||||
#[from(ignore)]
|
||||
NotEnoughRpcs {
|
||||
num_known: usize,
|
||||
min_head_rpcs: usize,
|
||||
},
|
||||
NotFound,
|
||||
NotImplemented,
|
||||
OriginRequired,
|
||||
#[error(ignore)]
|
||||
#[from(ignore)]
|
||||
OriginNotAllowed(headers::Origin),
|
||||
#[display(fmt = "{:?}", _0)]
|
||||
#[error(ignore)]
|
||||
ParseBytesError(Option<ethers::types::ParseBytesError>),
|
||||
ParseMsgError(siwe::ParseError),
|
||||
ParseAddressError,
|
||||
#[display(fmt = "{:?}, {:?}", _0, _1)]
|
||||
RateLimited(Authorization, Option<Instant>),
|
||||
Redis(RedisError),
|
||||
RefererRequired,
|
||||
#[display(fmt = "{:?}", _0)]
|
||||
#[error(ignore)]
|
||||
#[from(ignore)]
|
||||
RefererNotAllowed(headers::Referer),
|
||||
SemaphoreAcquireError(AcquireError),
|
||||
SendAppStatError(flume::SendError<crate::stats::AppStat>),
|
||||
SerdeJson(serde_json::Error),
|
||||
/// simple way to return an error message to the user and an anyhow to our logs
|
||||
#[display(fmt = "{}, {}, {:?}", _0, _1, _2)]
|
||||
StatusCode(StatusCode, String, Option<anyhow::Error>),
|
||||
/// TODO: what should be attached to the timout?
|
||||
Timeout(tokio::time::error::Elapsed),
|
||||
#[display(fmt = "{:?}", _0)]
|
||||
#[error(ignore)]
|
||||
Timeout(Option<tokio::time::error::Elapsed>),
|
||||
UlidDecode(ulid::DecodeError),
|
||||
UnknownBlockNumber,
|
||||
UnknownKey,
|
||||
UserAgentRequired,
|
||||
#[error(ignore)]
|
||||
UserAgentNotAllowed(headers::UserAgent),
|
||||
UserIdZero,
|
||||
VerificationError(siwe::VerificationError),
|
||||
WatchRecvError(tokio::sync::watch::error::RecvError),
|
||||
WatchSendError,
|
||||
WebsocketOnly,
|
||||
#[display(fmt = "{:?}, {}", _0, _1)]
|
||||
#[error(ignore)]
|
||||
WithContext(Option<Box<Web3ProxyError>>, String),
|
||||
}
|
||||
|
||||
impl FrontendErrorResponse {
|
||||
impl Web3ProxyError {
|
||||
pub fn into_response_parts(self) -> (StatusCode, JsonRpcForwardedResponse) {
|
||||
match self {
|
||||
Self::AccessDenied => {
|
||||
@ -85,6 +164,17 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::BadRouting => {
|
||||
error!("BadRouting");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"bad routing",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::Database(err) => {
|
||||
error!("database err={:?}", err);
|
||||
(
|
||||
@ -96,6 +186,78 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::EipVerificationFailed(err_1, err_191) => {
|
||||
info!(
|
||||
"EipVerificationFailed err_1={:#?} err2={:#?}",
|
||||
err_1, err_191
|
||||
);
|
||||
(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!(
|
||||
"both the primary and eip191 verification failed: {:#?}; {:#?}",
|
||||
err_1, err_191
|
||||
),
|
||||
Some(StatusCode::UNAUTHORIZED.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::EthersHttpClientError(err) => {
|
||||
warn!("EthersHttpClientError err={:?}", err);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"ether http client error",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::EthersProviderError(err) => {
|
||||
warn!("EthersProviderError err={:?}", err);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"ether provider error",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::EthersWsClientError(err) => {
|
||||
warn!("EthersWsClientError err={:?}", err);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"ether ws client error",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::FlumeRecvError(err) => {
|
||||
warn!("FlumeRecvError err={:#?}", err);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"flume recv error!",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::GasEstimateNotU256 => {
|
||||
warn!("GasEstimateNotU256");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"gas estimate result is not an U256",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::Headers(err) => {
|
||||
warn!("HeadersError {:?}", err);
|
||||
(
|
||||
@ -119,6 +281,20 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::InvalidBlockBounds { min, max } => {
|
||||
debug!("InvalidBlockBounds min={} max={}", min, max);
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!(
|
||||
"Invalid blocks bounds requested. min ({}) > max ({})",
|
||||
min, max
|
||||
),
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::IpAddrParse(err) => {
|
||||
warn!("IpAddrParse err={:?}", err);
|
||||
(
|
||||
@ -130,6 +306,17 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::IpNotAllowed(ip) => {
|
||||
warn!("IpNotAllowed ip={})", ip);
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!("IP ({}) is not allowed!", ip),
|
||||
Some(StatusCode::FORBIDDEN.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::InvalidHeaderValue(err) => {
|
||||
warn!("InvalidHeaderValue err={:?}", err);
|
||||
(
|
||||
@ -141,6 +328,72 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::InvalidEip => {
|
||||
warn!("InvalidEip");
|
||||
(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"invalid message eip given",
|
||||
Some(StatusCode::UNAUTHORIZED.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::InvalidInviteCode => {
|
||||
warn!("InvalidInviteCode");
|
||||
(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"invalid invite code",
|
||||
Some(StatusCode::UNAUTHORIZED.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::InvalidReferer => {
|
||||
warn!("InvalidReferer");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"invalid referer!",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::InvalidSignatureLength => {
|
||||
warn!("InvalidSignatureLength");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"invalid signature length",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::InvalidUserAgent => {
|
||||
warn!("InvalidUserAgent");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"invalid user agent!",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::InvalidUserKey => {
|
||||
warn!("InvalidUserKey");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"UserKey was not a ULID or UUID",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::JoinError(err) => {
|
||||
let code = if err.is_cancelled() {
|
||||
trace!("JoinError. likely shutting down. err={:?}", err);
|
||||
@ -160,6 +413,17 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::JsonRpc(err) => {
|
||||
debug!("JsonRpc err={:?}", err);
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"json rpc error!",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::MsgPackEncode(err) => {
|
||||
debug!("MsgPackEncode Error: {}", err);
|
||||
(
|
||||
@ -171,6 +435,75 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::NoBlockNumberOrHash => {
|
||||
warn!("NoBlockNumberOrHash");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"Blocks here must have a number or hash",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::NoBlocksKnown => {
|
||||
error!("NoBlocksKnown");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"no blocks known",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::NoConsensusHeadBlock => {
|
||||
error!("NoConsensusHeadBlock");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"no consensus head block",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::NoHandleReady => {
|
||||
error!("NoHandleReady");
|
||||
(
|
||||
StatusCode::BAD_GATEWAY,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"unable to retry for request handle",
|
||||
Some(StatusCode::BAD_GATEWAY.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::NoServersSynced => {
|
||||
warn!("NoServersSynced");
|
||||
(
|
||||
StatusCode::BAD_GATEWAY,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"no servers synced",
|
||||
Some(StatusCode::BAD_GATEWAY.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::NotEnoughRpcs {
|
||||
num_known,
|
||||
min_head_rpcs,
|
||||
} => {
|
||||
error!("NotEnoughRpcs {}/{}", num_known, min_head_rpcs);
|
||||
(
|
||||
StatusCode::BAD_GATEWAY,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!("not enough rpcs connected {}/{}", num_known, min_head_rpcs),
|
||||
Some(StatusCode::BAD_GATEWAY.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::NotFound => {
|
||||
// TODO: emit a stat?
|
||||
// TODO: instead of an error, show a normal html page for 404
|
||||
@ -183,6 +516,72 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::NotImplemented => {
|
||||
trace!("NotImplemented");
|
||||
(
|
||||
StatusCode::NOT_IMPLEMENTED,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"work in progress",
|
||||
Some(StatusCode::NOT_IMPLEMENTED.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::OriginRequired => {
|
||||
warn!("OriginRequired");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"Origin required",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::OriginNotAllowed(origin) => {
|
||||
warn!("OriginNotAllowed origin={}", origin);
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!("Origin ({}) is not allowed!", origin),
|
||||
Some(StatusCode::FORBIDDEN.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::ParseBytesError(err) => {
|
||||
warn!("ParseBytesError err={:?}", err);
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"parse bytes error!",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::ParseMsgError(err) => {
|
||||
warn!("ParseMsgError err={:?}", err);
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"parse message error!",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::ParseAddressError => {
|
||||
warn!("ParseAddressError");
|
||||
(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"unable to parse address",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
// TODO: this should actually by the id of the key. multiple users might control one key
|
||||
Self::RateLimited(authorization, retry_at) => {
|
||||
// TODO: emit a stat
|
||||
@ -226,6 +625,33 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::RefererRequired => {
|
||||
warn!("referer required");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"Referer required",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::RefererNotAllowed(referer) => {
|
||||
warn!("referer not allowed referer={:?}", referer);
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!("Referer ({:?}) is not allowed", referer),
|
||||
Some(StatusCode::FORBIDDEN.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::Arc(err) => match Arc::try_unwrap(err) {
|
||||
Ok(err) => err,
|
||||
Err(err) => Self::Anyhow(anyhow::anyhow!("{}", err)),
|
||||
}
|
||||
.into_response_parts(),
|
||||
Self::SemaphoreAcquireError(err) => {
|
||||
warn!("semaphore acquire err={:?}", err);
|
||||
(
|
||||
@ -238,6 +664,28 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::SendAppStatError(err) => {
|
||||
error!("SendAppStatError err={:?}", err);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"error stat_sender sending response_stat",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::SerdeJson(err) => {
|
||||
warn!("serde json err={:?}", err);
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"de/serialization error!",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::StatusCode(status_code, err_msg, err) => {
|
||||
// different status codes should get different error levels. 500s should warn. 400s should stat
|
||||
let code = status_code.as_u16();
|
||||
@ -283,6 +731,17 @@ impl FrontendErrorResponse {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::UnknownBlockNumber => {
|
||||
error!("UnknownBlockNumber");
|
||||
(
|
||||
StatusCode::BAD_GATEWAY,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"no servers synced. unknown eth_blockNumber",
|
||||
Some(StatusCode::BAD_GATEWAY.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
// TODO: stat?
|
||||
Self::UnknownKey => (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
@ -292,11 +751,114 @@ impl FrontendErrorResponse {
|
||||
None,
|
||||
),
|
||||
),
|
||||
Self::UserAgentRequired => {
|
||||
warn!("UserAgentRequired");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"User agent required",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::UserAgentNotAllowed(ua) => {
|
||||
warn!("UserAgentNotAllowed ua={}", ua);
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!("User agent ({}) is not allowed!", ua),
|
||||
Some(StatusCode::FORBIDDEN.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::UserIdZero => {
|
||||
warn!("UserIdZero");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"user ids should always be non-zero",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::VerificationError(err) => {
|
||||
warn!("VerificationError err={:?}", err);
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"verification error!",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::WatchRecvError(err) => {
|
||||
error!("WatchRecvError err={:?}", err);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"watch recv error!",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::WatchSendError => {
|
||||
error!("WatchSendError");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"watch send error!",
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::WebsocketOnly => {
|
||||
warn!("WebsocketOnly");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"redirect_public_url not set. only websockets work here",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::WithContext(err, msg) => {
|
||||
info!("in context: {}", msg);
|
||||
match err {
|
||||
Some(err) => err.into_response_parts(),
|
||||
None => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
msg,
|
||||
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for FrontendErrorResponse {
|
||||
impl From<ethers::types::ParseBytesError> for Web3ProxyError {
|
||||
fn from(err: ethers::types::ParseBytesError) -> Self {
|
||||
Self::ParseBytesError(Some(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tokio::time::error::Elapsed> for Web3ProxyError {
|
||||
fn from(err: tokio::time::error::Elapsed) -> Self {
|
||||
Self::Timeout(Some(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Web3ProxyError {
|
||||
fn into_response(self) -> Response {
|
||||
// TODO: include the request id in these so that users can give us something that will point to logs
|
||||
// TODO: status code is in the jsonrpc response and is also the first item in the tuple. DRY
|
||||
@ -307,5 +869,24 @@ impl IntoResponse for FrontendErrorResponse {
|
||||
}
|
||||
|
||||
pub async fn handler_404() -> Response {
|
||||
FrontendErrorResponse::NotFound.into_response()
|
||||
Web3ProxyError::NotFound.into_response()
|
||||
}
|
||||
|
||||
pub trait Web3ProxyErrorContext<T> {
|
||||
fn web3_context<S: Into<String>>(self, msg: S) -> Result<T, Web3ProxyError>;
|
||||
}
|
||||
|
||||
impl<T> Web3ProxyErrorContext<T> for Option<T> {
|
||||
fn web3_context<S: Into<String>>(self, msg: S) -> Result<T, Web3ProxyError> {
|
||||
self.ok_or(Web3ProxyError::WithContext(None, msg.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Web3ProxyErrorContext<T> for Result<T, E>
|
||||
where
|
||||
E: Into<Web3ProxyError>,
|
||||
{
|
||||
fn web3_context<S: Into<String>>(self, msg: S) -> Result<T, Web3ProxyError> {
|
||||
self.map_err(|err| Web3ProxyError::WithContext(Some(Box::new(err.into())), msg.into()))
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Take a user's HTTP JSON-RPC requests and either respond from local data or proxy the request to a backend rpc server.
|
||||
|
||||
use super::authorization::{ip_is_authorized, key_is_authorized};
|
||||
use super::errors::FrontendResult;
|
||||
use super::errors::Web3ProxyResponse;
|
||||
use super::rpc_proxy_ws::ProxyMode;
|
||||
use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum};
|
||||
use axum::extract::Path;
|
||||
@ -22,7 +22,7 @@ pub async fn proxy_web3_rpc(
|
||||
ip: InsecureClientIp,
|
||||
origin: Option<TypedHeader<Origin>>,
|
||||
Json(payload): Json<JsonRpcRequestEnum>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_proxy_web3_rpc(app, ip, origin, payload, ProxyMode::Best).await
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ pub async fn fastest_proxy_web3_rpc(
|
||||
ip: InsecureClientIp,
|
||||
origin: Option<TypedHeader<Origin>>,
|
||||
Json(payload): Json<JsonRpcRequestEnum>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
// 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
|
||||
@ -44,7 +44,7 @@ pub async fn versus_proxy_web3_rpc(
|
||||
ip: InsecureClientIp,
|
||||
origin: Option<TypedHeader<Origin>>,
|
||||
Json(payload): Json<JsonRpcRequestEnum>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_proxy_web3_rpc(app, ip, origin, payload, ProxyMode::Versus).await
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ async fn _proxy_web3_rpc(
|
||||
origin: Option<TypedHeader<Origin>>,
|
||||
payload: JsonRpcRequestEnum,
|
||||
proxy_mode: ProxyMode,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
// TODO: benchmark spawning this
|
||||
// TODO: do we care about keeping the TypedHeader wrapper?
|
||||
let origin = origin.map(|x| x.0);
|
||||
@ -115,7 +115,7 @@ pub async fn proxy_web3_rpc_with_key(
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
Path(rpc_key): Path<String>,
|
||||
Json(payload): Json<JsonRpcRequestEnum>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_proxy_web3_rpc_with_key(
|
||||
app,
|
||||
ip,
|
||||
@ -138,7 +138,7 @@ pub async fn debug_proxy_web3_rpc_with_key(
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
Path(rpc_key): Path<String>,
|
||||
Json(payload): Json<JsonRpcRequestEnum>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_proxy_web3_rpc_with_key(
|
||||
app,
|
||||
ip,
|
||||
@ -161,7 +161,7 @@ pub async fn fastest_proxy_web3_rpc_with_key(
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
Path(rpc_key): Path<String>,
|
||||
Json(payload): Json<JsonRpcRequestEnum>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_proxy_web3_rpc_with_key(
|
||||
app,
|
||||
ip,
|
||||
@ -184,7 +184,7 @@ pub async fn versus_proxy_web3_rpc_with_key(
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
Path(rpc_key): Path<String>,
|
||||
Json(payload): Json<JsonRpcRequestEnum>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_proxy_web3_rpc_with_key(
|
||||
app,
|
||||
ip,
|
||||
@ -208,7 +208,7 @@ async fn _proxy_web3_rpc_with_key(
|
||||
rpc_key: String,
|
||||
payload: JsonRpcRequestEnum,
|
||||
proxy_mode: ProxyMode,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
// TODO: DRY w/ proxy_web3_rpc
|
||||
// the request can take a while, so we spawn so that we can start serving another request
|
||||
let rpc_key = rpc_key.parse()?;
|
||||
|
@ -3,10 +3,11 @@
|
||||
//! WebSockets are the preferred method of receiving requests, but not all clients have good support.
|
||||
|
||||
use super::authorization::{ip_is_authorized, key_is_authorized, Authorization, RequestMetadata};
|
||||
use super::errors::{FrontendErrorResponse, FrontendResult};
|
||||
use super::errors::{Web3ProxyError, Web3ProxyResponse};
|
||||
use crate::stats::RpcQueryStats;
|
||||
use crate::{
|
||||
app::Web3ProxyApp,
|
||||
frontend::errors::Web3ProxyResult,
|
||||
jsonrpc::{JsonRpcForwardedResponse, JsonRpcForwardedResponseEnum, JsonRpcRequest},
|
||||
};
|
||||
use axum::headers::{Origin, Referer, UserAgent};
|
||||
@ -26,7 +27,7 @@ use futures::{
|
||||
use handlebars::Handlebars;
|
||||
use hashbrown::HashMap;
|
||||
use http::StatusCode;
|
||||
use log::{error, info, trace, warn};
|
||||
use log::{info, trace, warn};
|
||||
use serde_json::json;
|
||||
use serde_json::value::to_raw_value;
|
||||
use std::sync::Arc;
|
||||
@ -59,7 +60,7 @@ pub async fn websocket_handler(
|
||||
ip: InsecureClientIp,
|
||||
origin: Option<TypedHeader<Origin>>,
|
||||
ws_upgrade: Option<WebSocketUpgrade>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_websocket_handler(ProxyMode::Best, app, ip, origin, ws_upgrade).await
|
||||
}
|
||||
|
||||
@ -71,7 +72,7 @@ pub async fn fastest_websocket_handler(
|
||||
ip: InsecureClientIp,
|
||||
origin: Option<TypedHeader<Origin>>,
|
||||
ws_upgrade: Option<WebSocketUpgrade>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
// TODO: get the fastest number from the url params (default to 0/all)
|
||||
// TODO: config to disable this
|
||||
_websocket_handler(ProxyMode::Fastest(0), app, ip, origin, ws_upgrade).await
|
||||
@ -85,7 +86,7 @@ pub async fn versus_websocket_handler(
|
||||
ip: InsecureClientIp,
|
||||
origin: Option<TypedHeader<Origin>>,
|
||||
ws_upgrade: Option<WebSocketUpgrade>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
// TODO: config to disable this
|
||||
_websocket_handler(ProxyMode::Versus, app, ip, origin, ws_upgrade).await
|
||||
}
|
||||
@ -96,7 +97,7 @@ async fn _websocket_handler(
|
||||
InsecureClientIp(ip): InsecureClientIp,
|
||||
origin: Option<TypedHeader<Origin>>,
|
||||
ws_upgrade: Option<WebSocketUpgrade>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let origin = origin.map(|x| x.0);
|
||||
|
||||
let (authorization, _semaphore) = ip_is_authorized(&app, ip, origin, proxy_mode).await?;
|
||||
@ -112,11 +113,7 @@ async fn _websocket_handler(
|
||||
// this is not a websocket. redirect to a friendly page
|
||||
Ok(Redirect::permanent(redirect).into_response())
|
||||
} else {
|
||||
// TODO: do not use an anyhow error. send the user a 400
|
||||
Err(
|
||||
anyhow::anyhow!("redirect_public_url not set. only websockets work here")
|
||||
.into(),
|
||||
)
|
||||
Err(Web3ProxyError::WebsocketOnly)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -134,7 +131,7 @@ pub async fn websocket_handler_with_key(
|
||||
referer: Option<TypedHeader<Referer>>,
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
ws_upgrade: Option<WebSocketUpgrade>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_websocket_handler_with_key(
|
||||
ProxyMode::Best,
|
||||
app,
|
||||
@ -157,7 +154,7 @@ pub async fn debug_websocket_handler_with_key(
|
||||
referer: Option<TypedHeader<Referer>>,
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
ws_upgrade: Option<WebSocketUpgrade>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_websocket_handler_with_key(
|
||||
ProxyMode::Debug,
|
||||
app,
|
||||
@ -180,7 +177,7 @@ pub async fn fastest_websocket_handler_with_key(
|
||||
referer: Option<TypedHeader<Referer>>,
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
ws_upgrade: Option<WebSocketUpgrade>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
// TODO: get the fastest number from the url params (default to 0/all)
|
||||
_websocket_handler_with_key(
|
||||
ProxyMode::Fastest(0),
|
||||
@ -204,7 +201,7 @@ pub async fn versus_websocket_handler_with_key(
|
||||
referer: Option<TypedHeader<Referer>>,
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
ws_upgrade: Option<WebSocketUpgrade>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
_websocket_handler_with_key(
|
||||
ProxyMode::Versus,
|
||||
app,
|
||||
@ -228,7 +225,7 @@ async fn _websocket_handler_with_key(
|
||||
referer: Option<TypedHeader<Referer>>,
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
ws_upgrade: Option<WebSocketUpgrade>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let rpc_key = rpc_key.parse()?;
|
||||
|
||||
let (authorization, _semaphore) = key_is_authorized(
|
||||
@ -260,7 +257,7 @@ async fn _websocket_handler_with_key(
|
||||
&app.config.redirect_rpc_key_url,
|
||||
authorization.checks.rpc_secret_key_id,
|
||||
) {
|
||||
(None, None, _) => Err(FrontendErrorResponse::StatusCode(
|
||||
(None, None, _) => Err(Web3ProxyError::StatusCode(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"this page is for rpcs".to_string(),
|
||||
None,
|
||||
@ -273,7 +270,7 @@ async fn _websocket_handler_with_key(
|
||||
|
||||
if authorization.checks.rpc_secret_key_id.is_none() {
|
||||
// i don't think this is possible
|
||||
Err(FrontendErrorResponse::StatusCode(
|
||||
Err(Web3ProxyError::StatusCode(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"AUTHORIZATION header required".to_string(),
|
||||
None,
|
||||
@ -291,7 +288,7 @@ async fn _websocket_handler_with_key(
|
||||
}
|
||||
}
|
||||
// any other combinations get a simple error
|
||||
_ => Err(FrontendErrorResponse::StatusCode(
|
||||
_ => Err(Web3ProxyError::StatusCode(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"this page is for rpcs".to_string(),
|
||||
None,
|
||||
@ -341,7 +338,7 @@ async fn handle_socket_payload(
|
||||
Ok(json_request) => {
|
||||
let id = json_request.id.clone();
|
||||
|
||||
let response: anyhow::Result<JsonRpcForwardedResponseEnum> = match &json_request.method
|
||||
let response: Web3ProxyResult<JsonRpcForwardedResponseEnum> = match &json_request.method
|
||||
[..]
|
||||
{
|
||||
"eth_subscribe" => {
|
||||
@ -378,7 +375,7 @@ async fn handle_socket_payload(
|
||||
// TODO: move this logic into the app?
|
||||
let request_bytes = json_request.num_bytes();
|
||||
|
||||
let request_metadata = Arc::new(RequestMetadata::new(request_bytes).unwrap());
|
||||
let request_metadata = Arc::new(RequestMetadata::new(request_bytes));
|
||||
|
||||
let subscription_id = json_request.params.unwrap().to_string();
|
||||
|
||||
@ -417,16 +414,7 @@ async fn handle_socket_payload(
|
||||
_ => app
|
||||
.proxy_web3_rpc(authorization.clone(), json_request.into())
|
||||
.await
|
||||
.map_or_else(
|
||||
|err| match err {
|
||||
FrontendErrorResponse::Anyhow(err) => Err(err),
|
||||
_ => {
|
||||
error!("handle this better! {:?}", err);
|
||||
Err(anyhow::anyhow!("unexpected error! {:?}", err))
|
||||
}
|
||||
},
|
||||
|(response, _)| Ok(response),
|
||||
),
|
||||
.map(|(response, _)| response),
|
||||
};
|
||||
|
||||
(id, response)
|
||||
@ -442,9 +430,8 @@ async fn handle_socket_payload(
|
||||
let response_str = match response {
|
||||
Ok(x) => serde_json::to_string(&x).expect("to_string should always work here"),
|
||||
Err(err) => {
|
||||
// we have an anyhow error. turn it into a response
|
||||
let response = JsonRpcForwardedResponse::from_anyhow_error(err, None, Some(id));
|
||||
|
||||
let (_, mut response) = err.into_response_parts();
|
||||
response.id = id;
|
||||
serde_json::to_string(&response).expect("to_string should always work here")
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Handle registration, logins, and managing account data.
|
||||
use super::authorization::{login_is_authorized, RpcSecretKey};
|
||||
use super::errors::FrontendResult;
|
||||
use super::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResponse};
|
||||
use crate::app::Web3ProxyApp;
|
||||
use crate::http_params::{
|
||||
get_chain_id_from_params, get_page_from_params, get_query_start_from_params,
|
||||
@ -9,7 +9,6 @@ use crate::stats::influxdb_queries::query_user_stats;
|
||||
use crate::stats::StatType;
|
||||
use crate::user_token::UserBearerToken;
|
||||
use crate::{PostLogin, PostLoginQuery};
|
||||
use anyhow::Context;
|
||||
use axum::headers::{Header, Origin, Referer, UserAgent};
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
@ -65,7 +64,7 @@ pub async fn user_login_get(
|
||||
InsecureClientIp(ip): InsecureClientIp,
|
||||
// TODO: what does axum's error handling look like if the path fails to parse?
|
||||
Path(mut params): Path<HashMap<String, String>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
login_is_authorized(&app, ip).await?;
|
||||
|
||||
// create a message and save it in redis
|
||||
@ -81,11 +80,9 @@ pub async fn user_login_get(
|
||||
// TODO: allow ENS names here?
|
||||
let user_address: Address = params
|
||||
.remove("user_address")
|
||||
// TODO: map_err so this becomes a 500. routing must be bad
|
||||
.context("impossible")?
|
||||
.ok_or(Web3ProxyError::BadRouting)?
|
||||
.parse()
|
||||
// TODO: map_err so this becomes a 401
|
||||
.context("unable to parse address")?;
|
||||
.or(Err(Web3ProxyError::ParseAddressError))?;
|
||||
|
||||
let login_domain = app
|
||||
.config
|
||||
@ -113,7 +110,7 @@ pub async fn user_login_get(
|
||||
resources: vec![],
|
||||
};
|
||||
|
||||
let db_conn = app.db_conn().context("login requires a database")?;
|
||||
let db_conn = app.db_conn().web3_context("login requires a database")?;
|
||||
|
||||
// massage types to fit in the database. sea-orm does not make this very elegant
|
||||
let uuid = Uuid::from_u128(nonce.into());
|
||||
@ -135,7 +132,7 @@ pub async fn user_login_get(
|
||||
user_pending_login
|
||||
.save(&db_conn)
|
||||
.await
|
||||
.context("saving user's pending_login")?;
|
||||
.web3_context("saving user's pending_login")?;
|
||||
|
||||
// there are multiple ways to sign messages and not all wallets support them
|
||||
// TODO: default message eip from config?
|
||||
@ -148,8 +145,7 @@ pub async fn user_login_get(
|
||||
"eip191_hash" => Bytes::from(&message.eip191_hash().unwrap()).to_string(),
|
||||
"eip4361" => message.to_string(),
|
||||
_ => {
|
||||
// TODO: custom error that is handled a 401
|
||||
return Err(anyhow::anyhow!("invalid message eip given").into());
|
||||
return Err(Web3ProxyError::InvalidEip);
|
||||
}
|
||||
};
|
||||
|
||||
@ -165,13 +161,13 @@ pub async fn user_login_post(
|
||||
InsecureClientIp(ip): InsecureClientIp,
|
||||
Query(query): Query<PostLoginQuery>,
|
||||
Json(payload): Json<PostLogin>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
login_is_authorized(&app, ip).await?;
|
||||
|
||||
// TODO: this seems too verbose. how can we simply convert a String into a [u8; 65]
|
||||
let their_sig_bytes = Bytes::from_str(&payload.sig).context("parsing sig")?;
|
||||
let their_sig_bytes = Bytes::from_str(&payload.sig).web3_context("parsing sig")?;
|
||||
if their_sig_bytes.len() != 65 {
|
||||
return Err(anyhow::anyhow!("checking signature length").into());
|
||||
return Err(Web3ProxyError::InvalidSignatureLength);
|
||||
}
|
||||
let mut their_sig: [u8; 65] = [0; 65];
|
||||
for x in 0..65 {
|
||||
@ -181,17 +177,18 @@ pub async fn user_login_post(
|
||||
// we can't trust that they didn't tamper with the message in some way. like some clients return it hex encoded
|
||||
// TODO: checking 0x seems fragile, but I think it will be fine. siwe message text shouldn't ever start with 0x
|
||||
let their_msg: Message = if payload.msg.starts_with("0x") {
|
||||
let their_msg_bytes = Bytes::from_str(&payload.msg).context("parsing payload message")?;
|
||||
let their_msg_bytes =
|
||||
Bytes::from_str(&payload.msg).web3_context("parsing payload message")?;
|
||||
|
||||
// TODO: lossy or no?
|
||||
String::from_utf8_lossy(their_msg_bytes.as_ref())
|
||||
.parse::<siwe::Message>()
|
||||
.context("parsing hex string message")?
|
||||
.web3_context("parsing hex string message")?
|
||||
} else {
|
||||
payload
|
||||
.msg
|
||||
.parse::<siwe::Message>()
|
||||
.context("parsing string message")?
|
||||
.web3_context("parsing string message")?
|
||||
};
|
||||
|
||||
// the only part of the message we will trust is their nonce
|
||||
@ -199,7 +196,9 @@ pub async fn user_login_post(
|
||||
let login_nonce = UserBearerToken::from_str(&their_msg.nonce)?;
|
||||
|
||||
// fetch the message we gave them from our database
|
||||
let db_replica = app.db_replica().context("Getting database connection")?;
|
||||
let db_replica = app
|
||||
.db_replica()
|
||||
.web3_context("Getting database connection")?;
|
||||
|
||||
// massage type for the db
|
||||
let login_nonce_uuid: Uuid = login_nonce.clone().into();
|
||||
@ -208,13 +207,13 @@ pub async fn user_login_post(
|
||||
.filter(pending_login::Column::Nonce.eq(login_nonce_uuid))
|
||||
.one(db_replica.conn())
|
||||
.await
|
||||
.context("database error while finding pending_login")?
|
||||
.context("login nonce not found")?;
|
||||
.web3_context("database error while finding pending_login")?
|
||||
.web3_context("login nonce not found")?;
|
||||
|
||||
let our_msg: siwe::Message = user_pending_login
|
||||
.message
|
||||
.parse()
|
||||
.context("parsing siwe message")?;
|
||||
.web3_context("parsing siwe message")?;
|
||||
|
||||
// default options are fine. the message includes timestamp and domain and nonce
|
||||
let verify_config = VerificationOpts::default();
|
||||
@ -223,16 +222,16 @@ pub async fn user_login_post(
|
||||
if let Err(err_1) = our_msg
|
||||
.verify(&their_sig, &verify_config)
|
||||
.await
|
||||
.context("verifying signature against our local message")
|
||||
.web3_context("verifying signature against our local message")
|
||||
{
|
||||
// verification method 1 failed. try eip191
|
||||
if let Err(err_191) = our_msg
|
||||
.verify_eip191(&their_sig)
|
||||
.context("verifying eip191 signature against our local message")
|
||||
.web3_context("verifying eip191 signature against our local message")
|
||||
{
|
||||
let db_conn = app
|
||||
.db_conn()
|
||||
.context("deleting expired pending logins requires a db")?;
|
||||
.web3_context("deleting expired pending logins requires a db")?;
|
||||
|
||||
// delete ALL expired rows.
|
||||
let now = Utc::now();
|
||||
@ -244,12 +243,10 @@ pub async fn user_login_post(
|
||||
// TODO: emit a stat? if this is high something weird might be happening
|
||||
debug!("cleared expired pending_logins: {:?}", delete_result);
|
||||
|
||||
return Err(anyhow::anyhow!(
|
||||
"both the primary and eip191 verification failed: {:#?}; {:#?}",
|
||||
err_1,
|
||||
err_191
|
||||
)
|
||||
.into());
|
||||
return Err(Web3ProxyError::EipVerificationFailed(
|
||||
Box::new(err_1),
|
||||
Box::new(err_191),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,7 +257,7 @@ pub async fn user_login_post(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let db_conn = app.db_conn().context("login requires a db")?;
|
||||
let db_conn = app.db_conn().web3_context("login requires a db")?;
|
||||
|
||||
let (u, uks, status_code) = match u {
|
||||
None => {
|
||||
@ -270,7 +267,7 @@ pub async fn user_login_post(
|
||||
// TODO: more advanced invite codes that set different request/minute and concurrency limits
|
||||
if let Some(invite_code) = &app.config.invite_code {
|
||||
if query.invite_code.as_ref() != Some(invite_code) {
|
||||
return Err(anyhow::anyhow!("checking invite_code").into());
|
||||
return Err(Web3ProxyError::InvalidInviteCode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,7 +297,7 @@ pub async fn user_login_post(
|
||||
let uk = uk
|
||||
.insert(&txn)
|
||||
.await
|
||||
.context("Failed saving new user key")?;
|
||||
.web3_context("Failed saving new user key")?;
|
||||
|
||||
let uks = vec![uk];
|
||||
|
||||
@ -315,7 +312,7 @@ pub async fn user_login_post(
|
||||
.filter(rpc_key::Column::UserId.eq(u.id))
|
||||
.all(db_replica.conn())
|
||||
.await
|
||||
.context("failed loading user's key")?;
|
||||
.web3_context("failed loading user's key")?;
|
||||
|
||||
(u, uks, StatusCode::OK)
|
||||
}
|
||||
@ -355,7 +352,7 @@ pub async fn user_login_post(
|
||||
user_login
|
||||
.save(&db_conn)
|
||||
.await
|
||||
.context("saving user login")?;
|
||||
.web3_context("saving user login")?;
|
||||
|
||||
if let Err(err) = user_pending_login
|
||||
.into_active_model()
|
||||
@ -373,10 +370,12 @@ pub async fn user_login_post(
|
||||
pub async fn user_logout_post(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let user_bearer = UserBearerToken::try_from(bearer)?;
|
||||
|
||||
let db_conn = app.db_conn().context("database needed for user logout")?;
|
||||
let db_conn = app
|
||||
.db_conn()
|
||||
.web3_context("database needed for user logout")?;
|
||||
|
||||
if let Err(err) = login::Entity::delete_many()
|
||||
.filter(login::Column::BearerToken.eq(user_bearer.uuid()))
|
||||
@ -417,7 +416,7 @@ pub async fn user_logout_post(
|
||||
pub async fn user_get(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer_token)): TypedHeader<Authorization<Bearer>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let (user, _semaphore) = app.bearer_is_authorized(bearer_token).await?;
|
||||
|
||||
Ok(Json(user).into_response())
|
||||
@ -435,7 +434,7 @@ pub async fn user_post(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer_token)): TypedHeader<Authorization<Bearer>>,
|
||||
Json(payload): Json<UserPost>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let (user, _semaphore) = app.bearer_is_authorized(bearer_token).await?;
|
||||
|
||||
let mut user: user::ActiveModel = user.into();
|
||||
@ -456,7 +455,7 @@ pub async fn user_post(
|
||||
// TODO: what else can we update here? password hash? subscription to newsletter?
|
||||
|
||||
let user = if user.is_changed() {
|
||||
let db_conn = app.db_conn().context("Getting database connection")?;
|
||||
let db_conn = app.db_conn().web3_context("Getting database connection")?;
|
||||
|
||||
user.save(&db_conn).await?
|
||||
} else {
|
||||
@ -464,7 +463,7 @@ pub async fn user_post(
|
||||
user
|
||||
};
|
||||
|
||||
let user: user::Model = user.try_into().context("Returning updated user")?;
|
||||
let user: user::Model = user.try_into().web3_context("Returning updated user")?;
|
||||
|
||||
Ok(Json(user).into_response())
|
||||
}
|
||||
@ -480,8 +479,8 @@ pub async fn user_post(
|
||||
pub async fn user_balance_get(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
|
||||
) -> FrontendResult {
|
||||
let (user, _semaphore) = app.bearer_is_authorized(bearer).await?;
|
||||
) -> Web3ProxyResponse {
|
||||
let (_user, _semaphore) = app.bearer_is_authorized(bearer).await?;
|
||||
|
||||
todo!("user_balance_get");
|
||||
}
|
||||
@ -495,8 +494,8 @@ pub async fn user_balance_get(
|
||||
pub async fn user_balance_post(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
|
||||
) -> FrontendResult {
|
||||
let (user, _semaphore) = app.bearer_is_authorized(bearer).await?;
|
||||
) -> Web3ProxyResponse {
|
||||
let (_user, _semaphore) = app.bearer_is_authorized(bearer).await?;
|
||||
|
||||
todo!("user_balance_post");
|
||||
}
|
||||
@ -506,18 +505,18 @@ pub async fn user_balance_post(
|
||||
pub async fn rpc_keys_get(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let (user, _semaphore) = app.bearer_is_authorized(bearer).await?;
|
||||
|
||||
let db_replica = app
|
||||
.db_replica()
|
||||
.context("db_replica is required to fetch a user's keys")?;
|
||||
.web3_context("db_replica is required to fetch a user's keys")?;
|
||||
|
||||
let uks = rpc_key::Entity::find()
|
||||
.filter(rpc_key::Column::UserId.eq(user.id))
|
||||
.all(db_replica.conn())
|
||||
.await
|
||||
.context("failed loading user's key")?;
|
||||
.web3_context("failed loading user's key")?;
|
||||
|
||||
let response_json = json!({
|
||||
"user_id": user.id,
|
||||
@ -535,11 +534,11 @@ pub async fn rpc_keys_get(
|
||||
pub async fn rpc_keys_delete(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
|
||||
) -> FrontendResult {
|
||||
let (user, _semaphore) = app.bearer_is_authorized(bearer).await?;
|
||||
) -> Web3ProxyResponse {
|
||||
let (_user, _semaphore) = app.bearer_is_authorized(bearer).await?;
|
||||
|
||||
// TODO: think about how cascading deletes and billing should work
|
||||
Err(anyhow::anyhow!("work in progress").into())
|
||||
Err(Web3ProxyError::NotImplemented)
|
||||
}
|
||||
|
||||
/// the JSON input to the `rpc_keys_management` handler.
|
||||
@ -567,12 +566,14 @@ pub async fn rpc_keys_management(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
|
||||
Json(payload): Json<UserKeyManagement>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
// TODO: is there a way we can know if this is a PUT or POST? right now we can modify or create keys with either. though that probably doesn't matter
|
||||
|
||||
let (user, _semaphore) = app.bearer_is_authorized(bearer).await?;
|
||||
|
||||
let db_replica = app.db_replica().context("getting db for user's keys")?;
|
||||
let db_replica = app
|
||||
.db_replica()
|
||||
.web3_context("getting db for user's keys")?;
|
||||
|
||||
let mut uk = if let Some(existing_key_id) = payload.key_id {
|
||||
// get the key and make sure it belongs to the user
|
||||
@ -581,8 +582,8 @@ pub async fn rpc_keys_management(
|
||||
.filter(rpc_key::Column::Id.eq(existing_key_id))
|
||||
.one(db_replica.conn())
|
||||
.await
|
||||
.context("failed loading user's key")?
|
||||
.context("key does not exist or is not controlled by this bearer token")?
|
||||
.web3_context("failed loading user's key")?
|
||||
.web3_context("key does not exist or is not controlled by this bearer token")?
|
||||
.into_active_model()
|
||||
} else {
|
||||
// make a new key
|
||||
@ -591,7 +592,7 @@ pub async fn rpc_keys_management(
|
||||
|
||||
let log_level = payload
|
||||
.log_level
|
||||
.context("log level must be 'none', 'detailed', or 'aggregated'")?;
|
||||
.web3_context("log level must be 'none', 'detailed', or 'aggregated'")?;
|
||||
|
||||
rpc_key::ActiveModel {
|
||||
user_id: sea_orm::Set(user.id),
|
||||
@ -720,9 +721,11 @@ pub async fn rpc_keys_management(
|
||||
}
|
||||
|
||||
let uk = if uk.is_changed() {
|
||||
let db_conn = app.db_conn().context("login requires a db")?;
|
||||
let db_conn = app.db_conn().web3_context("login requires a db")?;
|
||||
|
||||
uk.save(&db_conn).await.context("Failed saving user key")?
|
||||
uk.save(&db_conn)
|
||||
.await
|
||||
.web3_context("Failed saving user key")?
|
||||
} else {
|
||||
uk
|
||||
};
|
||||
@ -738,7 +741,7 @@ pub async fn user_revert_logs_get(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let (user, _semaphore) = app.bearer_is_authorized(bearer).await?;
|
||||
|
||||
let chain_id = get_chain_id_from_params(app.as_ref(), ¶ms)?;
|
||||
@ -757,13 +760,13 @@ pub async fn user_revert_logs_get(
|
||||
|
||||
let db_replica = app
|
||||
.db_replica()
|
||||
.context("getting replica db for user's revert logs")?;
|
||||
.web3_context("getting replica db for user's revert logs")?;
|
||||
|
||||
let uks = rpc_key::Entity::find()
|
||||
.filter(rpc_key::Column::UserId.eq(user.id))
|
||||
.all(db_replica.conn())
|
||||
.await
|
||||
.context("failed loading user's key")?;
|
||||
.web3_context("failed loading user's key")?;
|
||||
|
||||
// TODO: only select the ids
|
||||
let uks: Vec<_> = uks.into_iter().map(|x| x.id).collect();
|
||||
@ -808,7 +811,7 @@ pub async fn user_stats_aggregated_get(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
bearer: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let response = query_user_stats(&app, bearer, ¶ms, StatType::Aggregated).await?;
|
||||
|
||||
Ok(response)
|
||||
@ -828,7 +831,7 @@ pub async fn user_stats_detailed_get(
|
||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||
bearer: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> FrontendResult {
|
||||
) -> Web3ProxyResponse {
|
||||
let response = query_user_stats(&app, bearer, ¶ms, StatType::Detailed).await?;
|
||||
|
||||
Ok(response)
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::app::DatabaseReplica;
|
||||
use crate::frontend::errors::FrontendErrorResponse;
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyResult};
|
||||
use crate::{app::Web3ProxyApp, user_token::UserBearerToken};
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
@ -24,7 +24,7 @@ pub async fn get_user_id_from_params(
|
||||
// this is a long type. should we strip it down?
|
||||
bearer: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
params: &HashMap<String, String>,
|
||||
) -> Result<u64, FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<u64> {
|
||||
match (bearer, params.get("user_id")) {
|
||||
(Some(TypedHeader(Authorization(bearer))), Some(user_id)) => {
|
||||
// check for the bearer cache key
|
||||
@ -45,7 +45,7 @@ pub async fn get_user_id_from_params(
|
||||
.one(db_replica.conn())
|
||||
.await
|
||||
.context("database error while querying for user")?
|
||||
.ok_or(FrontendErrorResponse::AccessDenied)?;
|
||||
.ok_or(Web3ProxyError::AccessDenied)?;
|
||||
|
||||
// if expired, delete ALL expired logins
|
||||
let now = Utc::now();
|
||||
@ -60,7 +60,7 @@ pub async fn get_user_id_from_params(
|
||||
// TODO: emit a stat? if this is high something weird might be happening
|
||||
debug!("cleared expired logins: {:?}", delete_result);
|
||||
|
||||
return Err(FrontendErrorResponse::AccessDenied);
|
||||
return Err(Web3ProxyError::AccessDenied);
|
||||
}
|
||||
|
||||
save_to_redis = true;
|
||||
@ -76,7 +76,7 @@ pub async fn get_user_id_from_params(
|
||||
let user_id: u64 = user_id.parse().context("Parsing user_id param")?;
|
||||
|
||||
if bearer_user_id != user_id {
|
||||
return Err(FrontendErrorResponse::AccessDenied);
|
||||
return Err(Web3ProxyError::AccessDenied);
|
||||
}
|
||||
|
||||
if save_to_redis {
|
||||
@ -103,7 +103,7 @@ pub async fn get_user_id_from_params(
|
||||
// TODO: proper error code from a useful error code
|
||||
// TODO: maybe instead of this sharp edged warn, we have a config value?
|
||||
// TODO: check config for if we should deny or allow this
|
||||
Err(FrontendErrorResponse::AccessDenied)
|
||||
Err(Web3ProxyError::AccessDenied)
|
||||
// // TODO: make this a flag
|
||||
// warn!("allowing without auth during development!");
|
||||
// Ok(x.parse()?)
|
||||
@ -215,7 +215,7 @@ pub fn get_query_stop_from_params(
|
||||
|
||||
pub fn get_query_window_seconds_from_params(
|
||||
params: &HashMap<String, String>,
|
||||
) -> Result<u64, FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<u64> {
|
||||
params.get("query_window_seconds").map_or_else(
|
||||
|| {
|
||||
// no page in params. set default
|
||||
@ -225,15 +225,13 @@ pub fn get_query_window_seconds_from_params(
|
||||
// parse the given timestamp
|
||||
query_window_seconds.parse::<u64>().map_err(|err| {
|
||||
trace!("Unable to parse rpc_key_id: {:#?}", err);
|
||||
FrontendErrorResponse::BadRequest("Unable to parse rpc_key_id".to_string())
|
||||
Web3ProxyError::BadRequest("Unable to parse rpc_key_id".to_string())
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_stats_column_from_params(
|
||||
params: &HashMap<String, String>,
|
||||
) -> Result<&str, FrontendErrorResponse> {
|
||||
pub fn get_stats_column_from_params(params: &HashMap<String, String>) -> Web3ProxyResult<&str> {
|
||||
params.get("query_stats_column").map_or_else(
|
||||
|| Ok("frontend_requests"),
|
||||
|query_stats_column: &String| {
|
||||
@ -247,7 +245,7 @@ pub fn get_stats_column_from_params(
|
||||
| "sum_request_bytes"
|
||||
| "sum_response_bytes"
|
||||
| "sum_response_millis" => Ok(query_stats_column),
|
||||
_ => Err(FrontendErrorResponse::BadRequest(
|
||||
_ => Err(Web3ProxyError::BadRequest(
|
||||
"Unable to parse query_stats_column. It must be one of: \
|
||||
frontend_requests, \
|
||||
backend_requests, \
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::frontend::errors::Web3ProxyResult;
|
||||
use derive_more::From;
|
||||
use ethers::prelude::{HttpClientError, ProviderError, WsClientError};
|
||||
use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor};
|
||||
@ -240,7 +241,7 @@ impl JsonRpcForwardedResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_ethers_error(e: ProviderError, id: Box<RawValue>) -> anyhow::Result<Self> {
|
||||
pub fn from_ethers_error(e: ProviderError, id: Box<RawValue>) -> Web3ProxyResult<Self> {
|
||||
// TODO: move turning ClientError into json to a helper function?
|
||||
let code;
|
||||
let message: String;
|
||||
@ -280,7 +281,7 @@ impl JsonRpcForwardedResponse {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("unexpected ethers error!"));
|
||||
return Err(anyhow::anyhow!("unexpected ethers error!").into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -302,7 +303,7 @@ impl JsonRpcForwardedResponse {
|
||||
pub fn try_from_response_result(
|
||||
result: Result<Box<RawValue>, ProviderError>,
|
||||
id: Box<RawValue>,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> Web3ProxyResult<Self> {
|
||||
match result {
|
||||
Ok(response) => Ok(Self::from_response(response, id)),
|
||||
Err(e) => Self::from_ethers_error(e, id),
|
||||
|
@ -4,8 +4,8 @@ use super::many::Web3Rpcs;
|
||||
use super::one::Web3Rpc;
|
||||
use super::transactions::TxStatus;
|
||||
use crate::frontend::authorization::Authorization;
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResult};
|
||||
use crate::{config::BlockAndRpc, jsonrpc::JsonRpcRequest};
|
||||
use anyhow::{anyhow, Context};
|
||||
use derive_more::From;
|
||||
use ethers::prelude::{Block, TxHash, H256, U64};
|
||||
use log::{debug, trace, warn, Level};
|
||||
@ -132,11 +132,11 @@ impl Web3ProxyBlock {
|
||||
}
|
||||
|
||||
impl TryFrom<ArcBlock> for Web3ProxyBlock {
|
||||
type Error = anyhow::Error;
|
||||
type Error = Web3ProxyError;
|
||||
|
||||
fn try_from(x: ArcBlock) -> Result<Self, Self::Error> {
|
||||
if x.number.is_none() || x.hash.is_none() {
|
||||
return Err(anyhow!("Blocks here must have a number of hash"));
|
||||
return Err(Web3ProxyError::NoBlockNumberOrHash);
|
||||
}
|
||||
|
||||
let b = Web3ProxyBlock {
|
||||
@ -166,7 +166,7 @@ impl Web3Rpcs {
|
||||
&self,
|
||||
block: Web3ProxyBlock,
|
||||
heaviest_chain: bool,
|
||||
) -> anyhow::Result<Web3ProxyBlock> {
|
||||
) -> Web3ProxyResult<Web3ProxyBlock> {
|
||||
// TODO: i think we can rearrange this function to make it faster on the hot path
|
||||
let block_hash = block.hash();
|
||||
|
||||
@ -198,13 +198,13 @@ impl Web3Rpcs {
|
||||
|
||||
/// Get a block from caches with fallback.
|
||||
/// Will query a specific node or the best available.
|
||||
/// TODO: return anyhow::Result<Option<ArcBlock>>?
|
||||
/// TODO: return Web3ProxyResult<Option<ArcBlock>>?
|
||||
pub async fn block(
|
||||
&self,
|
||||
authorization: &Arc<Authorization>,
|
||||
hash: &H256,
|
||||
rpc: Option<&Arc<Web3Rpc>>,
|
||||
) -> anyhow::Result<Web3ProxyBlock> {
|
||||
) -> Web3ProxyResult<Web3ProxyBlock> {
|
||||
// first, try to get the hash from our cache
|
||||
// the cache is set last, so if its here, its everywhere
|
||||
// TODO: use try_get_with
|
||||
@ -233,7 +233,7 @@ impl Web3Rpcs {
|
||||
x.try_into().ok()
|
||||
}
|
||||
})
|
||||
.context("no block!")?,
|
||||
.web3_context("no block!")?,
|
||||
None => {
|
||||
// TODO: helper for method+params => JsonRpcRequest
|
||||
// TODO: does this id matter?
|
||||
@ -253,16 +253,16 @@ impl Web3Rpcs {
|
||||
.await?;
|
||||
|
||||
if let Some(err) = response.error {
|
||||
let err = anyhow::anyhow!("{:#?}", err);
|
||||
|
||||
return Err(err.context("failed fetching block"));
|
||||
return Err(err).web3_context("failed fetching block");
|
||||
}
|
||||
|
||||
let block = response.result.context("no error, but also no block")?;
|
||||
let block = response
|
||||
.result
|
||||
.web3_context("no error, but also no block")?;
|
||||
|
||||
let block: Option<ArcBlock> = serde_json::from_str(block.get())?;
|
||||
|
||||
let block: ArcBlock = block.context("no block in the response")?;
|
||||
let block: ArcBlock = block.web3_context("no block in the response")?;
|
||||
|
||||
// TODO: received time is going to be weird
|
||||
Web3ProxyBlock::try_from(block)?
|
||||
@ -281,7 +281,7 @@ impl Web3Rpcs {
|
||||
&self,
|
||||
authorization: &Arc<Authorization>,
|
||||
num: &U64,
|
||||
) -> anyhow::Result<(H256, u64)> {
|
||||
) -> Web3ProxyResult<(H256, u64)> {
|
||||
let (block, block_depth) = self.cannonical_block(authorization, num).await?;
|
||||
|
||||
let hash = *block.hash();
|
||||
@ -295,7 +295,7 @@ impl Web3Rpcs {
|
||||
&self,
|
||||
authorization: &Arc<Authorization>,
|
||||
num: &U64,
|
||||
) -> anyhow::Result<(Web3ProxyBlock, u64)> {
|
||||
) -> Web3ProxyResult<(Web3ProxyBlock, u64)> {
|
||||
// we only have blocks by hash now
|
||||
// maybe save them during save_block in a blocks_by_number Cache<U64, Vec<ArcBlock>>
|
||||
// if theres multiple, use petgraph to find the one on the main chain (and remove the others if they have enough confirmations)
|
||||
@ -303,7 +303,7 @@ impl Web3Rpcs {
|
||||
let mut consensus_head_receiver = self
|
||||
.watch_consensus_head_sender
|
||||
.as_ref()
|
||||
.context("need new head subscriptions to fetch cannonical_block")?
|
||||
.web3_context("need new head subscriptions to fetch cannonical_block")?
|
||||
.subscribe();
|
||||
|
||||
// be sure the requested block num exists
|
||||
@ -311,7 +311,7 @@ impl Web3Rpcs {
|
||||
let mut head_block_num = *consensus_head_receiver
|
||||
.borrow_and_update()
|
||||
.as_ref()
|
||||
.context("no consensus head block")?
|
||||
.web3_context("no consensus head block")?
|
||||
.number();
|
||||
|
||||
loop {
|
||||
@ -352,7 +352,7 @@ impl Web3Rpcs {
|
||||
debug!("could not find canonical block {}: {:?}", num, err);
|
||||
}
|
||||
|
||||
let raw_block = response.result.context("no cannonical block result")?;
|
||||
let raw_block = response.result.web3_context("no cannonical block result")?;
|
||||
|
||||
let block: ArcBlock = serde_json::from_str(raw_block.get())?;
|
||||
|
||||
@ -412,13 +412,13 @@ impl Web3Rpcs {
|
||||
consensus_finder: &mut ConsensusFinder,
|
||||
new_block: Option<Web3ProxyBlock>,
|
||||
rpc: Arc<Web3Rpc>,
|
||||
pending_tx_sender: &Option<broadcast::Sender<TxStatus>>,
|
||||
) -> anyhow::Result<()> {
|
||||
_pending_tx_sender: &Option<broadcast::Sender<TxStatus>>,
|
||||
) -> Web3ProxyResult<()> {
|
||||
// TODO: how should we handle an error here?
|
||||
if !consensus_finder
|
||||
.update_rpc(new_block.clone(), rpc.clone(), self)
|
||||
.await
|
||||
.context("failed to update rpc")?
|
||||
.web3_context("failed to update rpc")?
|
||||
{
|
||||
// nothing changed. no need to scan for a new consensus head
|
||||
return Ok(());
|
||||
@ -429,12 +429,10 @@ impl Web3Rpcs {
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
let err = err.context("error while finding consensus head block!");
|
||||
|
||||
return Err(err);
|
||||
return Err(err).web3_context("error while finding consensus head block!");
|
||||
}
|
||||
Ok(None) => {
|
||||
return Err(anyhow!("no consensus head block!"));
|
||||
return Err(Web3ProxyError::NoConsensusHeadBlock);
|
||||
}
|
||||
Ok(Some(x)) => x,
|
||||
};
|
||||
@ -479,7 +477,8 @@ impl Web3Rpcs {
|
||||
|
||||
watch_consensus_head_sender
|
||||
.send(Some(consensus_head_block))
|
||||
.context(
|
||||
.or(Err(Web3ProxyError::WatchSendError))
|
||||
.web3_context(
|
||||
"watch_consensus_head_sender failed sending first consensus_head_block",
|
||||
)?;
|
||||
}
|
||||
@ -529,11 +528,12 @@ impl Web3Rpcs {
|
||||
let consensus_head_block = self
|
||||
.try_cache_block(consensus_head_block, true)
|
||||
.await
|
||||
.context("save consensus_head_block as heaviest chain")?;
|
||||
.web3_context("save consensus_head_block as heaviest chain")?;
|
||||
|
||||
watch_consensus_head_sender
|
||||
.send(Some(consensus_head_block))
|
||||
.context("watch_consensus_head_sender failed sending uncled consensus_head_block")?;
|
||||
.or(Err(Web3ProxyError::WatchSendError))
|
||||
.web3_context("watch_consensus_head_sender failed sending uncled consensus_head_block")?;
|
||||
}
|
||||
}
|
||||
Ordering::Less => {
|
||||
@ -562,11 +562,14 @@ impl Web3Rpcs {
|
||||
let consensus_head_block = self
|
||||
.try_cache_block(consensus_head_block, true)
|
||||
.await
|
||||
.context("save_block sending consensus_head_block as heaviest chain")?;
|
||||
.web3_context(
|
||||
"save_block sending consensus_head_block as heaviest chain",
|
||||
)?;
|
||||
|
||||
watch_consensus_head_sender
|
||||
.send(Some(consensus_head_block))
|
||||
.context("watch_consensus_head_sender failed sending rollback consensus_head_block")?;
|
||||
.or(Err(Web3ProxyError::WatchSendError))
|
||||
.web3_context("watch_consensus_head_sender failed sending rollback consensus_head_block")?;
|
||||
}
|
||||
Ordering::Greater => {
|
||||
debug!(
|
||||
@ -590,7 +593,9 @@ impl Web3Rpcs {
|
||||
let consensus_head_block =
|
||||
self.try_cache_block(consensus_head_block, true).await?;
|
||||
|
||||
watch_consensus_head_sender.send(Some(consensus_head_block)).context("watch_consensus_head_sender failed sending new consensus_head_block")?;
|
||||
watch_consensus_head_sender.send(Some(consensus_head_block))
|
||||
.or(Err(Web3ProxyError::WatchSendError))
|
||||
.web3_context("watch_consensus_head_sender failed sending new consensus_head_block")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use super::blockchain::Web3ProxyBlock;
|
||||
use super::many::Web3Rpcs;
|
||||
use super::one::Web3Rpc;
|
||||
use crate::frontend::authorization::Authorization;
|
||||
use anyhow::Context;
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResult};
|
||||
use ethers::prelude::{H256, U64};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use itertools::{Itertools, MinMaxResult};
|
||||
@ -156,7 +156,7 @@ impl ConsensusFinder {
|
||||
rpc: Arc<Web3Rpc>,
|
||||
// we need this so we can save the block to caches. i don't like it though. maybe we should use a lazy_static Cache wrapper that has a "save_block" method?. i generally dislike globals but i also dislike all the types having to pass eachother around
|
||||
web3_connections: &Web3Rpcs,
|
||||
) -> anyhow::Result<bool> {
|
||||
) -> Web3ProxyResult<bool> {
|
||||
// add the rpc's block to connection_heads, or remove the rpc from connection_heads
|
||||
let changed = match rpc_head_block {
|
||||
Some(mut rpc_head_block) => {
|
||||
@ -164,7 +164,7 @@ impl ConsensusFinder {
|
||||
rpc_head_block = web3_connections
|
||||
.try_cache_block(rpc_head_block, false)
|
||||
.await
|
||||
.context("failed caching block")?;
|
||||
.web3_context("failed caching block")?;
|
||||
|
||||
// if let Some(max_block_lag) = max_block_lag {
|
||||
// if rpc_head_block.number() < ??? {
|
||||
@ -203,7 +203,7 @@ impl ConsensusFinder {
|
||||
&mut self,
|
||||
authorization: &Arc<Authorization>,
|
||||
web3_rpcs: &Web3Rpcs,
|
||||
) -> anyhow::Result<Option<ConsensusWeb3Rpcs>> {
|
||||
) -> Web3ProxyResult<Option<ConsensusWeb3Rpcs>> {
|
||||
let minmax_block = self.rpc_heads.values().minmax_by_key(|&x| x.number());
|
||||
|
||||
let (lowest_block, highest_block) = match minmax_block {
|
||||
|
@ -7,6 +7,7 @@ use crate::app::{flatten_handle, AnyhowJoinHandle, Web3ProxyApp};
|
||||
///! Load balanced communication with a group of web3 providers
|
||||
use crate::config::{BlockAndRpc, TxHashAndRpc, Web3RpcConfig};
|
||||
use crate::frontend::authorization::{Authorization, RequestMetadata};
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyResult};
|
||||
use crate::frontend::rpc_proxy_ws::ProxyMode;
|
||||
use crate::jsonrpc::{JsonRpcForwardedResponse, JsonRpcRequest};
|
||||
use crate::rpcs::transactions::TxStatus;
|
||||
@ -422,7 +423,7 @@ impl Web3Rpcs {
|
||||
params: Option<&serde_json::Value>,
|
||||
error_level: Level,
|
||||
// TODO: remove this box once i figure out how to do the options
|
||||
) -> anyhow::Result<JsonRpcForwardedResponse> {
|
||||
) -> Web3ProxyResult<JsonRpcForwardedResponse> {
|
||||
// TODO: if only 1 active_request_handles, do self.try_send_request?
|
||||
|
||||
let responses = active_request_handles
|
||||
@ -500,7 +501,7 @@ impl Web3Rpcs {
|
||||
// TODO: if we are checking for the consensus head, i don' think we need min_block_needed/max_block_needed
|
||||
min_block_needed: Option<&U64>,
|
||||
max_block_needed: Option<&U64>,
|
||||
) -> anyhow::Result<OpenRequestResult> {
|
||||
) -> Web3ProxyResult<OpenRequestResult> {
|
||||
let usable_rpcs_by_tier_and_head_number: BTreeMap<(u64, Option<U64>), Vec<Arc<Web3Rpc>>> = {
|
||||
let synced_connections = self.watch_consensus_rpcs_sender.borrow().clone();
|
||||
|
||||
@ -529,11 +530,10 @@ impl Web3Rpcs {
|
||||
cmp::Ordering::Greater => {
|
||||
// TODO: force a debug log of the original request to see if our logic is wrong?
|
||||
// TODO: attach the rpc_key_id so we can find the user to ask if they need help
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid blocks bounds requested. min ({}) > max ({})",
|
||||
min_block_needed,
|
||||
max_block_needed
|
||||
));
|
||||
return Err(Web3ProxyError::InvalidBlockBounds {
|
||||
min: min_block_needed.as_u64(),
|
||||
max: max_block_needed.as_u64(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -837,7 +837,7 @@ impl Web3Rpcs {
|
||||
request_metadata: Option<&Arc<RequestMetadata>>,
|
||||
min_block_needed: Option<&U64>,
|
||||
max_block_needed: Option<&U64>,
|
||||
) -> anyhow::Result<JsonRpcForwardedResponse> {
|
||||
) -> Web3ProxyResult<JsonRpcForwardedResponse> {
|
||||
let mut skip_rpcs = vec![];
|
||||
let mut method_not_available_response = None;
|
||||
|
||||
@ -1065,7 +1065,7 @@ impl Web3Rpcs {
|
||||
error_level: Level,
|
||||
max_count: Option<usize>,
|
||||
always_include_backups: bool,
|
||||
) -> anyhow::Result<JsonRpcForwardedResponse> {
|
||||
) -> Web3ProxyResult<JsonRpcForwardedResponse> {
|
||||
let mut watch_consensus_rpcs = self.watch_consensus_rpcs_sender.subscribe();
|
||||
|
||||
loop {
|
||||
@ -1161,7 +1161,7 @@ impl Web3Rpcs {
|
||||
request_metadata: Option<&Arc<RequestMetadata>>,
|
||||
min_block_needed: Option<&U64>,
|
||||
max_block_needed: Option<&U64>,
|
||||
) -> anyhow::Result<JsonRpcForwardedResponse> {
|
||||
) -> Web3ProxyResult<JsonRpcForwardedResponse> {
|
||||
match authorization.checks.proxy_mode {
|
||||
ProxyMode::Debug | ProxyMode::Best => {
|
||||
self.try_send_best_consensus_head_connection(
|
||||
@ -1173,7 +1173,7 @@ impl Web3Rpcs {
|
||||
)
|
||||
.await
|
||||
}
|
||||
ProxyMode::Fastest(x) => todo!("Fastest"),
|
||||
ProxyMode::Fastest(_x) => todo!("Fastest"),
|
||||
ProxyMode::Versus => todo!("Versus"),
|
||||
}
|
||||
}
|
||||
@ -1676,7 +1676,7 @@ mod tests {
|
||||
OpenRequestResult::Handle(_)
|
||||
));
|
||||
|
||||
let best_available_server_from_none = rpcs
|
||||
let _best_available_server_from_none = rpcs
|
||||
.best_available_rpc(&authorization, None, &[], None, None)
|
||||
.await;
|
||||
|
||||
|
@ -5,6 +5,7 @@ use super::request::{OpenRequestHandle, OpenRequestResult};
|
||||
use crate::app::{flatten_handle, AnyhowJoinHandle};
|
||||
use crate::config::{BlockAndRpc, Web3RpcConfig};
|
||||
use crate::frontend::authorization::Authorization;
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyResult};
|
||||
use crate::rpcs::request::RequestRevertHandler;
|
||||
use anyhow::{anyhow, Context};
|
||||
use ethers::prelude::{Bytes, Middleware, ProviderError, TxHash, H256, U64};
|
||||
@ -1129,7 +1130,7 @@ impl Web3Rpc {
|
||||
trace!("watching pending transactions on {}", self);
|
||||
// TODO: does this keep the lock open for too long?
|
||||
match provider.as_ref() {
|
||||
Web3Provider::Http(provider) => {
|
||||
Web3Provider::Http(_provider) => {
|
||||
// there is a "watch_pending_transactions" function, but a lot of public nodes do not support the necessary rpc endpoints
|
||||
self.wait_for_disconnect().await?;
|
||||
}
|
||||
@ -1184,7 +1185,7 @@ impl Web3Rpc {
|
||||
authorization: &'a Arc<Authorization>,
|
||||
max_wait: Option<Duration>,
|
||||
unlocked_provider: Option<Arc<Web3Provider>>,
|
||||
) -> anyhow::Result<OpenRequestHandle> {
|
||||
) -> Web3ProxyResult<OpenRequestHandle> {
|
||||
let max_wait = max_wait.map(|x| Instant::now() + x);
|
||||
|
||||
loop {
|
||||
@ -1206,8 +1207,7 @@ impl Web3Rpc {
|
||||
if let Some(max_wait) = max_wait {
|
||||
if retry_at > max_wait {
|
||||
// break now since we will wait past our maximum wait time
|
||||
// TODO: don't use anyhow. use specific error type
|
||||
return Err(anyhow::anyhow!("timeout waiting for request handle"));
|
||||
return Err(Web3ProxyError::Timeout(None));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1221,7 +1221,7 @@ impl Web3Rpc {
|
||||
let now = Instant::now();
|
||||
|
||||
if now > max_wait {
|
||||
return Err(anyhow::anyhow!("unable to retry for request handle"));
|
||||
return Err(Web3ProxyError::NoHandleReady);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1239,7 +1239,7 @@ impl Web3Rpc {
|
||||
authorization: &Arc<Authorization>,
|
||||
// TODO: borrow on this instead of needing to clone the Arc?
|
||||
unlocked_provider: Option<Arc<Web3Provider>>,
|
||||
) -> anyhow::Result<OpenRequestResult> {
|
||||
) -> Web3ProxyResult<OpenRequestResult> {
|
||||
// TODO: think more about this read block
|
||||
// TODO: this should *not* be new_head_client. this should be a separate object
|
||||
if unlocked_provider.is_some() || self.provider.read().await.is_some() {
|
||||
|
@ -1,12 +1,12 @@
|
||||
use super::StatType;
|
||||
use crate::app::Web3ProxyApp;
|
||||
use crate::frontend::errors::FrontendErrorResponse;
|
||||
use crate::frontend::errors::{Web3ProxyError, Web3ProxyResponse, Web3ProxyResult};
|
||||
use crate::http_params::{
|
||||
get_chain_id_from_params, get_page_from_params, get_query_start_from_params,
|
||||
get_query_window_seconds_from_params, get_user_id_from_params,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::Json;
|
||||
use axum::{
|
||||
headers::{authorization::Bearer, Authorization},
|
||||
@ -28,7 +28,7 @@ pub fn filter_query_window_seconds(
|
||||
query_window_seconds: u64,
|
||||
response: &mut HashMap<&str, serde_json::Value>,
|
||||
q: Select<rpc_accounting::Entity>,
|
||||
) -> Result<Select<rpc_accounting::Entity>, FrontendErrorResponse> {
|
||||
) -> Web3ProxyResult<Select<rpc_accounting::Entity>> {
|
||||
if query_window_seconds == 0 {
|
||||
// TODO: order by more than this?
|
||||
// query_window_seconds is not set so we aggregate all records
|
||||
@ -62,7 +62,7 @@ pub async fn query_user_stats<'a>(
|
||||
bearer: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
params: &'a HashMap<String, String>,
|
||||
stat_response_type: StatType,
|
||||
) -> Result<Response, FrontendErrorResponse> {
|
||||
) -> Web3ProxyResponse {
|
||||
let db_conn = app.db_conn().context("query_user_stats needs a db")?;
|
||||
let db_replica = app
|
||||
.db_replica()
|
||||
@ -209,7 +209,7 @@ pub async fn query_user_stats<'a>(
|
||||
// TODO: move getting the param and checking the bearer token into a helper function
|
||||
if let Some(rpc_key_id) = params.get("rpc_key_id") {
|
||||
let rpc_key_id = rpc_key_id.parse::<u64>().map_err(|e| {
|
||||
FrontendErrorResponse::StatusCode(
|
||||
Web3ProxyError::StatusCode(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Unable to parse rpc_key_id".to_string(),
|
||||
Some(e.into()),
|
||||
|
@ -2,7 +2,7 @@ use super::StatType;
|
||||
use crate::http_params::get_stats_column_from_params;
|
||||
use crate::{
|
||||
app::Web3ProxyApp,
|
||||
frontend::errors::FrontendErrorResponse,
|
||||
frontend::errors::{Web3ProxyError, Web3ProxyResponse},
|
||||
http_params::{
|
||||
get_chain_id_from_params, get_query_start_from_params, get_query_stop_from_params,
|
||||
get_query_window_seconds_from_params, get_user_id_from_params,
|
||||
@ -11,7 +11,7 @@ use crate::{
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
headers::{authorization::Bearer, Authorization},
|
||||
response::{IntoResponse, Response},
|
||||
response::IntoResponse,
|
||||
Json, TypedHeader,
|
||||
};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
@ -60,7 +60,7 @@ pub async fn query_user_stats<'a>(
|
||||
bearer: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
params: &'a HashMap<String, String>,
|
||||
stat_response_type: StatType,
|
||||
) -> Result<Response, FrontendErrorResponse> {
|
||||
) -> Web3ProxyResponse {
|
||||
info!("Got this far 1");
|
||||
let db_conn = app.db_conn().context("query_user_stats needs a db")?;
|
||||
let db_replica = app
|
||||
@ -96,7 +96,7 @@ pub async fn query_user_stats<'a>(
|
||||
|
||||
// Return a bad request if query_start == query_stop, because then the query is empty basically
|
||||
if query_start == query_stop {
|
||||
return Err(FrontendErrorResponse::BadRequest(
|
||||
return Err(Web3ProxyError::BadRequest(
|
||||
"Start and Stop date cannot be equal. Please specify a (different) start date."
|
||||
.to_owned(),
|
||||
));
|
||||
@ -462,9 +462,9 @@ pub async fn query_user_stats<'a>(
|
||||
|
||||
// Also optionally add the rpc_key_id:
|
||||
if let Some(rpc_key_id) = params.get("rpc_key_id") {
|
||||
let rpc_key_id = rpc_key_id.parse::<u64>().map_err(|e| {
|
||||
FrontendErrorResponse::BadRequest("Unable to parse rpc_key_id".to_string())
|
||||
})?;
|
||||
let rpc_key_id = rpc_key_id
|
||||
.parse::<u64>()
|
||||
.map_err(|e| Web3ProxyError::BadRequest("Unable to parse rpc_key_id".to_string()))?;
|
||||
response_body.insert("rpc_key_id", serde_json::Value::Number(rpc_key_id.into()));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user