Merge remote-tracking branch 'origin/devel' into devel

This commit is contained in:
Bryan Stitt 2023-05-23 14:44:40 -07:00
commit d83e2f7015
11 changed files with 373 additions and 38 deletions

@ -0,0 +1,49 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "admin_increase_balance_receipt")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Decimal(Some((20, 10)))")]
pub amount: Decimal,
pub admin_id: u64,
pub deposit_to_user_id: u64,
pub note: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::admin::Entity",
from = "Column::AdminId",
to = "super::admin::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
Admin,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::DepositToUserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
User,
}
impl Related<super::admin::Entity> for Entity {
fn to() -> RelationDef {
Relation::Admin.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -3,6 +3,7 @@
pub mod prelude;
pub mod admin;
pub mod admin_increase_balance_receipt;
pub mod admin_trail;
pub mod balance;
pub mod increase_on_chain_balance_receipt;

@ -1,6 +1,7 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.7
pub use super::admin::Entity as Admin;
pub use super::admin_increase_balance_receipt::Entity as AdminIncreaseBalanceReceipt;
pub use super::admin_trail::Entity as AdminTrail;
pub use super::balance::Entity as Balance;
pub use super::increase_on_chain_balance_receipt::Entity as IncreaseOnChainBalanceReceipt;

@ -27,6 +27,7 @@ mod m20230412_171916_modify_secondary_user_add_primary_user;
mod m20230422_172555_premium_downgrade_logic;
mod m20230511_161214_remove_columns_statsv2_origin_and_method;
mod m20230512_220213_allow_null_rpc_key_id_in_stats_v2;
mod m20230514_114803_admin_add_credits;
pub struct Migrator;
@ -51,16 +52,17 @@ impl MigratorTrait for Migrator {
Box::new(m20230125_204810_stats_v2::Migration),
Box::new(m20230130_124740_read_only_login_logic::Migration),
Box::new(m20230130_165144_prepare_admin_imitation_pre_login::Migration),
Box::new(m20230215_152254_admin_trail::Migration),
Box::new(m20230307_002623_migrate_rpc_accounting_to_rpc_accounting_v2::Migration),
Box::new(m20230205_130035_create_balance::Migration),
Box::new(m20230205_133755_create_referrals::Migration),
Box::new(m20230214_134254_increase_balance_transactions::Migration),
Box::new(m20230215_152254_admin_trail::Migration),
Box::new(m20230221_230953_track_spend::Migration),
Box::new(m20230307_002623_migrate_rpc_accounting_to_rpc_accounting_v2::Migration),
Box::new(m20230412_171916_modify_secondary_user_add_primary_user::Migration),
Box::new(m20230422_172555_premium_downgrade_logic::Migration),
Box::new(m20230511_161214_remove_columns_statsv2_origin_and_method::Migration),
Box::new(m20230512_220213_allow_null_rpc_key_id_in_stats_v2::Migration),
Box::new(m20230514_114803_admin_add_credits::Migration),
]
}
}

