Admin and opertor features #3
@ -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);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user