Admin and opertor features #3

Merged
noormohammedb merged 6 commits from general_and_admin into main 2025-05-25 21:40:46 +00:00
5 changed files with 126 additions and 10 deletions
Showing only changes of commit ff03ec6085 - Show all commits

@ -75,6 +75,38 @@ impl Account {
op_account.save(db).await?; op_account.save(db).await?;
Ok(()) Ok(())
} }
pub async fn slash_account(
db: &Surreal<Client>,
account: &str,
slash_amount: u64,
) -> Result<(), Error> {
let tx_query = "
BEGIN TRANSACTION;
LET $account = $account_input;
UPDATE $account SET escrow -= $slash_amount;
IF $account.escrow < 0 {{
THROW 'Insufficient escrow.'
}};
COMMIT TRANSACTION;";
let mut query_resp = db
.query(tx_query)
.bind(("account_input", RecordId::from((ACCOUNT, account))))
.bind(("slash_amount", slash_amount.saturating_mul(TOKEN_DECIMAL)))
.await?;
log::trace!("query_resp: {query_resp:?}");
let query_error = query_resp.take_errors();
if !query_error.is_empty() {
log::error!("slash_account query error: {query_error:?}");
return Err(Error::FailedToSlashOperator(account.to_string()));
}
Ok(())
}
} }
impl Account { impl Account {
@ -105,6 +137,9 @@ impl Account {
let vm_node_ban: Option<Ban> = query_response.take(0).unwrap(); let vm_node_ban: Option<Ban> = query_response.take(0).unwrap();
let app_node_ban: Option<Ban> = query_response.take(1).unwrap(); let app_node_ban: Option<Ban> = query_response.take(1).unwrap();
log::trace!("vm_node_ban: {vm_node_ban:?}");
log::trace!("app_node_ban: {app_node_ban:?}");
Ok(vm_node_ban.is_some() || app_node_ban.is_some()) Ok(vm_node_ban.is_some() || app_node_ban.is_some())
} }
} }
@ -162,6 +197,7 @@ impl Ban {
user_wallet: &str, user_wallet: &str,
) -> Result<(), Error> { ) -> Result<(), Error> {
if Self::get_record_by_wallets(db, op_wallet, user_wallet).await?.is_some() { if Self::get_record_by_wallets(db, op_wallet, user_wallet).await?.is_some() {
log::error!("User {user_wallet} is already banned by {op_wallet}");
return Err(Error::AlreadyBanned(op_wallet.to_string())); return Err(Error::AlreadyBanned(op_wallet.to_string()));
} }
let _: Vec<Self> = db let _: Vec<Self> = db

@ -47,6 +47,8 @@ pub enum Error {
FailedKickContract(String), FailedKickContract(String),
#[error("Already banned {0}")] #[error("Already banned {0}")]
AlreadyBanned(String), AlreadyBanned(String),
#[error("Failed to slash operator {0}")]
FailedToSlashOperator(String),
} }
pub mod prelude { pub mod prelude {

@ -154,6 +154,7 @@ impl BrainGeneralCli for GeneralCliServer {
async fn ban_user(&self, req: Request<BanUserReq>) -> Result<Response<Empty>, Status> { async fn ban_user(&self, req: Request<BanUserReq>) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?; let req = check_sig_from_req(req)?;
log::info!("Banning user: {}, by: {}", req.user_wallet, req.operator_wallet);
db::Ban::ban_a_user(&self.db, &req.operator_wallet, &req.user_wallet).await?; db::Ban::ban_a_user(&self.db, &req.operator_wallet, &req.user_wallet).await?;
Ok(Response::new(Empty {})) Ok(Response::new(Empty {}))
} }
@ -163,16 +164,16 @@ impl BrainGeneralCli for GeneralCliServer {
async fn airdrop(&self, req: Request<AirdropReq>) -> Result<Response<Empty>, Status> { async fn airdrop(&self, req: Request<AirdropReq>) -> Result<Response<Empty>, Status> {
check_admin_key(&req)?; check_admin_key(&req)?;
let req = check_sig_from_req(req)?; let req = check_sig_from_req(req)?;
log::info!("Airdropping {} tokens to {}", req.tokens, req.pubkey);
db::Account::airdrop(&self.db, &req.pubkey, req.tokens).await?; db::Account::airdrop(&self.db, &req.pubkey, req.tokens).await?;
Ok(Response::new(Empty {})) Ok(Response::new(Empty {}))
} }
async fn slash(&self, _req: Request<SlashReq>) -> Result<Response<Empty>, Status> { async fn slash(&self, req: Request<SlashReq>) -> Result<Response<Empty>, Status> {
todo!(); check_admin_key(&req)?;
// check_admin_key(&req)?; let req = check_sig_from_req(req)?;
// let req = check_sig_from_req(req)?; db::Account::slash_account(&self.db, &req.pubkey, req.tokens).await?;
// self.data.slash_account(&req.pubkey, req.tokens); Ok(Response::new(Empty {}))
// Ok(Response::new(Empty {}))
} }
async fn list_accounts( async fn list_accounts(

@ -2,7 +2,7 @@ use super::test_utils::{admin_keys, Key};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use detee_shared::common_proto::Empty; use detee_shared::common_proto::Empty;
use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient; use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient;
use detee_shared::general_proto::{AirdropReq, ReportNodeReq}; use detee_shared::general_proto::{AirdropReq, RegOperatorReq, ReportNodeReq};
use detee_shared::vm_proto; use detee_shared::vm_proto;
use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient;
use surreal_brain::constants::{ACTIVE_VM, NEW_VM_REQ}; use surreal_brain::constants::{ACTIVE_VM, NEW_VM_REQ};
@ -77,3 +77,12 @@ pub async fn report_node(
Ok(client_gen_cli.report_node(key.sign_request(report_req)?).await?) Ok(client_gen_cli.report_node(key.sign_request(report_req)?).await?)
} }
pub async fn register_operator(brain_channel: &Channel, key: &Key, escrow: u64) -> Result<()> {
let mut cli_client = BrainGeneralCliClient::new(brain_channel.clone());
let reg_req =
RegOperatorReq { pubkey: key.pubkey.clone(), escrow, email: "foo@bar.com".to_string() };
cli_client.register_operator(key.sign_request(reg_req.clone()).unwrap()).await?;
Ok(())
}

@ -2,14 +2,14 @@ use common::prepare_test_env::{
prepare_test_db, run_service_for_stream, run_service_in_background, prepare_test_db, run_service_for_stream, run_service_in_background,
}; };
use common::test_utils::{admin_keys, Key}; use common::test_utils::{admin_keys, Key};
use common::vm_cli_utils::{airdrop, create_new_vm, report_node}; use common::vm_cli_utils::{airdrop, create_new_vm, register_operator, report_node};
use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node}; use common::vm_daemon_utils::{mock_vm_daemon, register_vm_node};
use detee_shared::common_proto::{Empty, Pubkey}; use detee_shared::common_proto::{Empty, Pubkey};
use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient; use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient;
use detee_shared::general_proto::{AirdropReq, BanUserReq}; use detee_shared::general_proto::{AirdropReq, BanUserReq, SlashReq};
use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient; use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient;
use futures::StreamExt; use futures::StreamExt;
use surreal_brain::constants::{BAN, TOKEN_DECIMAL, VM_NODE}; use surreal_brain::constants::{ACCOUNT, BAN, TOKEN_DECIMAL, VM_NODE};
use surreal_brain::db::prelude as db; use surreal_brain::db::prelude as db;
use surreal_brain::db::vm::VmNodeWithReports; use surreal_brain::db::vm::VmNodeWithReports;
@ -210,6 +210,37 @@ async fn test_inspect_operator() {
assert_eq!(&inspect_response.vm_nodes[0].operator, &operator_key.pubkey); assert_eq!(&inspect_response.vm_nodes[0].operator, &operator_key.pubkey);
} }
#[tokio::test]
async fn test_register_operator() {
let db_conn = prepare_test_db().await.unwrap();
let brain_channel = run_service_for_stream().await.unwrap();
let key = Key::new();
let min_escrew_error = register_operator(&brain_channel, &key, 10).await.err().unwrap();
assert!(min_escrew_error.to_string().contains("Minimum escrow amount is 5000"));
let no_balance = register_operator(&brain_channel, &key, 5000).await.err().unwrap();
assert!(no_balance.to_string().contains("Insufficient funds, deposit more tokens"));
airdrop(&brain_channel, &key.pubkey, 1000).await.unwrap();
let no_balance = register_operator(&brain_channel, &key, 5000).await.err().unwrap();
assert!(no_balance.to_string().contains("Insufficient funds, deposit more tokens"));
airdrop(&brain_channel, &key.pubkey, 7000).await.unwrap();
register_operator(&brain_channel, &key, 6000).await.unwrap();
let operator_account: Option<db::Account> =
db_conn.select((ACCOUNT, key.pubkey)).await.unwrap();
assert!(operator_account.is_some());
let account = operator_account.unwrap();
assert_eq!(account.escrow, 6000 * TOKEN_DECIMAL);
assert_eq!(account.balance, 2000 * TOKEN_DECIMAL);
assert_eq!(account.tmp_locked, 0);
}
#[tokio::test] #[tokio::test]
async fn test_kick_contract() { async fn test_kick_contract() {
// TODO: implement seed data to test // TODO: implement seed data to test
@ -308,3 +339,40 @@ async fn test_ban_user() {
assert!(operator_banned_you.to_string().contains("This operator banned you")); assert!(operator_banned_you.to_string().contains("This operator banned you"));
} }
#[tokio::test]
async fn test_slash_operator() {
let db_conn = prepare_test_db().await.unwrap();
let brain_channel = run_service_for_stream().await.unwrap();
let mut cli_client = BrainGeneralCliClient::new(brain_channel.clone());
let op_key = Key::new();
let admin = admin_keys()[0].clone();
let escrew = 5500;
let slash_amt = 2500;
airdrop(&brain_channel, &op_key.pubkey, 10000).await.unwrap();
register_operator(&brain_channel, &op_key, escrew).await.unwrap();
let raw_slash_req = SlashReq { pubkey: op_key.pubkey.clone(), tokens: 2500 };
let other_key = Key::new();
let admin_error = cli_client
.slash(other_key.sign_request(raw_slash_req.clone()).unwrap())
.await
.err()
.unwrap();
assert!(admin_error.message().contains("This operation is reserved to admin accounts"));
let admin_error =
cli_client.slash(op_key.sign_request(raw_slash_req.clone()).unwrap()).await.err().unwrap();
assert!(admin_error.message().contains("This operation is reserved to admin accounts"));
cli_client.slash(admin.sign_request(raw_slash_req.clone()).unwrap()).await.unwrap();
let operator_account: Option<db::Account> =
db_conn.select((ACCOUNT, op_key.pubkey)).await.unwrap();
assert!(operator_account.is_some());
let account = operator_account.unwrap();
assert_eq!(account.escrow, (escrew - slash_amt) * TOKEN_DECIMAL);
}