@ -0,0 +1,97 @@
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> {
manager
.create_table(
Table::create()
.table(AdminIncreaseBalanceReceipt::Table)
.if_not_exists()
.col(
ColumnDef::new(AdminIncreaseBalanceReceipt::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(AdminIncreaseBalanceReceipt::Amount)
.decimal_len(20, 10)
.not_null(),
)
.col(
ColumnDef::new(AdminIncreaseBalanceReceipt::AdminId)
.big_unsigned()
.not_null(),
)
.foreign_key(
ForeignKey::create()
.name("fk-admin_id")
.from(
AdminIncreaseBalanceReceipt::Table,
AdminIncreaseBalanceReceipt::AdminId,
)
.to(Admin::Table, Admin::Id),
)
.col(
ColumnDef::new(AdminIncreaseBalanceReceipt::DepositToUserId)
.big_unsigned()
.not_null(),
)
.foreign_key(
ForeignKey::create()
.name("fk-admin_deposits_to_user_id")
.from(
AdminIncreaseBalanceReceipt::Table,
AdminIncreaseBalanceReceipt::DepositToUserId,
)
.to(User::Table, User::Id),
)
.col(
ColumnDef::new(AdminIncreaseBalanceReceipt::Note)
.string()
.not_null(),
)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(
Table::drop()
.table(AdminIncreaseBalanceReceipt::Table)
.to_owned(),
)
.await
}
}
#[derive(Iden)]
enum Admin {
Table,
Id,
}
/// Learn more at https://docs.rs/sea-query#iden
#[derive(Iden)]
enum User {
Table,
Id,
}
/// Learn more at https://docs.rs/sea-query#iden
#[derive(Iden)]
enum AdminIncreaseBalanceReceipt {
Table,
Id,
Amount,
AdminId,
DepositToUserId,
Note,
}

@ -5,4 +5,4 @@
# https://github.com/INFURA/versus
# ./ethspam | ./versus --stop-after 100 "http://localhost:8544/" # Pipe into the endpoint ..., add a bearer token and all that
./ethspam http://127.0.0.1:8544 | ./versus --stop-after 100 http://localhost:8544
./ethspam http://127.0.0.1:8544/rpc/01H0ZZJDNNEW49FRFS4D9SPR8B | ./versus --concurrency=4 --stop-after 100 http://localhost:8544/rpc/01H0ZZJDNNEW49FRFS4D9SPR8B

@ -24,14 +24,14 @@ curl -X POST http://127.0.0.1:8544/user/login \
-H 'Content-Type: application/json' \
-d '{
"address": "0xeb3e928a2e54be013ef8241d4c9eaf4dfae94d5a",
"msg": "0x6c6c616d616e6f6465732e636f6d2077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078654233453932384132453534424530313345463832343164344339456146344466414539344435610a0af09fa699f09fa699f09fa699f09fa699f09fa6990a0a5552493a2068747470733a2f2f6c6c616d616e6f6465732e636f6d2f0a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a203031475a4b384b4847305259474737514e5132475037464444470a4973737565642041743a20323032332d30352d30345431313a33333a32312e3533363734355a0a45787069726174696f6e2054696d653a20323032332d30352d30345431313a35333a32312e3533363734355a",
"sig": "cebd9effff15f4517e53522dbe91798d59dc0df0299faaec25d3f6443fa121f847e4311d5ca7386e75b87d6d45df92b8ced58c822117519c666ab1a6b2fc7bd21b",
"msg": "0x6c6c616d616e6f6465732e636f6d2077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078654233453932384132453534424530313345463832343164344339456146344466414539344435610a0af09fa699f09fa699f09fa699f09fa699f09fa6990a0a5552493a2068747470733a2f2f6c6c616d616e6f6465732e636f6d2f0a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a20303148305a5a48434356324b32324738544850535758485131480a4973737565642041743a20323032332d30352d32315432303a32303a34332e3033353539315a0a45787069726174696f6e2054696d653a20323032332d30352d32315432303a34303a34332e3033353539315a",
"sig": "7591251840bf75d2ab7c895bc566a49d2f4c3ad6bb14d7256258a59e52055fc94c11f8f3836f5311b52fc18aca40867cd85802636645e1d757494800631cad381c",
"version": "3",
"signer": "MEW"
}'
# bearer token is: 01GZK8MHHGQWK4VPGF97HS91MB
# scret key is: 01GZK65YNV0P0WN2SCXYTW3R9S
# bearer token is: 01H0ZZJDQ2F02MAXZR5K1X5NCP
# scret key is: 01H0ZZJDNNEW49FRFS4D9SPR8B
# 01GZH2PS89EJJY6V8JFCVTQ4BX
# 01GZH2PS7CTHA3TAZ4HXCTX6KQ
@ -42,7 +42,7 @@ curl -X POST http://127.0.0.1:8544/user/login \
# Check the balance of the user
# Balance seems to be returning properly (0, in this test case)
curl \
-H "Authorization: Bearer 01GZK8MHHGQWK4VPGF97HS91MB" \
-H "Authorization: Bearer 01H0ZZJDQ2F02MAXZR5K1X5NCP" \
-X GET "127.0.0.1:8544/user/balance"
@ -73,10 +73,10 @@ curl \
## Check if calling an RPC endpoint logs the stats
## This one does already even it seems
for i in {1..100}
for i in {1..300}
do
curl \
-X POST "127.0.0.1:8544/rpc/01GZK65YNV0P0WN2SCXYTW3R9S" \
-X POST "127.0.0.1:8544/rpc/01H0ZZJDNNEW49FRFS4D9SPR8B" \
-H "Content-Type: application/json" \
--data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}'
done

