clean up migration

This commit is contained in:
Bryan Stitt 2022-11-01 18:54:39 +00:00
parent 1d22291737
commit 8b35bf5e63
26 changed files with 308 additions and 160 deletions

4
Cargo.lock generated

@ -1348,7 +1348,7 @@ dependencies = [
[[package]]
name = "entities"
version = "0.6.0"
version = "0.8.0"
dependencies = [
"sea-orm",
"serde",
@ -2657,7 +2657,7 @@ dependencies = [
[[package]]
name = "migration"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"sea-orm-migration",
"tokio",

@ -64,6 +64,13 @@ $ websocat ws://127.0.0.1:8544
You can copy `config/example.toml` to `config/production-$CHAINNAME.toml` and then run `docker-compose up --build -d` start proxies for many chains.
Run migrations (useful during development. in production, the migrations run on application start)
```
cd migration
cargo run up
```
## Database entities
This command only needs to be run during development. Production should use the already generated entities.

@ -1,6 +1,6 @@
[package]
name = "entities"
version = "0.6.0"
version = "0.8.0"
edition = "2021"
[lib]

@ -1,10 +1,11 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.1
pub mod prelude;
pub mod revert_logs;
pub mod revert_log;
pub mod rpc_accounting;
pub mod rpc_key;
pub mod sea_orm_active_enums;
pub mod secondary_user;
pub mod user;
pub mod rpc_keys;
pub mod user_tier;

@ -1,7 +1,8 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.1
pub use super::revert_logs::Entity as RevertLogs;
pub use super::revert_log::Entity as RevertLog;
pub use super::rpc_accounting::Entity as RpcAccounting;
pub use super::rpc_keys::Entity as UserKeys;
pub use super::rpc_key::Entity as RpcKey;
pub use super::secondary_user::Entity as SecondaryUser;
pub use super::user::Entity as User;
pub use super::user_tier::Entity as UserTier;

@ -1,11 +1,11 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.1
use super::sea_orm_active_enums::Method;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "revert_logs")]
#[sea_orm(table_name = "revert_log")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: u64,
@ -21,18 +21,18 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::rpc_keys::Entity",
belongs_to = "super::rpc_key::Entity",
from = "Column::RpcKeyId",
to = "super::rpc_keys::Column::Id",
to = "super::rpc_key::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
UserKeys,
RpcKey,
}
impl Related<super::rpc_keys::Entity> for Entity {
impl Related<super::rpc_key::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserKeys.def()
Relation::RpcKey.def()
}
}

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
@ -13,14 +13,12 @@ pub struct Model {
pub method: String,
pub error_response: bool,
pub period_datetime: DateTimeUtc,
// TODO: migration to make these u32
pub frontend_requests: u64,
pub backend_requests: u64,
pub backend_retries: u64,
pub no_servers: u64,
pub cache_misses: u64,
pub cache_hits: u64,
// TODO: end migration to make these u32
pub sum_request_bytes: u64,
pub min_request_bytes: u64,
pub mean_request_bytes: f64,
@ -47,18 +45,18 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::rpc_keys::Entity",
belongs_to = "super::rpc_key::Entity",
from = "Column::RpcKeyId",
to = "super::rpc_keys::Column::Id",
to = "super::rpc_key::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
RpcKeys,
RpcKey,
}
impl Related<super::rpc_keys::Entity> for Entity {
impl Related<super::rpc_key::Entity> for Entity {
fn to() -> RelationDef {
Relation::RpcKeys.def()
Relation::RpcKey.def()
}
}

@ -1,22 +1,19 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "rpc_keys")]
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "rpc_key")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: u64,
pub user_id: u64,
#[sea_orm(unique)]
pub rpc_key: Uuid,
pub secret_key: Uuid,
pub description: Option<String>,
pub private_txs: bool,
pub active: bool,
pub requests_per_minute: Option<u64>,
#[sea_orm(column_type = "Decimal(Some((5, 4)))")]
pub log_revert_chance: Decimal,
#[sea_orm(column_type = "Text", nullable)]
pub allowed_ips: Option<String>,
#[sea_orm(column_type = "Text", nullable)]
@ -25,7 +22,7 @@ pub struct Model {
pub allowed_referers: Option<String>,
#[sea_orm(column_type = "Text", nullable)]
pub allowed_user_agents: Option<String>,
pub max_concurrent_requests: Option<u64>,
pub log_revert_chance: f64,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@ -38,8 +35,8 @@ pub enum Relation {
on_delete = "NoAction"
)]
User,
#[sea_orm(has_many = "super::revert_logs::Entity")]
RevertLogs,
#[sea_orm(has_many = "super::revert_log::Entity")]
RevertLog,
#[sea_orm(has_many = "super::rpc_accounting::Entity")]
RpcAccounting,
}
@ -50,9 +47,9 @@ impl Related<super::user::Entity> for Entity {
}
}
impl Related<super::revert_logs::Entity> for Entity {
impl Related<super::revert_log::Entity> for Entity {
fn to() -> RelationDef {
Relation::RevertLogs.def()
Relation::RevertLog.def()
}
}

@ -1,7 +1,6 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.1
use sea_orm::entity::prelude::*;
use sea_orm::EnumIter;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.1
use super::sea_orm_active_enums::Role;
use sea_orm::entity::prelude::*;
@ -10,9 +10,7 @@ pub struct Model {
#[sea_orm(primary_key)]
pub id: u64,
pub user_id: u64,
pub address: Vec<u8>,
pub description: Option<String>,
pub email: Option<String>,
pub role: Role,
}

@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
@ -12,14 +12,35 @@ pub struct Model {
pub address: Vec<u8>,
pub description: Option<String>,
pub email: Option<String>,
pub user_tier_id: u64,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::user_tier::Entity",
from = "Column::UserTierId",
to = "super::user_tier::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
UserTier,
#[sea_orm(has_many = "super::rpc_key::Entity")]
RpcKey,
#[sea_orm(has_many = "super::secondary_user::Entity")]
SecondaryUser,
#[sea_orm(has_many = "super::rpc_keys::Entity")]
RpcKeys,
}
impl Related<super::user_tier::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserTier.def()
}
}
impl Related<super::rpc_key::Entity> for Entity {
fn to() -> RelationDef {
Relation::RpcKey.def()
}
}
impl Related<super::secondary_user::Entity> for Entity {
@ -28,10 +49,4 @@ impl Related<super::secondary_user::Entity> for Entity {
}
}
impl Related<super::rpc_keys::Entity> for Entity {
fn to() -> RelationDef {
Relation::RpcKeys.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

28
entities/src/user_tier.rs Normal file

@ -0,0 +1,28 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.10.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user_tier")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: u64,
pub title: String,
pub max_requests_per_minute: Option<u64>,
pub max_concurrent_requests: Option<u32>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::user::Entity")]
User,
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -1,6 +1,6 @@
[package]
name = "migration"
version = "0.7.0"
version = "0.8.0"
edition = "2021"
publish = false

@ -7,6 +7,7 @@ mod m20221007_213828_accounting;
mod m20221025_210326_add_chain_id_to_reverts;
mod m20221026_230819_rename_user_keys;
mod m20221027_002407_user_tiers;
mod m20221031_211916_clean_up;
pub struct Migrator;
@ -21,6 +22,7 @@ impl MigratorTrait for Migrator {
Box::new(m20221025_210326_add_chain_id_to_reverts::Migration),
Box::new(m20221026_230819_rename_user_keys::Migration),
Box::new(m20221027_002407_user_tiers::Migration),
Box::new(m20221031_211916_clean_up::Migration),
]
}
}

