use super::Error; use crate::constants::{ACCOUNT, KICK, MIN_ESCROW, TOKEN_DECIMAL}; use crate::db::prelude::*; use crate::old_brain; use serde::{Deserialize, Serialize}; use surrealdb::engine::remote::ws::Client; use surrealdb::sql::Datetime; use surrealdb::{RecordId, Surreal}; #[derive(Debug, Serialize, Deserialize)] pub struct Account { pub id: RecordId, pub balance: u64, pub tmp_locked: u64, pub escrow: u64, pub email: String, } impl Account { pub async fn get(db: &Surreal, address: &str) -> Result { let id = (ACCOUNT, address); let account: Option = db.select(id).await?; let account = match account { Some(account) => account, None => { Self { id: id.into(), balance: 0, tmp_locked: 0, escrow: 0, email: String::new() } } }; Ok(account) } pub async fn get_or_create(db: &Surreal, address: &str) -> Result { let id = (ACCOUNT, address); match db.select(id).await? { Some(account) => Ok(account), None => { let account: Option = db.create(id).await?; account.ok_or(Error::FailedToCreateDBEntry(ACCOUNT.to_string())) } } } pub async fn airdrop(db: &Surreal, account: &str, tokens: u64) -> Result<(), Error> { let tokens = tokens.saturating_mul(1_000_000_000); let _ = db .query(format!("upsert account:{account} SET balance = (balance || 0) + {tokens};")) .await?; Ok(()) } pub async fn save(self, db: &Surreal) -> Result, Error> { let account: Option = db.upsert(self.id.clone()).content(self).await?; Ok(account) } pub async fn operator_reg( db: &Surreal, wallet: &str, email: &str, escrow: u64, ) -> Result<(), Error> { if escrow < MIN_ESCROW { return Err(Error::MinimalEscrow); } let mut op_account = Self::get(db, wallet).await?; let escrow = escrow.saturating_mul(TOKEN_DECIMAL); let op_total_balance = op_account.balance.saturating_add(op_account.escrow); if op_total_balance < escrow { return Err(Error::InsufficientFunds); } op_account.email = email.to_string(); op_account.balance = op_total_balance.saturating_sub(escrow); op_account.escrow = escrow; op_account.save(db).await?; Ok(()) } } impl Account { pub async fn is_banned_by_node( db: &Surreal, user: &str, node: &str, ) -> Result { let mut query_response = db .query(format!( "(select operator->ban[0] as ban from vm_node:{node} where operator->ban->account contains account:{user} ).ban;" )) .query(format!( "(select operator->ban[0] as ban from app_node:{node} where operator->ban->account contains account:{user} ).ban;" )) .await?; let vm_node_ban: Option = query_response.take(0)?; let app_node_ban: Option = query_response.take(1)?; Ok(vm_node_ban.is_some() || app_node_ban.is_some()) } } impl From<&old_brain::BrainData> for Vec { fn from(old_data: &old_brain::BrainData) -> Self { let mut accounts = Vec::new(); for old_account in old_data.accounts.iter() { let mut a = Account { id: RecordId::from(("account", old_account.key())), balance: old_account.value().balance, tmp_locked: old_account.value().tmp_locked, escrow: 0, email: String::new(), }; if let Some(operator) = old_data.operators.get(old_account.key()) { a.escrow = operator.escrow; a.email = operator.email.clone(); } accounts.push(a); } accounts } } #[derive(Debug, Serialize, Deserialize)] pub struct Ban { id: RecordId, #[serde(rename = "in")] from_account: RecordId, #[serde(rename = "out")] to_account: RecordId, created_at: Datetime, } #[derive(Debug, Serialize, Deserialize)] pub struct Kick { id: RecordId, #[serde(rename = "in")] from_account: RecordId, #[serde(rename = "out")] to_account: RecordId, created_at: Datetime, reason: String, contract: RecordId, node: RecordId, } impl Kick { pub async fn kicked_in_a_day(db: &Surreal, account: &str) -> Result, Error> { let yesterday = chrono::Utc::now() - chrono::Duration::days(1); let mut result = db .query(format!( "select * from {KICK} where in = {ACCOUNT}:{account} and created_at > {yesterday};" )) .await?; let kicks: Vec = result.take(0)?; Ok(kicks) } pub async fn submit(self, db: &Surreal) -> Result<(), Error> { let _: Vec = db.insert(KICK).relation(self).await?; Ok(()) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Report { #[serde(rename = "in")] from_account: RecordId, #[serde(rename = "out")] to_node: RecordId, created_at: Datetime, pub reason: String, pub contract_id: String, } impl Report { // TODO: test this functionality and remove this comment pub async fn create( db: &Surreal, from_account: RecordId, to_node: RecordId, reason: String, contract_id: String, ) -> Result<(), Error> { let _: Vec = db .insert("report") .relation(Report { from_account, to_node, created_at: Datetime::default(), reason, contract_id, }) .await?; Ok(()) } } /// This is the operator obtained from the DB, /// however the relation is defined using OperatorRelation #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Operator { pub account: RecordId, pub app_nodes: u64, pub vm_nodes: u64, pub email: String, pub escrow: u64, pub reports: u64, } impl Operator { pub async fn list(db: &Surreal) -> Result, Error> { let mut result = db .query( "array::distinct(array::flatten( [ (select operator from vm_node group by operator).operator, (select operator from app_node group by operator).operator ]));" .to_string(), ) .await?; let operator_accounts: Vec = result.take(0)?; let mut operators: Vec = Vec::new(); for account in operator_accounts.iter() { if let Some(operator) = Self::inspect(db, &account.key().to_string()).await? { operators.push(operator); } } Ok(operators) } pub async fn inspect(db: &Surreal, account: &str) -> Result, Error> { let mut result = db .query(format!( "$vm_nodes = (select id from vm_node where operator = account:{account}).id; $app_nodes = (select id from app_node where operator = account:{account}).id; select *, id as account, email, escrow, $vm_nodes.len() as vm_nodes, $app_nodes.len() as app_nodes, (select id from report where $vm_nodes contains out).len() + (select id from report where $app_nodes contains out).len() as reports from account where id = account:{account};" )) .await?; let operator: Option = result.take(2)?; Ok(operator) } pub async fn inspect_nodes( db: &Surreal, account: &str, ) -> Result<(Option, Vec, Vec), Error> { let operator = Self::inspect(db, account).await?; let mut result = db .query(format!( "select *, operator, <-report.* as reports from vm_node where operator = account:{account};" )) .query(format!( "select *, operator, <-report.* as reports from app_node where operator = account:{account};" )) .await?; let vm_nodes: Vec = result.take(0)?; let app_nodes: Vec = result.take(1)?; Ok((operator, vm_nodes, app_nodes)) } } pub enum WrapperContract { Vm(ActiveVmWithNode), App(ActiveAppWithNode), } impl WrapperContract { pub async fn kick_contract( db: &Surreal, operator_wallet: &str, contract_uuid: &str, reason: &str, ) -> Result { let (node_operator, admin, contract_id, node_id, collected_at, price_per_mint, is_vm) = if let Some(active_vm) = ActiveVmWithNode::get_by_uuid(db, contract_uuid).await? { let price_per_minute = active_vm.price_per_minute(); ( active_vm.vm_node.operator.to_string(), active_vm.admin.key().to_string(), active_vm.id, active_vm.vm_node.id, active_vm.collected_at, price_per_minute, true, ) } else if let Some(active_app) = ActiveAppWithNode::get_by_uuid(db, contract_uuid).await? { let price_per_minute = Into::::into(active_app.clone()).price_per_minute(); ( active_app.app_node.operator.to_string(), active_app.admin.key().to_string(), active_app.id, active_app.app_node.id, active_app.collected_at, price_per_minute, false, ) } else { return Err(Error::ContractNotFound); }; if node_operator != operator_wallet { return Err(Error::AccessDenied); } let mut minutes_to_refund = chrono::Utc::now().signed_duration_since(*collected_at).num_minutes().unsigned_abs(); let one_week_minute = 10080; if minutes_to_refund > one_week_minute { minutes_to_refund = one_week_minute; } let mut refund_amount = minutes_to_refund * price_per_mint; if !Kick::kicked_in_a_day(db, &admin).await?.is_empty() { refund_amount = 0; } let mut operator_account = Account::get(db, &node_operator).await?; let mut admin_account = Account::get(db, &admin).await?; if operator_account.escrow < refund_amount { refund_amount = operator_account.escrow; } log::debug!( "Removing {refund_amount} escrow from {} and giving it to {}", node_operator, admin ); admin_account.balance = admin_account.balance.saturating_add(refund_amount); operator_account.escrow = operator_account.escrow.saturating_sub(refund_amount); let kick = Kick { id: RecordId::from((KICK, contract_uuid)), from_account: operator_account.id.clone(), to_account: admin_account.id.clone(), created_at: Datetime::default(), reason: reason.to_string(), contract: contract_id.clone(), node: node_id, }; kick.submit(db).await?; operator_account.save(db).await?; admin_account.save(db).await?; let contract_id = contract_id.to_string(); if is_vm && !ActiveVm::delete(db, &contract_id).await? { return Err(Error::FailedToDeleteContract(format!("vm:{contract_id}"))); } else if !is_vm && !ActiveApp::delete(db, &contract_id).await? { return Err(Error::FailedToDeleteContract(format!("app:{contract_id}"))); } Ok(refund_amount) } }