@ -0,0 +1,44 @@
# Create / Login user1
curl -X GET "http://127.0.0.1:8544/user/login/0xeb3e928a2e54be013ef8241d4c9eaf4dfae94d5a"
curl -X POST http://127.0.0.1:8544/user/login \
-H 'Content-Type: application/json' \
-d '{
"address": "0xeb3e928a2e54be013ef8241d4c9eaf4dfae94d5a",
"msg": "0x6c6c616d616e6f6465732e636f6d2077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078654233453932384132453534424530313345463832343164344339456146344466414539344435610a0af09fa699f09fa699f09fa699f09fa699f09fa6990a0a5552493a2068747470733a2f2f6c6c616d616e6f6465732e636f6d2f0a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a203031483044573642334a48355a4b384a4e3947504d594e4d4b370a4973737565642041743a20323032332d30352d31345431393a33353a35352e3736323632395a0a45787069726174696f6e2054696d653a20323032332d30352d31345431393a35353a35352e3736323632395a",
"sig": "f88b42d638246f8e51637c753052cab3a13b2a138faf3107c921ce2f0027d6506b9adcd3a7b72af830cdf50d20e6e9cb3f9f456dd1be47f6543990ea050909791c",
"version": "3",
"signer": "MEW"
}'
# 01H0DW6VFCP365B9TXVQVVMHHY
# 01H0DVZNDJWQ7YG8RBHXQHJ301
# Make user1 an admin
cargo run change_admin_status 0xeB3E928A2E54BE013EF8241d4C9EaF4DfAE94D5a true
# Create/Login user2
curl -X GET "http://127.0.0.1:8544/user/login/0x762390ae7a3c4D987062a398C1eA8767029AB08E"
curl -X POST http://127.0.0.1:8544/user/login \
-H 'Content-Type: application/json' \
-d '{
"address": "0x762390ae7a3c4d987062a398c1ea8767029ab08e",
"msg": "0x6c6c616d616e6f6465732e636f6d2077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078373632333930616537613363344439383730363261333938433165413837363730323941423038450a0af09fa699f09fa699f09fa699f09fa699f09fa6990a0a5552493a2068747470733a2f2f6c6c616d616e6f6465732e636f6d2f0a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a20303148304457384233304e534447594e484d33514d4a31434e530a4973737565642041743a20323032332d30352d31345431393a33373a30312e3238303338355a0a45787069726174696f6e2054696d653a20323032332d30352d31345431393a35373a30312e3238303338355a",
"sig": "c545235557b7952a789dffa2af153af5cf663dcc05449bcc4b651b04cda57de05bcef55c0f5cbf6aa2432369582eb6a40927d14ad0a2d15f48fa45f32fbf273f1c",
"version": "3",
"signer": "MEW"
}'
# 01H0DWPXRQA7XX2VFSNR02CG1N
# 01H0DWPXQQ951Y3R90QMF6MYGE
curl \
-H "Authorization: Bearer 01H0DWPXRQA7XX2VFSNR02CG1N" \
-X GET "127.0.0.1:8544/user/balance"
# Admin add balance
curl \
-H "Authorization: Bearer 01H0DW6VFCP365B9TXVQVVMHHY" \
-X GET "127.0.0.1:8544/admin/increase_balance?user_address=0x762390ae7a3c4D987062a398C1eA8767029AB08E&amount=100.0"