@ -0,0 +1,103 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// rename tables from plural to singluar
manager
.rename_table(
Table::rename()
.table(Alias::new("revert_logs"), Alias::new("revert_log"))
.to_owned(),
)
.await?;
manager
.rename_table(
Table::rename()
.table(Alias::new("rpc_keys"), Alias::new("rpc_key"))
.to_owned(),
)
.await?;
// on rpc_key table, rename rpc_key to secret_key
manager
.alter_table(
Table::alter()
.table(Alias::new("rpc_key"))
.rename_column(Alias::new("rpc_key"), Alias::new("secret_key"))
.to_owned(),
)
.await?;
// on revert_log table, rename user_key_id to rpc_key_id
manager
.alter_table(
Table::alter()
.table(Alias::new("revert_log"))
.rename_column(Alias::new("user_key_id"), Alias::new("rpc_key_id"))
.to_owned(),
)
.await?;
// on rpc_accounting table, rename user_key_id to rpc_key_id
manager
.alter_table(
Table::alter()
.table(Alias::new("rpc_accounting"))
.rename_column(Alias::new("user_key_id"), Alias::new("rpc_key_id"))
.to_owned(),
)
.await?;
// on secondary_users table, remove "email" and "address" column
manager
.alter_table(
Table::alter()
.table(Alias::new("secondary_user"))
.drop_column(Alias::new("email"))
.drop_column(Alias::new("address"))
.to_owned(),
)
.await?;
// on user_tier table, rename requests_per_minute to max_requests_per_minute
manager
.alter_table(
Table::alter()
.table(Alias::new("user_tier"))
.rename_column(
Alias::new("requests_per_minute"),
Alias::new("max_requests_per_minute"),
)
.to_owned(),
)
.await?;
manager
.alter_table(
Table::alter()
.table(Alias::new("rpc_key"))
.drop_column(Alias::new("log_revert_chance"))
.add_column(
ColumnDef::new(Alias::new("log_revert_chance"))
.double()
.not_null()
.default(0.0),
)
.to_owned(),
)
.await?;
// rename column rpc_key to rpc_secret_key
Ok(())
}
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
// Replace the sample below with your own migration scripts
todo!();
}
}

