pass name through and use pub more
This commit is contained in:
parent
21055799f3
commit
77a589a96d
1
TODO.md
1
TODO.md
@ -66,6 +66,7 @@
|
|||||||
- [x] Add a "weight" key to the servers. Sort on that after block. keep most requests local
|
- [x] Add a "weight" key to the servers. Sort on that after block. keep most requests local
|
||||||
- [x] cache db query results for user data. db is a big bottleneck right now
|
- [x] cache db query results for user data. db is a big bottleneck right now
|
||||||
- [x] allow blocking public requests
|
- [x] allow blocking public requests
|
||||||
|
- [ ] cache more things locally or in redis
|
||||||
- [ ] use siwe messages and signatures for sign up and login
|
- [ ] use siwe messages and signatures for sign up and login
|
||||||
- [ ] basic request method stats
|
- [ ] basic request method stats
|
||||||
|
|
||||||
|
@ -58,6 +58,21 @@ type ActiveRequestsMap = DashMap<CacheKey, watch::Receiver<bool>>;
|
|||||||
|
|
||||||
pub type AnyhowJoinHandle<T> = JoinHandle<anyhow::Result<T>>;
|
pub type AnyhowJoinHandle<T> = JoinHandle<anyhow::Result<T>>;
|
||||||
|
|
||||||
|
// TODO: think more about TxState
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum TxState {
|
||||||
|
Pending(Transaction),
|
||||||
|
Confirmed(Transaction),
|
||||||
|
Orphaned(Transaction),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, From)]
|
||||||
|
pub struct UserCacheValue {
|
||||||
|
pub expires_at: Instant,
|
||||||
|
pub user_id: i64,
|
||||||
|
pub user_count_per_period: u32,
|
||||||
|
}
|
||||||
|
|
||||||
/// flatten a JoinError into an anyhow error
|
/// flatten a JoinError into an anyhow error
|
||||||
/// Useful when joining multiple futures.
|
/// Useful when joining multiple futures.
|
||||||
pub async fn flatten_handle<T>(handle: AnyhowJoinHandle<T>) -> anyhow::Result<T> {
|
pub async fn flatten_handle<T>(handle: AnyhowJoinHandle<T>) -> anyhow::Result<T> {
|
||||||
@ -109,42 +124,26 @@ pub async fn get_migrated_db(
|
|||||||
Ok(db)
|
Ok(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: think more about TxState
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum TxState {
|
|
||||||
Pending(Transaction),
|
|
||||||
Confirmed(Transaction),
|
|
||||||
Orphaned(Transaction),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, From)]
|
|
||||||
pub struct UserCacheValue {
|
|
||||||
pub expires_at: Instant,
|
|
||||||
pub user_id: i64,
|
|
||||||
pub user_count_per_period: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The application
|
/// The application
|
||||||
// TODO: this debug impl is way too verbose. make something smaller
|
// TODO: this debug impl is way too verbose. make something smaller
|
||||||
// TODO: if Web3ProxyApp is always in an Arc, i think we can avoid having at least some of these internal things in arcs
|
|
||||||
// TODO: i'm sure this is more arcs than necessary, but spawning futures makes references hard
|
// TODO: i'm sure this is more arcs than necessary, but spawning futures makes references hard
|
||||||
pub struct Web3ProxyApp {
|
pub struct Web3ProxyApp {
|
||||||
/// Send requests to the best server available
|
/// Send requests to the best server available
|
||||||
balanced_rpcs: Arc<Web3Connections>,
|
pub balanced_rpcs: Arc<Web3Connections>,
|
||||||
/// Send private requests (like eth_sendRawTransaction) to all these servers
|
/// Send private requests (like eth_sendRawTransaction) to all these servers
|
||||||
private_rpcs: Arc<Web3Connections>,
|
pub private_rpcs: Arc<Web3Connections>,
|
||||||
/// Track active requests so that we don't send the same query to multiple backends
|
/// Track active requests so that we don't send the same query to multiple backends
|
||||||
active_requests: ActiveRequestsMap,
|
pub active_requests: ActiveRequestsMap,
|
||||||
response_cache: ResponseLrcCache,
|
response_cache: ResponseLrcCache,
|
||||||
// don't drop this or the sender will stop working
|
// don't drop this or the sender will stop working
|
||||||
// TODO: broadcast channel instead?
|
// TODO: broadcast channel instead?
|
||||||
head_block_receiver: watch::Receiver<Arc<Block<TxHash>>>,
|
head_block_receiver: watch::Receiver<Arc<Block<TxHash>>>,
|
||||||
pending_tx_sender: broadcast::Sender<TxState>,
|
pending_tx_sender: broadcast::Sender<TxState>,
|
||||||
pending_transactions: Arc<DashMap<TxHash, TxState>>,
|
pub pending_transactions: Arc<DashMap<TxHash, TxState>>,
|
||||||
user_cache: RwLock<FifoCountMap<Uuid, UserCacheValue>>,
|
pub user_cache: RwLock<FifoCountMap<Uuid, UserCacheValue>>,
|
||||||
redis_pool: Option<RedisPool>,
|
pub redis_pool: Option<RedisPool>,
|
||||||
rate_limiter: Option<RedisCell>,
|
pub rate_limiter: Option<RedisCell>,
|
||||||
db_conn: Option<sea_orm::DatabaseConnection>,
|
pub db_conn: Option<sea_orm::DatabaseConnection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Web3ProxyApp {
|
impl fmt::Debug for Web3ProxyApp {
|
||||||
@ -155,26 +154,6 @@ impl fmt::Debug for Web3ProxyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Web3ProxyApp {
|
impl Web3ProxyApp {
|
||||||
pub fn db_conn(&self) -> Option<&sea_orm::DatabaseConnection> {
|
|
||||||
self.db_conn.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pending_transactions(&self) -> &DashMap<TxHash, TxState> {
|
|
||||||
&self.pending_transactions
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rate_limiter(&self) -> Option<&RedisCell> {
|
|
||||||
self.rate_limiter.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redis_pool(&self) -> Option<&RedisPool> {
|
|
||||||
self.redis_pool.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn user_cache(&self) -> &RwLock<FifoCountMap<Uuid, UserCacheValue>> {
|
|
||||||
&self.user_cache
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should we just take the rpc config as the only arg instead?
|
// TODO: should we just take the rpc config as the only arg instead?
|
||||||
pub async fn spawn(
|
pub async fn spawn(
|
||||||
app_config: AppConfig,
|
app_config: AppConfig,
|
||||||
@ -195,12 +174,12 @@ impl Web3ProxyApp {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let balanced_rpcs = app_config.balanced_rpcs.into_values().collect();
|
let balanced_rpcs = app_config.balanced_rpcs;
|
||||||
|
|
||||||
let private_rpcs = if let Some(private_rpcs) = app_config.private_rpcs {
|
let private_rpcs = if let Some(private_rpcs) = app_config.private_rpcs {
|
||||||
private_rpcs.into_values().collect()
|
private_rpcs
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: try_join_all instead?
|
// TODO: try_join_all instead?
|
||||||
@ -523,18 +502,6 @@ impl Web3ProxyApp {
|
|||||||
Ok((subscription_abort_handle, response))
|
Ok((subscription_abort_handle, response))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn balanced_rpcs(&self) -> &Web3Connections {
|
|
||||||
&self.balanced_rpcs
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn private_rpcs(&self) -> &Web3Connections {
|
|
||||||
&self.private_rpcs
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_requests(&self) -> &ActiveRequestsMap {
|
|
||||||
&self.active_requests
|
|
||||||
}
|
|
||||||
|
|
||||||
/// send the request or batch of requests to the approriate RPCs
|
/// send the request or batch of requests to the approriate RPCs
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn proxy_web3_rpc(
|
pub async fn proxy_web3_rpc(
|
||||||
|
@ -68,8 +68,10 @@ pub struct Web3ConnectionConfig {
|
|||||||
impl Web3ConnectionConfig {
|
impl Web3ConnectionConfig {
|
||||||
/// Create a Web3Connection from config
|
/// Create a Web3Connection from config
|
||||||
// #[instrument(name = "try_build_Web3ConnectionConfig", skip_all)]
|
// #[instrument(name = "try_build_Web3ConnectionConfig", skip_all)]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn spawn(
|
pub async fn spawn(
|
||||||
self,
|
self,
|
||||||
|
name: String,
|
||||||
redis_client_pool: Option<redis_cell_client::RedisPool>,
|
redis_client_pool: Option<redis_cell_client::RedisPool>,
|
||||||
chain_id: u64,
|
chain_id: u64,
|
||||||
http_client: Option<reqwest::Client>,
|
http_client: Option<reqwest::Client>,
|
||||||
@ -89,6 +91,7 @@ impl Web3ConnectionConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Web3Connection::spawn(
|
Web3Connection::spawn(
|
||||||
|
name,
|
||||||
chain_id,
|
chain_id,
|
||||||
self.url,
|
self.url,
|
||||||
http_client,
|
http_client,
|
||||||
|
@ -73,8 +73,9 @@ impl fmt::Debug for Web3Provider {
|
|||||||
|
|
||||||
/// An active connection to a Web3Rpc
|
/// An active connection to a Web3Rpc
|
||||||
pub struct Web3Connection {
|
pub struct Web3Connection {
|
||||||
|
name: String,
|
||||||
/// TODO: can we get this from the provider? do we even need it?
|
/// TODO: can we get this from the provider? do we even need it?
|
||||||
url: String,
|
pub url: String,
|
||||||
/// keep track of currently open requests. We sort on this
|
/// keep track of currently open requests. We sort on this
|
||||||
active_requests: AtomicU32,
|
active_requests: AtomicU32,
|
||||||
/// provider is in a RwLock so that we can replace it if re-connecting
|
/// provider is in a RwLock so that we can replace it if re-connecting
|
||||||
@ -83,66 +84,19 @@ pub struct Web3Connection {
|
|||||||
/// rate limits are stored in a central redis so that multiple proxies can share their rate limits
|
/// rate limits are stored in a central redis so that multiple proxies can share their rate limits
|
||||||
hard_limit: Option<redis_cell_client::RedisCell>,
|
hard_limit: Option<redis_cell_client::RedisCell>,
|
||||||
/// used for load balancing to the least loaded server
|
/// used for load balancing to the least loaded server
|
||||||
soft_limit: u32,
|
pub soft_limit: u32,
|
||||||
block_data_limit: AtomicU64,
|
block_data_limit: AtomicU64,
|
||||||
weight: u32,
|
pub weight: u32,
|
||||||
head_block: RwLock<(H256, U64)>,
|
head_block: RwLock<(H256, U64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Web3Connection {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
// 3 is the number of fields in the struct.
|
|
||||||
let mut state = serializer.serialize_struct("Web3Connection", 4)?;
|
|
||||||
|
|
||||||
// TODO: sanitize any credentials in the url
|
|
||||||
state.serialize_field("url", &self.url)?;
|
|
||||||
|
|
||||||
let block_data_limit = self.block_data_limit.load(atomic::Ordering::Relaxed);
|
|
||||||
state.serialize_field("block_data_limit", &block_data_limit)?;
|
|
||||||
|
|
||||||
state.serialize_field("soft_limit", &self.soft_limit)?;
|
|
||||||
|
|
||||||
state.serialize_field(
|
|
||||||
"active_requests",
|
|
||||||
&self.active_requests.load(atomic::Ordering::Relaxed),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
state.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl fmt::Debug for Web3Connection {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let mut f = f.debug_struct("Web3Connection");
|
|
||||||
|
|
||||||
f.field("url", &self.url);
|
|
||||||
|
|
||||||
let block_data_limit = self.block_data_limit.load(atomic::Ordering::Relaxed);
|
|
||||||
if block_data_limit == u64::MAX {
|
|
||||||
f.field("data", &"archive");
|
|
||||||
} else {
|
|
||||||
f.field("data", &block_data_limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
f.finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Web3Connection {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
// TODO: filter basic auth and api keys
|
|
||||||
write!(f, "{}", &self.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Web3Connection {
|
impl Web3Connection {
|
||||||
/// Connect to a web3 rpc
|
/// Connect to a web3 rpc
|
||||||
// #[instrument(name = "spawn_Web3Connection", skip(hard_limit, http_client))]
|
// #[instrument(name = "spawn_Web3Connection", skip(hard_limit, http_client))]
|
||||||
// TODO: have this take a builder (which will have channels attached)
|
// TODO: have this take a builder (which will have channels attached)
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn spawn(
|
pub async fn spawn(
|
||||||
|
name: String,
|
||||||
chain_id: u64,
|
chain_id: u64,
|
||||||
url_str: String,
|
url_str: String,
|
||||||
// optional because this is only used for http providers. websocket providers don't use it
|
// optional because this is only used for http providers. websocket providers don't use it
|
||||||
@ -172,6 +126,7 @@ impl Web3Connection {
|
|||||||
let provider = Web3Provider::from_str(&url_str, http_client).await?;
|
let provider = Web3Provider::from_str(&url_str, http_client).await?;
|
||||||
|
|
||||||
let new_connection = Self {
|
let new_connection = Self {
|
||||||
|
name,
|
||||||
url: url_str.clone(),
|
url: url_str.clone(),
|
||||||
active_requests: 0.into(),
|
active_requests: 0.into(),
|
||||||
provider: AsyncRwLock::new(Some(Arc::new(provider))),
|
provider: AsyncRwLock::new(Some(Arc::new(provider))),
|
||||||
@ -280,10 +235,6 @@ impl Web3Connection {
|
|||||||
Ok((new_connection, handle))
|
Ok((new_connection, handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn url(&self) -> &str {
|
|
||||||
&self.url
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TODO: this might be too simple. different nodes can prune differently
|
/// TODO: this might be too simple. different nodes can prune differently
|
||||||
pub fn block_data_limit(&self) -> U64 {
|
pub fn block_data_limit(&self) -> U64 {
|
||||||
self.block_data_limit.load(atomic::Ordering::Acquire).into()
|
self.block_data_limit.load(atomic::Ordering::Acquire).into()
|
||||||
@ -338,11 +289,6 @@ impl Web3Connection {
|
|||||||
self.active_requests.load(atomic::Ordering::Acquire)
|
self.active_requests.load(atomic::Ordering::Acquire)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn soft_limit(&self) -> u32 {
|
|
||||||
self.soft_limit
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn has_provider(&self) -> bool {
|
pub async fn has_provider(&self) -> bool {
|
||||||
self.provider.read().await.is_some()
|
self.provider.read().await.is_some()
|
||||||
@ -668,10 +614,6 @@ impl Web3Connection {
|
|||||||
|
|
||||||
Ok(HandleResult::ActiveRequest(handle))
|
Ok(HandleResult::ActiveRequest(handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn weight(&self) -> u32 {
|
|
||||||
self.weight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Web3Connection {
|
impl Hash for Web3Connection {
|
||||||
@ -768,3 +710,52 @@ impl PartialEq for Web3Connection {
|
|||||||
self.url == other.url
|
self.url == other.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for Web3Connection {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
// 3 is the number of fields in the struct.
|
||||||
|
let mut state = serializer.serialize_struct("Web3Connection", 4)?;
|
||||||
|
|
||||||
|
// the url is excluded because it likely includes private information. just show the name
|
||||||
|
state.serialize_field("name", &self.name)?;
|
||||||
|
|
||||||
|
let block_data_limit = self.block_data_limit.load(atomic::Ordering::Relaxed);
|
||||||
|
state.serialize_field("block_data_limit", &block_data_limit)?;
|
||||||
|
|
||||||
|
state.serialize_field("soft_limit", &self.soft_limit)?;
|
||||||
|
|
||||||
|
state.serialize_field(
|
||||||
|
"active_requests",
|
||||||
|
&self.active_requests.load(atomic::Ordering::Relaxed),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
state.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Web3Connection {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut f = f.debug_struct("Web3Connection");
|
||||||
|
|
||||||
|
f.field("url", &self.url);
|
||||||
|
|
||||||
|
let block_data_limit = self.block_data_limit.load(atomic::Ordering::Relaxed);
|
||||||
|
if block_data_limit == u64::MAX {
|
||||||
|
f.field("data", &"archive");
|
||||||
|
} else {
|
||||||
|
f.field("data", &block_data_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
f.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Web3Connection {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
// TODO: filter basic auth and api keys
|
||||||
|
write!(f, "{}", &self.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -31,10 +31,11 @@ use crate::config::Web3ConnectionConfig;
|
|||||||
use crate::connection::{ActiveRequestHandle, HandleResult, Web3Connection};
|
use crate::connection::{ActiveRequestHandle, HandleResult, Web3Connection};
|
||||||
use crate::jsonrpc::{JsonRpcForwardedResponse, JsonRpcRequest};
|
use crate::jsonrpc::{JsonRpcForwardedResponse, JsonRpcRequest};
|
||||||
|
|
||||||
// Serialize so we can print it on our debug endpoint
|
/// A collection of Web3Connections that are on the same block
|
||||||
|
/// Serialize is so we can print it on our debug endpoint
|
||||||
#[derive(Clone, Default, Serialize)]
|
#[derive(Clone, Default, Serialize)]
|
||||||
struct SyncedConnections {
|
struct SyncedConnections {
|
||||||
head_block_num: u64,
|
head_block_num: U64,
|
||||||
head_block_hash: H256,
|
head_block_hash: H256,
|
||||||
// TODO: this should be able to serialize, but it isn't
|
// TODO: this should be able to serialize, but it isn't
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
@ -53,16 +54,6 @@ impl fmt::Debug for SyncedConnections {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncedConnections {
|
|
||||||
pub fn head_block_hash(&self) -> &H256 {
|
|
||||||
&self.head_block_hash
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn head_block_num(&self) -> U64 {
|
|
||||||
self.head_block_num.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct BlockChain {
|
pub struct BlockChain {
|
||||||
/// only includes blocks on the main chain.
|
/// only includes blocks on the main chain.
|
||||||
@ -144,7 +135,7 @@ impl Web3Connections {
|
|||||||
// #[instrument(name = "spawn_Web3Connections", skip_all)]
|
// #[instrument(name = "spawn_Web3Connections", skip_all)]
|
||||||
pub async fn spawn(
|
pub async fn spawn(
|
||||||
chain_id: u64,
|
chain_id: u64,
|
||||||
server_configs: Vec<Web3ConnectionConfig>,
|
server_configs: HashMap<String, Web3ConnectionConfig>,
|
||||||
http_client: Option<reqwest::Client>,
|
http_client: Option<reqwest::Client>,
|
||||||
redis_client_pool: Option<redis_cell_client::RedisPool>,
|
redis_client_pool: Option<redis_cell_client::RedisPool>,
|
||||||
head_block_sender: Option<watch::Sender<Arc<Block<TxHash>>>>,
|
head_block_sender: Option<watch::Sender<Arc<Block<TxHash>>>>,
|
||||||
@ -193,7 +184,7 @@ impl Web3Connections {
|
|||||||
// turn configs into connections (in parallel)
|
// turn configs into connections (in parallel)
|
||||||
let spawn_handles: Vec<_> = server_configs
|
let spawn_handles: Vec<_> = server_configs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|server_config| {
|
.map(|(server_name, server_config)| {
|
||||||
let http_client = http_client.clone();
|
let http_client = http_client.clone();
|
||||||
let redis_client_pool = redis_client_pool.clone();
|
let redis_client_pool = redis_client_pool.clone();
|
||||||
let http_interval_sender = http_interval_sender.clone();
|
let http_interval_sender = http_interval_sender.clone();
|
||||||
@ -203,6 +194,7 @@ impl Web3Connections {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
server_config
|
server_config
|
||||||
.spawn(
|
.spawn(
|
||||||
|
server_name,
|
||||||
redis_client_pool,
|
redis_client_pool,
|
||||||
chain_id,
|
chain_id,
|
||||||
http_client,
|
http_client,
|
||||||
@ -223,7 +215,7 @@ impl Web3Connections {
|
|||||||
// TODO: how should we handle errors here? one rpc being down shouldn't cause the program to exit
|
// TODO: how should we handle errors here? one rpc being down shouldn't cause the program to exit
|
||||||
match x {
|
match x {
|
||||||
Ok(Ok((connection, handle))) => {
|
Ok(Ok((connection, handle))) => {
|
||||||
connections.insert(connection.url().to_string(), connection);
|
connections.insert(connection.url.clone(), connection);
|
||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
}
|
}
|
||||||
Ok(Err(err)) => {
|
Ok(Err(err)) => {
|
||||||
@ -506,18 +498,18 @@ impl Web3Connections {
|
|||||||
pub fn head_block(&self) -> (U64, H256) {
|
pub fn head_block(&self) -> (U64, H256) {
|
||||||
let synced_connections = self.synced_connections.load();
|
let synced_connections = self.synced_connections.load();
|
||||||
|
|
||||||
let num = synced_connections.head_block_num();
|
(
|
||||||
let hash = *synced_connections.head_block_hash();
|
synced_connections.head_block_num,
|
||||||
|
synced_connections.head_block_hash,
|
||||||
(num, hash)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn head_block_hash(&self) -> H256 {
|
pub fn head_block_hash(&self) -> H256 {
|
||||||
*self.synced_connections.load().head_block_hash()
|
self.synced_connections.load().head_block_hash
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn head_block_num(&self) -> U64 {
|
pub fn head_block_num(&self) -> U64 {
|
||||||
self.synced_connections.load().head_block_num()
|
self.synced_connections.load().head_block_num
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn synced(&self) -> bool {
|
pub fn synced(&self) -> bool {
|
||||||
@ -606,7 +598,7 @@ impl Web3Connections {
|
|||||||
let mut connection_heads = IndexMap::<String, Arc<Block<TxHash>>>::new();
|
let mut connection_heads = IndexMap::<String, Arc<Block<TxHash>>>::new();
|
||||||
|
|
||||||
while let Ok((new_block, rpc)) = block_receiver.recv_async().await {
|
while let Ok((new_block, rpc)) = block_receiver.recv_async().await {
|
||||||
if let Some(current_block) = connection_heads.get(rpc.url()) {
|
if let Some(current_block) = connection_heads.get(&rpc.url) {
|
||||||
if current_block.hash == new_block.hash {
|
if current_block.hash == new_block.hash {
|
||||||
// duplicate block
|
// duplicate block
|
||||||
continue;
|
continue;
|
||||||
@ -618,7 +610,7 @@ impl Web3Connections {
|
|||||||
} else {
|
} else {
|
||||||
warn!(%rpc, ?new_block, "Block without hash!");
|
warn!(%rpc, ?new_block, "Block without hash!");
|
||||||
|
|
||||||
connection_heads.remove(rpc.url());
|
connection_heads.remove(&rpc.url);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -631,7 +623,7 @@ impl Web3Connections {
|
|||||||
// maybe when a node is syncing or reconnecting?
|
// maybe when a node is syncing or reconnecting?
|
||||||
warn!(%rpc, ?new_block, "Block without number!");
|
warn!(%rpc, ?new_block, "Block without number!");
|
||||||
|
|
||||||
connection_heads.remove(rpc.url());
|
connection_heads.remove(&rpc.url);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -646,10 +638,10 @@ impl Web3Connections {
|
|||||||
if new_block_num == U64::zero() {
|
if new_block_num == U64::zero() {
|
||||||
warn!(%rpc, %new_block_num, "still syncing");
|
warn!(%rpc, %new_block_num, "still syncing");
|
||||||
|
|
||||||
connection_heads.remove(rpc.url());
|
connection_heads.remove(&rpc.url);
|
||||||
} else {
|
} else {
|
||||||
// TODO: no clone? we end up with different blocks for every rpc
|
// TODO: no clone? we end up with different blocks for every rpc
|
||||||
connection_heads.insert(rpc.url().to_string(), new_block.clone());
|
connection_heads.insert(rpc.url.clone(), new_block.clone());
|
||||||
|
|
||||||
self.chain.add_block(new_block.clone(), false);
|
self.chain.add_block(new_block.clone(), false);
|
||||||
}
|
}
|
||||||
@ -675,7 +667,7 @@ impl Web3Connections {
|
|||||||
for (rpc_url, head_block) in connection_heads.iter() {
|
for (rpc_url, head_block) in connection_heads.iter() {
|
||||||
if let Some(rpc) = self.conns.get(rpc_url) {
|
if let Some(rpc) = self.conns.get(rpc_url) {
|
||||||
// we need the total soft limit in order to know when its safe to update the backends
|
// we need the total soft limit in order to know when its safe to update the backends
|
||||||
total_soft_limit += rpc.soft_limit();
|
total_soft_limit += rpc.soft_limit;
|
||||||
|
|
||||||
let head_hash = head_block.hash.unwrap();
|
let head_hash = head_block.hash.unwrap();
|
||||||
|
|
||||||
@ -763,7 +755,7 @@ impl Web3Connections {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|rpc_url| {
|
.map(|rpc_url| {
|
||||||
if let Some(rpc) = self.conns.get(*rpc_url) {
|
if let Some(rpc) = self.conns.get(*rpc_url) {
|
||||||
rpc.soft_limit()
|
rpc.soft_limit
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
@ -824,7 +816,7 @@ impl Web3Connections {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let pending_synced_connections = SyncedConnections {
|
let pending_synced_connections = SyncedConnections {
|
||||||
head_block_num: best_head_num.as_u64(),
|
head_block_num: best_head_num,
|
||||||
head_block_hash: best_head_hash,
|
head_block_hash: best_head_hash,
|
||||||
conns,
|
conns,
|
||||||
};
|
};
|
||||||
@ -948,9 +940,9 @@ impl Web3Connections {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|rpc| {
|
.map(|rpc| {
|
||||||
// TODO: get active requests and the soft limit out of redis?
|
// TODO: get active requests and the soft limit out of redis?
|
||||||
let weight = rpc.weight();
|
let weight = rpc.weight;
|
||||||
let active_requests = rpc.active_requests();
|
let active_requests = rpc.active_requests();
|
||||||
let soft_limit = rpc.soft_limit();
|
let soft_limit = rpc.soft_limit;
|
||||||
|
|
||||||
let utilization = active_requests as f32 / soft_limit as f32;
|
let utilization = active_requests as f32 / soft_limit as f32;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ use crate::app::Web3ProxyApp;
|
|||||||
|
|
||||||
/// Health check page for load balancers to use
|
/// Health check page for load balancers to use
|
||||||
pub async fn health(Extension(app): Extension<Arc<Web3ProxyApp>>) -> impl IntoResponse {
|
pub async fn health(Extension(app): Extension<Arc<Web3ProxyApp>>) -> impl IntoResponse {
|
||||||
if app.balanced_rpcs().synced() {
|
if app.balanced_rpcs.synced() {
|
||||||
(StatusCode::OK, "OK")
|
(StatusCode::OK, "OK")
|
||||||
} else {
|
} else {
|
||||||
(StatusCode::SERVICE_UNAVAILABLE, ":(")
|
(StatusCode::SERVICE_UNAVAILABLE, ":(")
|
||||||
@ -17,17 +17,12 @@ pub async fn health(Extension(app): Extension<Arc<Web3ProxyApp>>) -> impl IntoRe
|
|||||||
/// TODO: replace this with proper stats and monitoring
|
/// TODO: replace this with proper stats and monitoring
|
||||||
pub async fn status(Extension(app): Extension<Arc<Web3ProxyApp>>) -> impl IntoResponse {
|
pub async fn status(Extension(app): Extension<Arc<Web3ProxyApp>>) -> impl IntoResponse {
|
||||||
// TODO: what else should we include? uptime? prometheus?
|
// TODO: what else should we include? uptime? prometheus?
|
||||||
let balanced_rpcs = app.balanced_rpcs();
|
|
||||||
let private_rpcs = app.private_rpcs();
|
|
||||||
let num_active_requests = app.active_requests().len();
|
|
||||||
let num_pending_transactions = app.pending_transactions().len();
|
|
||||||
|
|
||||||
let body = json!({
|
let body = json!({
|
||||||
"balanced_rpcs": balanced_rpcs,
|
"balanced_rpcs": app.balanced_rpcs,
|
||||||
"private_rpcs": private_rpcs,
|
"private_rpcs": app.private_rpcs,
|
||||||
"num_active_requests": num_active_requests,
|
"num_active_requests": app.active_requests.len(),
|
||||||
"num_pending_transactions": num_pending_transactions,
|
"num_pending_transactions": app.pending_transactions.len(),
|
||||||
});
|
});
|
||||||
|
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(body))
|
(StatusCode::OK, Json(body))
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ impl Web3ProxyApp {
|
|||||||
let rate_limiter_key = format!("ip-{}", ip);
|
let rate_limiter_key = format!("ip-{}", ip);
|
||||||
|
|
||||||
// TODO: dry this up with rate_limit_by_key
|
// TODO: dry this up with rate_limit_by_key
|
||||||
if let Some(rate_limiter) = self.rate_limiter() {
|
if let Some(rate_limiter) = &self.rate_limiter {
|
||||||
match rate_limiter
|
match rate_limiter
|
||||||
.throttle_key(&rate_limiter_key, None, None, None)
|
.throttle_key(&rate_limiter_key, None, None, None)
|
||||||
.await
|
.await
|
||||||
@ -52,10 +52,8 @@ impl Web3ProxyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn rate_limit_by_key(&self, user_key: Uuid) -> anyhow::Result<RateLimitResult> {
|
pub async fn rate_limit_by_key(&self, user_key: Uuid) -> anyhow::Result<RateLimitResult> {
|
||||||
let user_cache = self.user_cache();
|
|
||||||
|
|
||||||
// check the local cache
|
// check the local cache
|
||||||
let user_data = if let Some(cached_user) = user_cache.read().get(&user_key) {
|
let user_data = if let Some(cached_user) = self.user_cache.read().get(&user_key) {
|
||||||
// TODO: also include the time this value was last checked! otherwise we cache forever!
|
// TODO: also include the time this value was last checked! otherwise we cache forever!
|
||||||
if cached_user.expires_at < Instant::now() {
|
if cached_user.expires_at < Instant::now() {
|
||||||
// old record found
|
// old record found
|
||||||
@ -71,7 +69,7 @@ impl Web3ProxyApp {
|
|||||||
|
|
||||||
// if cache was empty, check the database
|
// if cache was empty, check the database
|
||||||
let user_data = if user_data.is_none() {
|
let user_data = if user_data.is_none() {
|
||||||
if let Some(db) = self.db_conn() {
|
if let Some(db) = &self.db_conn {
|
||||||
/// helper enum for query just a few columns instead of the entire table
|
/// helper enum for query just a few columns instead of the entire table
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||||
enum QueryAs {
|
enum QueryAs {
|
||||||
@ -105,7 +103,7 @@ impl Web3ProxyApp {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// save for the next run
|
// save for the next run
|
||||||
user_cache.write().insert(user_key, user_data);
|
self.user_cache.write().insert(user_key, user_data);
|
||||||
|
|
||||||
user_data
|
user_data
|
||||||
} else {
|
} else {
|
||||||
@ -118,7 +116,7 @@ impl Web3ProxyApp {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// user key is valid. now check rate limits
|
// user key is valid. now check rate limits
|
||||||
if let Some(rate_limiter) = self.rate_limiter() {
|
if let Some(rate_limiter) = &self.rate_limiter {
|
||||||
// TODO: how does max burst actually work? what should it be?
|
// TODO: how does max burst actually work? what should it be?
|
||||||
let user_max_burst = user_data.user_count_per_period / 3;
|
let user_max_burst = user_data.user_count_per_period / 3;
|
||||||
let user_period = 60;
|
let user_period = 60;
|
||||||
|
@ -53,7 +53,7 @@ pub async fn create_user(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = app.db_conn().unwrap();
|
let db = app.db_conn.as_ref().unwrap();
|
||||||
|
|
||||||
// TODO: proper error message
|
// TODO: proper error message
|
||||||
let user = user.insert(db).await.unwrap();
|
let user = user.insert(db).await.unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user