@ -5,8 +5,11 @@ use super::errors::Web3ProxyResponse;
use crate::admin_queries::query_admin_modify_usertier;
use crate::app::Web3ProxyApp;
use crate::frontend::errors::{Web3ProxyError, Web3ProxyErrorContext};
use crate::http_params::get_user_id_from_params;
use crate::user_token::UserBearerToken;
use crate::PostLogin;
use anyhow::Context;
use axum::body::HttpBody;
use axum::{
extract::{Path, Query},
headers::{authorization::Bearer, Authorization},
@ -16,15 +19,20 @@ use axum::{
use axum_client_ip::InsecureClientIp;
use axum_macros::debug_handler;
use chrono::{TimeZone, Utc};
use entities::{admin_trail, login, pending_login, rpc_key, user};
use entities::{
admin, admin_increase_balance_receipt, admin_trail, balance, login, pending_login, rpc_key,
user, user_tier,
};
use ethers::{prelude::Address, types::Bytes};
use hashbrown::HashMap;
use http::StatusCode;
use log::{debug, info, warn};
use migration::sea_orm::prelude::Uuid;
use migration::sea_orm::prelude::{Decimal, Uuid};
use migration::sea_orm::{
self, ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter,
self, ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter,
TransactionTrait, Update,
};
use migration::{ConnectionTrait, Expr, OnConflict};
use serde_json::json;
use siwe::{Message, VerificationOpts};
use std::ops::Add;
@ -33,6 +41,138 @@ use std::sync::Arc;
use time::{Duration, OffsetDateTime};
use ulid::Ulid;
/// `GET /admin/increase_balance` -- As an admin, modify a user's user-tier
///
/// - user_address that is to credited balance
/// - user_role_tier that is supposed to be adapted
#[debug_handler]
pub async fn admin_increase_balance(
Extension(app): Extension<Arc<Web3ProxyApp>>,
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
Query(params): Query<HashMap<String, String>>,
) -> Web3ProxyResponse {
let (caller, _) = app.bearer_is_authorized(bearer).await?;
let caller_id = caller.id;
// Establish connections
let db_conn = app
.db_conn()
.context("query_admin_modify_user needs a db")?;
// Check if the caller is an admin (if not, return early)
let admin_entry: admin::Model = admin::Entity::find()
.filter(admin::Column::UserId.eq(caller_id))
.one(&db_conn)
.await?
.ok_or(Web3ProxyError::AccessDenied)?;
// Get the user from params
let user_address: Address = params
.get("user_address")
.ok_or_else(|| {
Web3ProxyError::BadRequest("Unable to find user_address key in request".to_string())
})?
.parse::<Address>()
.map_err(|_| {
Web3ProxyError::BadRequest("Unable to parse user_address as an Address".to_string())
})?;
let user_address_bytes: Vec<u8> = user_address.clone().to_fixed_bytes().into();
let note: String = params
.get("note")
.ok_or_else(|| {
Web3ProxyError::BadRequest("Unable to find 'note' key in request".to_string())
})?
.parse::<String>()
.map_err(|_| {
Web3ProxyError::BadRequest("Unable to parse 'note' as a String".to_string())
})?;
// Get the amount from params
// Decimal::from_str
let amount: Decimal = params
.get("amount")
.ok_or_else(|| {
Web3ProxyError::BadRequest("Unable to get the amount key from the request".to_string())
})
.map(|x| Decimal::from_str(x))?
.or_else(|err| {
Err(Web3ProxyError::BadRequest(format!(
"Unable to parse amount from the request {:?}",
err
)))
})?;
let user_entry: user::Model = user::Entity::find()
.filter(user::Column::Address.eq(user_address_bytes.clone()))
.one(&db_conn)
.await?
.ok_or(Web3ProxyError::BadRequest(
"No user with this id found".to_string(),
))?;
let increase_balance_receipt = admin_increase_balance_receipt::ActiveModel {
amount: sea_orm::Set(amount),
admin_id: sea_orm::Set(admin_entry.id),
deposit_to_user_id: sea_orm::Set(user_entry.id),
note: sea_orm::Set(note),
..Default::default()
};
increase_balance_receipt.save(&db_conn).await?;
let mut out = HashMap::new();
out.insert(
"user",
serde_json::Value::String(format!("{:?}", user_address)),
);
out.insert("amount", serde_json::Value::String(amount.to_string()));
// Get the balance row
let balance_entry: balance::Model = balance::Entity::find()
.filter(balance::Column::UserId.eq(user_entry.id))
.one(&db_conn)
.await?
.context("User does not have a balance row")?;
// Finally make the user premium if balance is above 10$
let premium_user_tier = user_tier::Entity::find()
.filter(user_tier::Column::Title.eq("Premium"))
.one(&db_conn)
.await?
.context("Premium tier was not found!")?;
let balance_entry = balance_entry.into_active_model();
balance::Entity::insert(balance_entry)
.on_conflict(
OnConflict::new()
.values([
// (
// balance::Column::Id,
// Expr::col(balance::Column::Id).add(self.frontend_requests),
// ),
(
balance::Column::AvailableBalance,
Expr::col(balance::Column::AvailableBalance).add(amount),
),
// (
// balance::Column::Used,
// Expr::col(balance::Column::UsedBalance).add(self.backend_retries),
// ),
// (
// balance::Column::UserId,
// Expr::col(balance::Column::UserId).add(self.no_servers),
// ),
])
.to_owned(),
)
.exec(&db_conn)
.await?;
// TODO: Downgrade otherwise, right now not functioning properly
// Then read and save in one transaction
let response = (StatusCode::OK, Json(out)).into_response();
Ok(response)
}
/// `GET /admin/modify_role` -- As an admin, modify a user's user-tier
///
/// - user_address that is to be modified

@ -201,6 +201,10 @@ pub async fn serve(
"/user/logout",
post(users::authentication::user_logout_post),
)
.route(
"/admin/increase_balance",
get(admin::admin_increase_balance),
)
.route("/admin/modify_role", get(admin::admin_change_user_roles))
.route(
"/admin/imitate-login/:admin_address/:user_address",

@ -73,7 +73,9 @@ pub async fn query_user_stats<'a>(
let mut join_candidates: Vec<String> = vec![
"_time".to_string(),
"_measurement".to_string(),
"archive_needed".to_string(),
"chain_id".to_string(),
"error_response".to_string(),
];
// Include a hashmap to go from rpc_secret_key_id to the rpc_secret_key
@ -184,35 +186,18 @@ pub async fn query_user_stats<'a>(
{filter_chain_id}
{drop_method}
// cumsum = base
base
|> aggregateWindow(every: {query_window_seconds}s, fn: sum, createEmpty: false)
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
|> drop(columns: ["balance"])
|> map(fn: (r) => ({{ r with "archive_needed": if r.archive_needed == "true" then r.frontend_requests else 0}}))
|> map(fn: (r) => ({{ r with "error_response": if r.error_response == "true" then r.frontend_requests else 0}}))
|> group(columns: ["_time", "_measurement", "chain_id", "method", "rpc_secret_key_id"])
|> group(columns: ["_time", "_measurement", "archive_needed", "chain_id", "error_response", "method", "rpc_secret_key_id"])
|> sort(columns: ["frontend_requests"])
|> map(fn:(r) => ({{ r with "sum_credits_used": float(v: r["sum_credits_used"]) }}))
|> cumulativeSum(columns: ["archive_needed", "error_response", "backend_requests", "cache_hits", "cache_misses", "frontend_requests", "sum_credits_used", "sum_request_bytes", "sum_response_bytes", "sum_response_millis"])
|> cumulativeSum(columns: ["backend_requests", "cache_hits", "cache_misses", "frontend_requests", "sum_credits_used", "sum_request_bytes", "sum_response_bytes", "sum_response_millis"])
|> sort(columns: ["frontend_requests"], desc: true)
|> limit(n: 1)
|> group()
|> sort(columns: ["_time", "_measurement", "chain_id", "method", "rpc_secret_key_id"], desc: true)
// balance = base
// |> toFloat()
// |> aggregateWindow(every: {query_window_seconds}s, fn: mean, createEmpty: false)
// |> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
// |> group(columns: ["_time", "_measurement", "chain_id", "method", "rpc_secret_key_id"])
// |> mean(column: "balance")
// |> group()
// |> sort(columns: ["_time", "_measurement", "chain_id", "method", "rpc_secret_key_id"], desc: true)
// join(
// tables: {{cumsum, balance}},
// on: {join_candidates}
// )
|> sort(columns: ["_time", "_measurement", "archive_needed", "chain_id", "error_response", "method", "rpc_secret_key_id"], desc: true)
"#);
info!("Raw query to db is: {:?}", query);
@ -438,22 +423,34 @@ pub async fn query_user_stats<'a>(
}
} else if key == "archive_needed" {
match value {
influxdb2_structmap::value::Value::Long(inner) => {
influxdb2_structmap::value::Value::String(inner) => {
out.insert(
"archive_needed".to_owned(),
serde_json::Value::Number(inner.into()),
if inner == "true" {
serde_json::Value::Bool(true)
} else if inner == "false" {
serde_json::Value::Bool(false)
} else {
serde_json::Value::String("error".to_owned())
},
);
}
_ => {
error!("archive_needed should always be a Long!");
error!("archive_needed should always be a String!");
}
}
} else if key == "error_response" {
match value {
influxdb2_structmap::value::Value::Long(inner) => {
influxdb2_structmap::value::Value::String(inner) => {
out.insert(
"error_response".to_owned(),
serde_json::Value::Number(inner.into()),
if inner == "true" {
serde_json::Value::Bool(true)
} else if inner == "false" {
serde_json::Value::Bool(false)
} else {
serde_json::Value::String("error".to_owned())
},
);
}
_ => {