tx stats too
This commit is contained in:
parent
8868c35e95
commit
81c49d08cf
2
TODO.md
2
TODO.md
@ -584,3 +584,5 @@ in another repo: event subscriber
|
|||||||
- [ ] some internal requests should go through app.proxy_rpc_request so that they get caching!
|
- [ ] some internal requests should go through app.proxy_rpc_request so that they get caching!
|
||||||
- be careful not to make an infinite loop
|
- be careful not to make an infinite loop
|
||||||
- [ ] request timeout messages should include the request id
|
- [ ] request timeout messages should include the request id
|
||||||
|
- [ ] have an upgrade tier that queries multiple backends at once. returns on first Ok result, collects errors. if no Ok, find the most common error and then respond with that
|
||||||
|
- [ ] give public_recent_ips_salt a better, more general, name
|
@ -34,6 +34,7 @@ use migration::sea_orm::{self, ConnectionTrait, Database, DatabaseConnection};
|
|||||||
use migration::sea_query::table::ColumnDef;
|
use migration::sea_query::table::ColumnDef;
|
||||||
use migration::{Alias, DbErr, Migrator, MigratorTrait, Table};
|
use migration::{Alias, DbErr, Migrator, MigratorTrait, Table};
|
||||||
use moka::future::Cache;
|
use moka::future::Cache;
|
||||||
|
use redis_rate_limiter::redis::AsyncCommands;
|
||||||
use redis_rate_limiter::{redis, DeadpoolRuntime, RedisConfig, RedisPool, RedisRateLimiter};
|
use redis_rate_limiter::{redis, DeadpoolRuntime, RedisConfig, RedisPool, RedisRateLimiter};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@ -709,7 +710,7 @@ impl Web3ProxyApp {
|
|||||||
// TODO: what globals? should this be the hostname or what?
|
// TODO: what globals? should this be the hostname or what?
|
||||||
// globals.insert("service", "web3_proxy");
|
// globals.insert("service", "web3_proxy");
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Default, Serialize)]
|
||||||
struct RecentCounts {
|
struct RecentCounts {
|
||||||
one_day: i64,
|
one_day: i64,
|
||||||
one_hour: i64,
|
one_hour: i64,
|
||||||
@ -726,9 +727,12 @@ impl Web3ProxyApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (recent_ip_counts, recent_user_counts): (RecentCounts, RecentCounts) =
|
let (recent_ip_counts, recent_user_id_counts, recent_tx_counts): (
|
||||||
match self.redis_conn().await {
|
RecentCounts,
|
||||||
Ok(mut redis_conn) => {
|
RecentCounts,
|
||||||
|
RecentCounts,
|
||||||
|
) = match self.redis_conn().await {
|
||||||
|
Ok(Some(mut redis_conn)) => {
|
||||||
// TODO: delete any hash entries where
|
// TODO: delete any hash entries where
|
||||||
const ONE_MINUTE: i64 = 60;
|
const ONE_MINUTE: i64 = 60;
|
||||||
const ONE_HOUR: i64 = ONE_MINUTE * 60;
|
const ONE_HOUR: i64 = ONE_MINUTE * 60;
|
||||||
@ -738,59 +742,86 @@ impl Web3ProxyApp {
|
|||||||
let one_hour_ago = Utc::now().timestamp() - ONE_HOUR;
|
let one_hour_ago = Utc::now().timestamp() - ONE_HOUR;
|
||||||
let one_minute_ago = Utc::now().timestamp() - ONE_MINUTE;
|
let one_minute_ago = Utc::now().timestamp() - ONE_MINUTE;
|
||||||
|
|
||||||
let recent_users_by_user =
|
let recent_users_by_id = format!("recent_users:id:{}", self.config.chain_id);
|
||||||
format!("recent_users:registered:{}", self.config.chain_id);
|
|
||||||
let recent_users_by_ip = format!("recent_users:ip:{}", self.config.chain_id);
|
let recent_users_by_ip = format!("recent_users:ip:{}", self.config.chain_id);
|
||||||
|
let recent_transactions =
|
||||||
|
format!("eth_sendRawTransaction:{}", self.config.chain_id);
|
||||||
|
|
||||||
match redis::pipe()
|
match redis::pipe()
|
||||||
.atomic()
|
.atomic()
|
||||||
// delete any entries older than 24 hours
|
// delete any entries older than 24 hours
|
||||||
.zrembyscore(&recent_users_by_user, i64::MIN, one_day_ago)
|
.zrembyscore(&recent_users_by_id, i64::MIN, one_day_ago)
|
||||||
.ignore()
|
.ignore()
|
||||||
.zrembyscore(&recent_users_by_ip, i64::MIN, one_day_ago)
|
.zrembyscore(&recent_users_by_ip, i64::MIN, one_day_ago)
|
||||||
.ignore()
|
.ignore()
|
||||||
// get count for last day
|
.zrembyscore(&recent_transactions, i64::MIN, one_day_ago)
|
||||||
.zcount(&recent_users_by_user, one_day_ago, i64::MAX)
|
.ignore()
|
||||||
|
// get counts for last day
|
||||||
|
.zcount(&recent_users_by_id, one_day_ago, i64::MAX)
|
||||||
.zcount(&recent_users_by_ip, one_day_ago, i64::MAX)
|
.zcount(&recent_users_by_ip, one_day_ago, i64::MAX)
|
||||||
// get count for last hour
|
.zcount(&recent_transactions, one_day_ago, i64::MAX)
|
||||||
.zcount(&recent_users_by_user, one_hour_ago, i64::MAX)
|
// get counts for last hour
|
||||||
|
.zcount(&recent_users_by_id, one_hour_ago, i64::MAX)
|
||||||
.zcount(&recent_users_by_ip, one_hour_ago, i64::MAX)
|
.zcount(&recent_users_by_ip, one_hour_ago, i64::MAX)
|
||||||
// get count for last minute
|
.zcount(&recent_transactions, one_hour_ago, i64::MAX)
|
||||||
.zcount(&recent_users_by_user, one_minute_ago, i64::MAX)
|
// get counts for last minute
|
||||||
|
.zcount(&recent_users_by_id, one_minute_ago, i64::MAX)
|
||||||
.zcount(&recent_users_by_ip, one_minute_ago, i64::MAX)
|
.zcount(&recent_users_by_ip, one_minute_ago, i64::MAX)
|
||||||
|
.zcount(&recent_transactions, one_minute_ago, i64::MAX)
|
||||||
.query_async(&mut redis_conn)
|
.query_async(&mut redis_conn)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok((
|
Ok((
|
||||||
day_by_user,
|
user_id_in_day,
|
||||||
day_by_ip,
|
ip_in_day,
|
||||||
hour_by_user,
|
txs_in_day,
|
||||||
hour_by_ip,
|
user_id_in_hour,
|
||||||
minute_by_user,
|
ip_in_hour,
|
||||||
minute_by_ip,
|
txs_in_hour,
|
||||||
|
user_id_in_minute,
|
||||||
|
ip_in_minute,
|
||||||
|
txs_in_minute,
|
||||||
)) => {
|
)) => {
|
||||||
let recent_ip_counts = RecentCounts {
|
let recent_user_id_counts = RecentCounts {
|
||||||
one_day: day_by_ip,
|
one_day: user_id_in_day,
|
||||||
one_hour: hour_by_ip,
|
one_hour: user_id_in_hour,
|
||||||
one_minute: minute_by_ip,
|
one_minute: user_id_in_minute,
|
||||||
};
|
};
|
||||||
let recent_user_counts = RecentCounts {
|
let recent_ip_counts = RecentCounts {
|
||||||
one_day: day_by_user,
|
one_day: ip_in_day,
|
||||||
one_hour: hour_by_user,
|
one_hour: ip_in_hour,
|
||||||
one_minute: minute_by_user,
|
one_minute: ip_in_minute,
|
||||||
|
};
|
||||||
|
let recent_tx_counts = RecentCounts {
|
||||||
|
one_day: txs_in_day,
|
||||||
|
one_hour: txs_in_hour,
|
||||||
|
one_minute: txs_in_minute,
|
||||||
};
|
};
|
||||||
|
|
||||||
(recent_ip_counts, recent_user_counts)
|
(recent_ip_counts, recent_user_id_counts, recent_tx_counts)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("unable to count recent users: {}", err);
|
warn!("unable to count recent users: {}", err);
|
||||||
(RecentCounts::for_err(), RecentCounts::for_err())
|
(
|
||||||
|
RecentCounts::for_err(),
|
||||||
|
RecentCounts::for_err(),
|
||||||
|
RecentCounts::for_err(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(None) => (
|
||||||
|
RecentCounts::default(),
|
||||||
|
RecentCounts::default(),
|
||||||
|
RecentCounts::default(),
|
||||||
|
),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("unable to connect to redis while counting users: {:?}", err);
|
warn!("unable to connect to redis while counting users: {:?}", err);
|
||||||
(RecentCounts::for_err(), RecentCounts::for_err())
|
(
|
||||||
|
RecentCounts::for_err(),
|
||||||
|
RecentCounts::for_err(),
|
||||||
|
RecentCounts::for_err(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -799,14 +830,16 @@ impl Web3ProxyApp {
|
|||||||
app: &'a Web3ProxyAppMetrics,
|
app: &'a Web3ProxyAppMetrics,
|
||||||
backend_rpc: &'a OpenRequestHandleMetrics,
|
backend_rpc: &'a OpenRequestHandleMetrics,
|
||||||
recent_ip_counts: RecentCounts,
|
recent_ip_counts: RecentCounts,
|
||||||
recent_user_counts: RecentCounts,
|
recent_user_id_counts: RecentCounts,
|
||||||
|
recent_tx_counts: RecentCounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
let metrics = CombinedMetrics {
|
let metrics = CombinedMetrics {
|
||||||
app: &self.app_metrics,
|
app: &self.app_metrics,
|
||||||
backend_rpc: &self.open_request_handle_metrics,
|
backend_rpc: &self.open_request_handle_metrics,
|
||||||
recent_ip_counts,
|
recent_ip_counts,
|
||||||
recent_user_counts,
|
recent_user_id_counts,
|
||||||
|
recent_tx_counts,
|
||||||
};
|
};
|
||||||
|
|
||||||
serde_prometheus::to_string(&metrics, Some("web3_proxy"), globals)
|
serde_prometheus::to_string(&metrics, Some("web3_proxy"), globals)
|
||||||
@ -898,13 +931,14 @@ impl Web3ProxyApp {
|
|||||||
self.db_replica.clone()
|
self.db_replica.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn redis_conn(&self) -> anyhow::Result<redis_rate_limiter::RedisConnection> {
|
pub async fn redis_conn(&self) -> anyhow::Result<Option<redis_rate_limiter::RedisConnection>> {
|
||||||
match self.vredis_pool.as_ref() {
|
match self.vredis_pool.as_ref() {
|
||||||
None => Err(anyhow::anyhow!("no redis server configured")),
|
// TODO: don't do an error. return None
|
||||||
|
None => Ok(None),
|
||||||
Some(redis_pool) => {
|
Some(redis_pool) => {
|
||||||
let redis_conn = redis_pool.get().await?;
|
let redis_conn = redis_pool.get().await?;
|
||||||
|
|
||||||
Ok(redis_conn)
|
Ok(Some(redis_conn))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1136,7 +1170,42 @@ impl Web3ProxyApp {
|
|||||||
|
|
||||||
let rpcs = request_metadata.backend_requests.lock().clone();
|
let rpcs = request_metadata.backend_requests.lock().clone();
|
||||||
|
|
||||||
// TODO! STATS!
|
if let Some(salt) = self.config.public_recent_ips_salt.as_ref() {
|
||||||
|
if let Some(tx_hash) = response.result.clone() {
|
||||||
|
let now = Utc::now().timestamp();
|
||||||
|
let salt = salt.clone();
|
||||||
|
let app = self.clone();
|
||||||
|
|
||||||
|
let f = async move {
|
||||||
|
match app.redis_conn().await {
|
||||||
|
Ok(Some(mut redis_conn)) => {
|
||||||
|
let salted_tx_hash = format!("{}:{}", salt, tx_hash);
|
||||||
|
|
||||||
|
let hashed_tx_hash =
|
||||||
|
Bytes::from(keccak256(salted_tx_hash.as_bytes()));
|
||||||
|
|
||||||
|
let recent_tx_hash_key =
|
||||||
|
format!("eth_sendRawTransaction:{}", app.config.chain_id);
|
||||||
|
|
||||||
|
redis_conn
|
||||||
|
.zadd(recent_tx_hash_key, hashed_tx_hash.to_string(), now)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
"unable to save stats for eth_sendRawTransaction: {:?}",
|
||||||
|
err
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::spawn(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Ok((response, rpcs));
|
return Ok((response, rpcs));
|
||||||
}
|
}
|
||||||
|
@ -348,8 +348,7 @@ pub async fn ip_is_authorized(
|
|||||||
let f = async move {
|
let f = async move {
|
||||||
let now = Utc::now().timestamp();
|
let now = Utc::now().timestamp();
|
||||||
|
|
||||||
let mut redis_conn = app.redis_conn().await?;
|
if let Some(mut redis_conn) = app.redis_conn().await? {
|
||||||
|
|
||||||
let salt = app
|
let salt = app
|
||||||
.config
|
.config
|
||||||
.public_recent_ips_salt
|
.public_recent_ips_salt
|
||||||
@ -365,6 +364,7 @@ pub async fn ip_is_authorized(
|
|||||||
redis_conn
|
redis_conn
|
||||||
.zadd(recent_ip_key, hashed_ip.to_string(), now)
|
.zadd(recent_ip_key, hashed_ip.to_string(), now)
|
||||||
.await?;
|
.await?;
|
||||||
|
};
|
||||||
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
}
|
}
|
||||||
@ -410,8 +410,7 @@ pub async fn key_is_authorized(
|
|||||||
let f = async move {
|
let f = async move {
|
||||||
let now = Utc::now().timestamp();
|
let now = Utc::now().timestamp();
|
||||||
|
|
||||||
let mut redis_conn = app.redis_conn().await?;
|
if let Some(mut redis_conn) = app.redis_conn().await? {
|
||||||
|
|
||||||
let salt = app
|
let salt = app
|
||||||
.config
|
.config
|
||||||
.public_recent_ips_salt
|
.public_recent_ips_salt
|
||||||
@ -427,6 +426,7 @@ pub async fn key_is_authorized(
|
|||||||
redis_conn
|
redis_conn
|
||||||
.zadd(recent_user_id_key, hashed_user_id.to_string(), now)
|
.zadd(recent_user_id_key, hashed_user_id.to_string(), now)
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
}
|
}
|
||||||
|
@ -268,6 +268,7 @@ pub async fn query_user_stats<'a>(
|
|||||||
let mut redis_conn = app
|
let mut redis_conn = app
|
||||||
.redis_conn()
|
.redis_conn()
|
||||||
.await
|
.await
|
||||||
|
.context("query_user_stats had a redis connection error")?
|
||||||
.context("query_user_stats needs a redis")?;
|
.context("query_user_stats needs a redis")?;
|
||||||
|
|
||||||
// get the user id first. if it is 0, we should use a cache on the app
|
// get the user id first. if it is 0, we should use a cache on the app
|
||||||
|
Loading…
Reference in New Issue
Block a user