@ -29,7 +29,6 @@ use metered::{metered, ErrorCount, HitCount, ResponseTime, Throughput};
use migration::{Migrator, MigratorTrait};
use moka::future::Cache;
use redis_rate_limiter::{DeadpoolRuntime, RedisConfig, RedisPool, RedisRateLimiter};
use sea_orm::prelude::Decimal;
use sea_orm::DatabaseConnection;
use serde::Serialize;
use serde_json::json;
@ -68,9 +67,9 @@ pub struct UserKeyData {
pub user_id: u64,
/// database id of the rpc key
pub rpc_key_id: u64,
/// if None, allow unlimited queries
/// if None, allow unlimited queries. inherited from the user_tier
pub max_requests_per_period: Option<u64>,
// if None, allow unlimited concurrent requests
// if None, allow unlimited concurrent requests. inherited from the user_tier
pub max_concurrent_requests: Option<u64>,
/// if None, allow any Origin
pub allowed_origins: Option<Vec<Origin>>,
@ -81,7 +80,8 @@ pub struct UserKeyData {
/// if None, allow any IP Address
pub allowed_ips: Option<Vec<IpNet>>,
/// Chance to save reverting eth_call, eth_estimateGas, and eth_sendRawTransaction to the database.
pub log_revert_chance: Decimal,
/// TODO: f32 would be fine
pub log_revert_chance: f64,
}
/// The application
@ -108,7 +108,8 @@ pub struct Web3ProxyApp {
pub frontend_key_rate_limiter: Option<DeferredRateLimiter<Ulid>>,
pub login_rate_limiter: Option<RedisRateLimiter>,
pub vredis_pool: Option<RedisPool>,
pub rpc_key_cache: Cache<Ulid, UserKeyData, hashbrown::hash_map::DefaultHashBuilder>,
// TODO: this key should be our RpcSecretKey class, not Ulid
pub rpc_secret_key_cache: Cache<Ulid, UserKeyData, hashbrown::hash_map::DefaultHashBuilder>,
pub rpc_key_semaphores: Cache<u64, Arc<Semaphore>, hashbrown::hash_map::DefaultHashBuilder>,
pub ip_semaphores: Cache<IpAddr, Arc<Semaphore>, hashbrown::hash_map::DefaultHashBuilder>,
pub bearer_token_semaphores:
@ -456,9 +457,9 @@ impl Web3ProxyApp {
// if there is no database of users, there will be no keys and so this will be empty
// TODO: max_capacity from config
// TODO: ttl from config
let rpc_key_cache = Cache::builder()
let rpc_secret_key_cache = Cache::builder()
.max_capacity(10_000)
.time_to_live(Duration::from_secs(60))
.time_to_live(Duration::from_secs(600))
.build_with_hasher(hashbrown::hash_map::DefaultHashBuilder::new());
// create semaphores for concurrent connection limits
@ -488,7 +489,7 @@ impl Web3ProxyApp {
vredis_pool,
app_metrics,
open_request_handle_metrics,
rpc_key_cache,
rpc_secret_key_cache,
bearer_token_semaphores,
ip_semaphores,
rpc_key_semaphores,

@ -267,7 +267,7 @@ mod tests {
let app_config = TopConfig {
app: AppConfig {
chain_id: 31337,
default_user_requests_per_minute: Some(6_000_000),
default_user_max_requests_per_minute: Some(6_000_000),
min_sum_soft_limit: 1,
min_synced_rpcs: 1,
public_requests_per_minute: Some(1_000_000),

@ -35,7 +35,7 @@ impl CheckConfigSubCommand {
Some(_) => {}
}
match top_config.app.default_user_requests_per_minute {
match top_config.app.default_user_max_requests_per_minute {
None => {
info!("app.default_user_requests_per_minute is None. Fully open to registered requests!")
}

@ -1,12 +1,12 @@
use anyhow::Context;
use argh::FromArgs;
use entities::{rpc_keys, user};
use entities::{rpc_key, user};
use ethers::prelude::Address;
use sea_orm::{ActiveModelTrait, TransactionTrait};
use tracing::info;
use ulid::Ulid;
use uuid::Uuid;
use web3_proxy::frontend::authorization::RpcApiKey;
use web3_proxy::frontend::authorization::RpcSecretKey;
#[derive(FromArgs, PartialEq, Debug, Eq)]
/// Create a new user and api key
@ -25,12 +25,7 @@ pub struct CreateUserSubCommand {
/// the user's first api ULID or UUID key.
/// If none given, one will be created.
#[argh(option)]
rpc_key: RpcApiKey,
/// the key's maximum requests per minute.
/// Default to "None" which the code sees as "unlimited" requests.
#[argh(option)]
rpm: Option<u64>,
rpc_secret_key: RpcSecretKey,
/// an optional short description of the key's purpose.
#[argh(option)]
@ -74,10 +69,9 @@ impl CreateUserSubCommand {
);
// create a key for the new user
let uk = rpc_keys::ActiveModel {
let uk = rpc_key::ActiveModel {
user_id: u.id,
rpc_key: sea_orm::Set(self.rpc_key.into()),
requests_per_minute: sea_orm::Set(self.rpm),
secret_key: sea_orm::Set(self.rpc_secret_key.into()),
description: sea_orm::Set(self.description),
..Default::default()
};
@ -87,8 +81,8 @@ impl CreateUserSubCommand {
txn.commit().await?;
info!("user key as ULID: {}", Ulid::from(self.rpc_key));
info!("user key as UUID: {}", Uuid::from(self.rpc_key));
info!("user key as ULID: {}", Ulid::from(self.rpc_secret_key));
info!("user key as UUID: {}", Uuid::from(self.rpc_secret_key));
Ok(())
}

@ -74,7 +74,7 @@ pub struct AppConfig {
/// Default request limit for registered users.
/// 0 = block all requests
/// None = allow all requests
pub default_user_requests_per_minute: Option<u64>,
pub default_user_max_requests_per_minute: Option<u64>,
/// Restrict user registration.
/// None = no code needed

@ -10,12 +10,12 @@ use axum::headers::{Header, Origin, Referer, UserAgent};
use axum::TypedHeader;
use chrono::Utc;
use deferred_rate_limiter::DeferredRateLimitResult;
use entities::{rpc_keys, user};
use entities::{rpc_key, user, user_tier};
use http::HeaderValue;
use ipnet::IpNet;
use redis_rate_limiter::redis::AsyncCommands;
use redis_rate_limiter::RedisRateLimitResult;
use sea_orm::{prelude::Decimal, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
use std::fmt::Display;
use std::sync::atomic::{AtomicBool, AtomicU64};
use std::{net::IpAddr, str::FromStr, sync::Arc};
@ -28,7 +28,7 @@ use uuid::Uuid;
/// This lets us use UUID and ULID while we transition to only ULIDs
/// TODO: include the key's description.
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum RpcApiKey {
pub enum RpcSecretKey {
Ulid(Ulid),
Uuid(Uuid),
}
@ -55,7 +55,7 @@ pub struct AuthorizedKey {
pub user_id: u64,
pub rpc_key_id: u64,
// TODO: just use an f32? even an f16 is probably fine
pub log_revert_chance: Decimal,
pub log_revert_chance: f64,
}
#[derive(Debug)]
@ -107,19 +107,19 @@ impl RequestMetadata {
}
}
impl RpcApiKey {
impl RpcSecretKey {
pub fn new() -> Self {
Ulid::new().into()
}
}
impl Default for RpcApiKey {
impl Default for RpcSecretKey {
fn default() -> Self {
Self::new()
}
}
impl Display for RpcApiKey {
impl Display for RpcSecretKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// TODO: do this without dereferencing
let ulid: Ulid = (*self).into();
@ -128,7 +128,7 @@ impl Display for RpcApiKey {
}
}
impl FromStr for RpcApiKey {
impl FromStr for RpcSecretKey {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -143,32 +143,32 @@ impl FromStr for RpcApiKey {
}
}
impl From<Ulid> for RpcApiKey {
impl From<Ulid> for RpcSecretKey {
fn from(x: Ulid) -> Self {
RpcApiKey::Ulid(x)
RpcSecretKey::Ulid(x)
}
}
impl From<Uuid> for RpcApiKey {
impl From<Uuid> for RpcSecretKey {
fn from(x: Uuid) -> Self {
RpcApiKey::Uuid(x)
RpcSecretKey::Uuid(x)
}
}
impl From<RpcApiKey> for Ulid {
fn from(x: RpcApiKey) -> Self {
impl From<RpcSecretKey> for Ulid {
fn from(x: RpcSecretKey) -> Self {
match x {
RpcApiKey::Ulid(x) => x,
RpcApiKey::Uuid(x) => Ulid::from(x.as_u128()),
RpcSecretKey::Ulid(x) => x,
RpcSecretKey::Uuid(x) => Ulid::from(x.as_u128()),
}
}
}
impl From<RpcApiKey> for Uuid {
fn from(x: RpcApiKey) -> Self {
impl From<RpcSecretKey> for Uuid {
fn from(x: RpcSecretKey) -> Self {
match x {
RpcApiKey::Ulid(x) => Uuid::from_u128(x.0),
RpcApiKey::Uuid(x) => x,
RpcSecretKey::Ulid(x) => Uuid::from_u128(x.0),
RpcSecretKey::Uuid(x) => x,
}
}
}
@ -297,7 +297,7 @@ pub async fn ip_is_authorized(
pub async fn key_is_authorized(
app: &Web3ProxyApp,
rpc_key: RpcApiKey,
rpc_key: RpcSecretKey,
ip: IpAddr,
origin: Option<Origin>,
referer: Option<Referer>,
@ -502,27 +502,32 @@ impl Web3ProxyApp {
// check the local cache for user data, or query the database
#[instrument(level = "trace")]
pub(crate) async fn user_data(&self, rpc_key: RpcApiKey) -> anyhow::Result<UserKeyData> {
pub(crate) async fn user_data(
&self,
rpc_secret_key: RpcSecretKey,
) -> anyhow::Result<UserKeyData> {
let user_data: Result<_, Arc<anyhow::Error>> = self
.rpc_key_cache
.try_get_with(rpc_key.into(), async move {
trace!(?rpc_key, "user_cache miss");
.rpc_secret_key_cache
.try_get_with(rpc_secret_key.into(), async move {
trace!(?rpc_secret_key, "user cache miss");
let db_conn = self.db_conn().context("Getting database connection")?;
let rpc_key: Uuid = rpc_key.into();
let rpc_secret_key: Uuid = rpc_secret_key.into();
// TODO: join the user table to this to return the User? we don't always need it
// TODO: also attach secondary users
match rpc_keys::Entity::find()
.filter(rpc_keys::Column::RpcKey.eq(rpc_key))
.filter(rpc_keys::Column::Active.eq(true))
// TODO: join on secondary users
// TODO: join on user tier
match rpc_key::Entity::find()
.filter(rpc_key::Column::SecretKey.eq(rpc_secret_key))
.filter(rpc_key::Column::Active.eq(true))
.one(&db_conn)
.await?
{
Some(rpc_key_model) => {
// TODO: move these splits into helper functions
// TODO: can we have sea orm handle this for us?
// let user_tier_model = rpc_key_model.
let allowed_ips: Option<Vec<IpNet>> =
if let Some(allowed_ips) = rpc_key_model.allowed_ips {
@ -575,16 +580,18 @@ impl Web3ProxyApp {
None
};
// let user_tier_model = user_tier
Ok(UserKeyData {
user_id: rpc_key_model.user_id,
rpc_key_id: rpc_key_model.id,
max_requests_per_period: rpc_key_model.requests_per_minute,
max_concurrent_requests: rpc_key_model.max_concurrent_requests,
allowed_ips,
allowed_origins,
allowed_referers,
allowed_user_agents,
log_revert_chance: rpc_key_model.log_revert_chance,
max_concurrent_requests: None, // todo! user_tier_model.max_concurrent_requests,
max_requests_per_period: None, // todo! user_tier_model.max_requests_per_period,
})
}
None => Ok(UserKeyData::default()),
@ -597,7 +604,10 @@ impl Web3ProxyApp {
}
#[instrument(level = "trace")]
pub async fn rate_limit_by_key(&self, rpc_key: RpcApiKey) -> anyhow::Result<RateLimitResult> {
pub async fn rate_limit_by_key(
&self,
rpc_key: RpcSecretKey,
) -> anyhow::Result<RateLimitResult> {
let user_data = self.user_data(rpc_key).await?;
if user_data.rpc_key_id == 0 {

@ -39,14 +39,14 @@ pub async fn prometheus(Extension(app): Extension<Arc<Web3ProxyApp>>) -> impl In
#[instrument(level = "trace")]
pub async fn status(Extension(app): Extension<Arc<Web3ProxyApp>>) -> impl IntoResponse {
app.pending_transactions.sync();
app.rpc_key_cache.sync();
app.rpc_secret_key_cache.sync();
// TODO: what else should we include? uptime, cache hit rates, cpu load
let body = json!({
"pending_transactions_count": app.pending_transactions.entry_count(),
"pending_transactions_size": app.pending_transactions.weighted_size(),
"user_cache_count": app.rpc_key_cache.entry_count(),
"user_cache_size": app.rpc_key_cache.weighted_size(),
"user_cache_count": app.rpc_secret_key_cache.entry_count(),
"user_cache_size": app.rpc_secret_key_cache.weighted_size(),
"balanced_rpcs": app.balanced_rpcs,
"private_rpcs": app.private_rpcs,
});

@ -1,6 +1,6 @@
//! Handle registration, logins, and managing account data.
use super::authorization::{login_is_authorized, RpcApiKey};
use super::authorization::{login_is_authorized, RpcSecretKey};
use super::errors::FrontendResult;
use crate::app::Web3ProxyApp;
use crate::user_queries::{
@ -18,7 +18,7 @@ use axum::{
};
use axum_client_ip::ClientIp;
use axum_macros::debug_handler;
use entities::{revert_logs, rpc_keys, user};
use entities::{revert_log, rpc_key, user};
use ethers::{prelude::Address, types::Bytes};
use hashbrown::HashMap;
use http::{HeaderValue, StatusCode};
@ -250,6 +250,8 @@ pub async fn user_login_post(
// the only thing we need from them is an address
// everything else is optional
// TODO: different invite codes should allow different levels
// TODO: maybe decrement a count on the invite code?
let u = user::ActiveModel {
address: sea_orm::Set(our_msg.address.into()),
..Default::default()
@ -259,14 +261,13 @@ pub async fn user_login_post(
// create the user's first api key
// TODO: rename to UserApiKey? RpcApiKey?
let rpc_key = RpcApiKey::new();
let rpc_secret_key = RpcSecretKey::new();
// TODO: variable requests per minute depending on the invite code
let uk = rpc_keys::ActiveModel {
let uk = rpc_key::ActiveModel {
user_id: sea_orm::Set(u.id),
rpc_key: sea_orm::Set(rpc_key.into()),
secret_key: sea_orm::Set(rpc_secret_key.into()),
description: sea_orm::Set(Some("first".to_string())),
requests_per_minute: sea_orm::Set(app.config.default_user_requests_per_minute),
..Default::default()
};
@ -284,8 +285,8 @@ pub async fn user_login_post(
}
Some(u) => {
// the user is already registered
let uks = rpc_keys::Entity::find()
.filter(rpc_keys::Column::UserId.eq(u.id))
let uks = rpc_key::Entity::find()
.filter(rpc_key::Column::UserId.eq(u.id))
.all(&db_conn)
.await
.context("failed loading user's key")?;
@ -460,8 +461,8 @@ pub async fn rpc_keys_get(
let db_conn = app.db_conn().context("getting db to fetch user's keys")?;
let uks = rpc_keys::Entity::find()
.filter(rpc_keys::Column::UserId.eq(user.id))
let uks = rpc_key::Entity::find()
.filter(rpc_key::Column::UserId.eq(user.id))
.all(&db_conn)
.await
.context("failed loading user's key")?;
@ -525,9 +526,9 @@ pub async fn rpc_keys_management(
let mut uk = if let Some(existing_key_id) = payload.key_id {
// get the key and make sure it belongs to the user
let uk = rpc_keys::Entity::find()
.filter(rpc_keys::Column::UserId.eq(user.id))
.filter(rpc_keys::Column::Id.eq(existing_key_id))
let uk = rpc_key::Entity::find()
.filter(rpc_key::Column::UserId.eq(user.id))
.filter(rpc_key::Column::Id.eq(existing_key_id))
.one(&db_conn)
.await
.context("failed loading user's key")?
@ -537,12 +538,11 @@ pub async fn rpc_keys_management(
} else {
// make a new key
// TODO: limit to 10 keys?
let rpc_key = RpcApiKey::new();
let secret_key = RpcSecretKey::new();
rpc_keys::ActiveModel {
rpc_key::ActiveModel {
user_id: sea_orm::Set(user.id),
rpc_key: sea_orm::Set(rpc_key.into()),
requests_per_minute: sea_orm::Set(app.config.default_user_requests_per_minute),
secret_key: sea_orm::Set(secret_key.into()),
..Default::default()
}
};
@ -671,7 +671,7 @@ pub async fn rpc_keys_management(
uk
};
let uk: rpc_keys::Model = uk.try_into()?;
let uk: rpc_key::Model = uk.try_into()?;
Ok(Json(uk).into_response())
}
@ -702,8 +702,8 @@ pub async fn user_revert_logs_get(
let db_conn = app.db_conn().context("getting db for user's revert logs")?;
let uks = rpc_keys::Entity::find()
.filter(rpc_keys::Column::UserId.eq(user.id))
let uks = rpc_key::Entity::find()
.filter(rpc_key::Column::UserId.eq(user.id))
.all(&db_conn)
.await
.context("failed loading user's key")?;
@ -712,17 +712,17 @@ pub async fn user_revert_logs_get(
let uks: Vec<_> = uks.into_iter().map(|x| x.id).collect();
// get paginated logs
let q = revert_logs::Entity::find()
.filter(revert_logs::Column::Timestamp.gte(query_start))
.filter(revert_logs::Column::RpcKeyId.is_in(uks))
.order_by_asc(revert_logs::Column::Timestamp);
let q = revert_log::Entity::find()
.filter(revert_log::Column::Timestamp.gte(query_start))
.filter(revert_log::Column::RpcKeyId.is_in(uks))
.order_by_asc(revert_log::Column::Timestamp);
let q = if chain_id == 0 {
// don't do anything
q
} else {
// filter on chain id
q.filter(revert_logs::Column::ChainId.eq(chain_id))
q.filter(revert_log::Column::ChainId.eq(chain_id))
};
let revert_logs = q.paginate(&db_conn, page_size).fetch_page(page).await?;

@ -402,7 +402,7 @@ impl Web3Connections {
// if we get here, something is wrong. clear synced connections
let empty_synced_connections = SyncedConnections::default();
let old_synced_connections = self
let _ = self
.synced_connections
.swap(Arc::new(empty_synced_connections));

@ -4,7 +4,7 @@ use crate::frontend::authorization::AuthorizedRequest;
use crate::metered::{JsonRpcErrorCount, ProviderErrorCount};
use anyhow::Context;
use chrono::Utc;
use entities::revert_logs;
use entities::revert_log;
use entities::sea_orm_active_enums::Method;
use ethers::providers::{HttpClientError, ProviderError, WsClientError};
use ethers::types::{Address, Bytes};
@ -12,9 +12,7 @@ use metered::metered;
use metered::HitCount;
use metered::ResponseTime;
use metered::Throughput;
use num_traits::cast::FromPrimitive;
use rand::Rng;
use sea_orm::prelude::Decimal;
use sea_orm::ActiveEnum;
use sea_orm::ActiveModelTrait;
use serde_json::json;
@ -94,7 +92,7 @@ impl AuthorizedRequest {
.expect("address should always convert to a Vec<u8>");
let call_data = params.data.map(|x| format!("{}", x));
let rl = revert_logs::ActiveModel {
let rl = revert_log::ActiveModel {
rpc_key_id: sea_orm::Set(authorized_request.rpc_key_id),
method: sea_orm::Set(method),
to: sea_orm::Set(to),
@ -222,16 +220,13 @@ impl OpenRequestHandle {
} else {
let log_revert_chance = y.log_revert_chance;
if log_revert_chance.is_zero() {
if log_revert_chance == 0.0 {
trace!(%method, "no chance. skipping save on revert");
RequestErrorHandler::DebugLevel
} else if log_revert_chance == Decimal::ONE {
} else if log_revert_chance == 1.0 {
trace!(%method, "gaurenteed chance. SAVING on revert");
error_handler
} else if Decimal::from_f32(rand::thread_rng().gen_range(0.0f32..=1.0))
.expect("f32 should always convert to a Decimal")
> log_revert_chance
{
} else if rand::thread_rng().gen_range(0.0f64..=1.0) > log_revert_chance {
trace!(%method, "missed chance. skipping save on revert");
RequestErrorHandler::DebugLevel
} else {

@ -4,7 +4,7 @@ use axum::{
TypedHeader,
};
use chrono::NaiveDateTime;
use entities::{rpc_accounting, rpc_keys};
use entities::{rpc_accounting, rpc_key};
use hashbrown::HashMap;
use migration::Expr;
use num::Zero;
@ -272,11 +272,11 @@ pub async fn get_aggregate_rpc_stats_from_params(
// TODO: are these joins correct?
// TODO: what about keys where they are the secondary users?
let q = q
.join(JoinType::InnerJoin, rpc_accounting::Relation::RpcKeys.def())
.column(rpc_keys::Column::UserId)
.group_by(rpc_keys::Column::UserId);
.join(JoinType::InnerJoin, rpc_accounting::Relation::RpcKey.def())
.column(rpc_key::Column::UserId)
.group_by(rpc_key::Column::UserId);
let condition = condition.add(rpc_keys::Column::UserId.eq(user_id));
let condition = condition.add(rpc_key::Column::UserId.eq(user_id));
(condition, q)
};
@ -394,21 +394,20 @@ pub async fn get_detailed_stats(
// TODO: move authentication here?
// TODO: what about keys where this user is a secondary user?
let q = q
.join(JoinType::InnerJoin, rpc_accounting::Relation::RpcKeys.def())
.column(rpc_keys::Column::UserId)
.group_by(rpc_keys::Column::UserId);
.join(JoinType::InnerJoin, rpc_accounting::Relation::RpcKey.def())
.column(rpc_key::Column::UserId)
.group_by(rpc_key::Column::UserId);
let condition = condition.add(rpc_keys::Column::UserId.eq(user_id));
let condition = condition.add(rpc_key::Column::UserId.eq(user_id));
let q = if rpc_key_id == 0 {
q.column(rpc_keys::Column::UserId)
.group_by(rpc_keys::Column::UserId)
q.column(rpc_key::Column::UserId)
.group_by(rpc_key::Column::UserId)
} else {
response.insert("rpc_key_id", serde_json::to_value(rpc_key_id)?);
// no need to group_by user_id when we are grouping by key_id
q.column(rpc_keys::Column::Id)
.group_by(rpc_keys::Column::Id)
q.column(rpc_key::Column::Id).group_by(rpc_key::Column::Id)
};
(condition, q)