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