Implement refund on delete app and vm

fix authentication and refund on delete vm
refactor app daemon register to get delete app req
db function for delete app
sample environment variable
extensive tests for delete app and vm
refactor test utilities
This commit is contained in:
Noor 2025-06-04 21:04:32 +05:30 committed by ghe0
parent e59a3c8fd8
commit 479b9b6fa4
18 changed files with 344 additions and 64 deletions

2
Cargo.lock generated

@ -1000,7 +1000,7 @@ dependencies = [
[[package]] [[package]]
name = "detee-shared" name = "detee-shared"
version = "0.1.0" version = "0.1.0"
source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain_app#694d5811aa0edc9b2b9bdb17c6e972b04a21b96f" source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain_app#005677153b3fcd3251b64111a736c806106fdc04"
dependencies = [ dependencies = [
"bincode 2.0.1", "bincode 2.0.1",
"prost", "prost",

@ -295,8 +295,6 @@ impl From<ActiveApp> for DeletedApp {
disk_size_gb: value.disk_size_gb, disk_size_gb: value.disk_size_gb,
created_at: value.created_at, created_at: value.created_at,
price_per_unit: value.price_per_unit, price_per_unit: value.price_per_unit,
locked_nano: value.locked_nano,
collected_at: value.collected_at,
mr_enclave: value.mr_enclave, mr_enclave: value.mr_enclave,
package_url: value.package_url, package_url: value.package_url,
hratls_pubkey: value.hratls_pubkey, hratls_pubkey: value.hratls_pubkey,
@ -359,16 +357,43 @@ impl ActiveApp {
Ok(()) Ok(())
} }
pub async fn delete(db: &Surreal<Client>, id: &str) -> Result<bool, Error> { pub async fn delete(db: &Surreal<Client>, admin: &str, id: &str) -> Result<(), Error> {
let deleted_app: Option<Self> = db.delete((ACTIVE_APP, id)).await?; let mut app_del_resp = db
if let Some(deleted_app) = deleted_app { .query(
let deleted_app: DeletedApp = deleted_app.into(); "
let _: Vec<DeletedApp> = db.insert(DELETED_APP).relation(deleted_app).await?; BEGIN TRANSACTION;
Ok(true)
} else { LET $active_app = $app_id_input;
Ok(false) LET $admin = $admin_input;
IF $active_app.in != $admin {
THROW 'Unauthorized'
};
return fn::delete_app($active_app);
COMMIT TRANSACTION;
",
)
.bind(("app_id_input", RecordId::from((ACTIVE_APP, id))))
.bind(("admin_input", RecordId::from((ACCOUNT, admin))))
.await?;
log::trace!("delete_app query response: {app_del_resp:?}");
let query_err = app_del_resp.take_errors();
if !query_err.is_empty() {
log::trace!("errors in delete_app: {query_err:?}");
let tx_fail_err_str =
String::from("The query was not executed due to a failed transaction");
if query_err.contains_key(&2) && query_err[&2].to_string() != tx_fail_err_str {
log::error!("Unauthorized: {}", query_err[&2]);
return Err(Error::AccessDenied);
} }
} }
Ok(())
}
} }
pub enum WrappedAppResp { pub enum WrappedAppResp {
@ -653,9 +678,17 @@ pub struct DeletedApp {
pub disk_size_gb: u32, pub disk_size_gb: u32,
pub created_at: Datetime, pub created_at: Datetime,
pub price_per_unit: u64, pub price_per_unit: u64,
pub locked_nano: u64,
pub collected_at: Datetime,
pub mr_enclave: String, pub mr_enclave: String,
pub package_url: String, pub package_url: String,
pub hratls_pubkey: String, pub hratls_pubkey: String,
} }
impl DeletedApp {
pub async fn list_by_node(db: &Surreal<Client>, node_pubkey: &str) -> Result<Vec<Self>, Error> {
let mut result = db
.query(format!("select * from {DELETED_APP} where out = {APP_NODE}:{node_pubkey};"))
.await?;
let contracts: Vec<Self> = result.take(0)?;
Ok(contracts)
}
}

@ -546,16 +546,43 @@ impl ActiveVm {
Ok(false) Ok(false)
} }
pub async fn delete(db: &Surreal<Client>, id: &str) -> Result<bool, Error> { pub async fn delete(db: &Surreal<Client>, admin: &str, id: &str) -> Result<(), Error> {
let deleted_vm: Option<Self> = db.delete((ACTIVE_VM, id)).await?; let mut vm_del_resp = db
if let Some(deleted_vm) = deleted_vm { .query(
let deleted_vm: DeletedVm = deleted_vm.into(); "
let _: Vec<DeletedVm> = db.insert(DELETED_VM).relation(deleted_vm).await?; BEGIN TRANSACTION;
Ok(true)
} else { LET $active_vm = $vm_id_input;
Ok(false) LET $admin = $admin_input;
IF $active_vm.in != $admin {
THROW 'Unauthorized'
};
return fn::delete_vm($active_vm);
COMMIT TRANSACTION;
",
)
.bind(("vm_id_input", RecordId::from((ACTIVE_VM, id))))
.bind(("admin_input", RecordId::from((ACCOUNT, admin))))
.await?;
log::trace!("delete_vm query response: {vm_del_resp:?}");
let query_err = vm_del_resp.take_errors();
if !query_err.is_empty() {
log::trace!("errors in delete_vm: {query_err:?}");
let tx_fail_err_str =
String::from("The query was not executed due to a failed transaction");
if query_err.contains_key(&2) && query_err[&2].to_string() != tx_fail_err_str {
log::error!("Unauthorized: {}", query_err[&2]);
return Err(Error::AccessDenied);
} }
} }
Ok(())
}
pub async fn extend_time( pub async fn extend_time(
db: &Surreal<Client>, db: &Surreal<Client>,

@ -29,7 +29,7 @@ impl AppDaemonServer {
#[tonic::async_trait] #[tonic::async_trait]
impl BrainAppDaemon for AppDaemonServer { impl BrainAppDaemon for AppDaemonServer {
type RegisterAppNodeStream = Pin<Box<dyn Stream<Item = Result<AppContract, Status>> + Send>>; type RegisterAppNodeStream = Pin<Box<dyn Stream<Item = Result<DelAppReq, Status>> + Send>>;
type BrainMessagesStream = Pin<Box<dyn Stream<Item = Result<BrainMessageApp, Status>> + Send>>; type BrainMessagesStream = Pin<Box<dyn Stream<Item = Result<BrainMessageApp, Status>> + Send>>;
async fn register_app_node( async fn register_app_node(
@ -59,11 +59,11 @@ impl BrainAppDaemon for AppDaemonServer {
app_node.register(&self.db).await?; app_node.register(&self.db).await?;
info!("Sending existing contracts to {}", req.node_pubkey); info!("Sending existing contracts to {}", req.node_pubkey);
let contracts = db::ActiveAppWithNode::list_by_node(&self.db, &req.node_pubkey).await?; let deleted_apps = db::DeletedApp::list_by_node(&self.db, &req.node_pubkey).await?;
let (tx, rx) = mpsc::channel(6); let (tx, rx) = mpsc::channel(6);
tokio::spawn(async move { tokio::spawn(async move {
for contract in contracts { for deleted_app in deleted_apps {
let _ = tx.send(Ok(contract.into())).await; let _ = tx.send(Ok(deleted_app.into())).await;
} }
}); });
@ -226,9 +226,15 @@ impl BrainAppCli for AppCliServer {
async fn delete_app(&self, req: Request<DelAppReq>) -> Result<Response<Empty>, Status> { async fn delete_app(&self, req: Request<DelAppReq>) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?; let req = check_sig_from_req(req)?;
info!("delete_app process starting for {:?}", req); info!("delete_app process starting for {:?}", req);
match ActiveApp::delete(&self.db, &req.uuid).await? { match ActiveApp::delete(&self.db, &req.admin_pubkey, &req.uuid).await {
true => Ok(Response::new(Empty {})), Ok(()) => Ok(Response::new(Empty {})),
false => Err(Status::not_found(format!("Could not find App contract {}", &req.uuid))), Err(db::Error::AccessDenied) => Err(Status::permission_denied("Unauthorized")),
Err(e) => {
log::error!("Error deleting app contract {}: {e}", &req.uuid);
Err(Status::unknown(
"Unknown error. Please try again or contact the DeTEE devs team.",
))
}
} }
} }

@ -342,9 +342,15 @@ impl BrainVmCli for VmCliServer {
async fn delete_vm(&self, req: Request<DeleteVmReq>) -> Result<Response<Empty>, Status> { async fn delete_vm(&self, req: Request<DeleteVmReq>) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?; let req = check_sig_from_req(req)?;
match db::ActiveVm::delete(&self.db, &req.uuid).await? { match db::ActiveVm::delete(&self.db, &req.admin_pubkey, &req.uuid).await {
true => Ok(Response::new(Empty {})), Ok(()) => Ok(Response::new(Empty {})),
false => Err(Status::not_found(format!("Could not find VM contract {}", &req.uuid))), Err(db::Error::AccessDenied) => Err(Status::permission_denied("Unauthorized")),
Err(e) => {
log::error!("Error deleting VM contract {}: {e}", &req.uuid);
Err(Status::unknown(
"Unknown error. Please try again or contact the DeTEE devs team.",
))
}
} }
} }

@ -22,7 +22,7 @@ DEFINE FUNCTION OVERWRITE fn::delete_vm(
UPDATE $account SET balance += $vm.locked_nano; UPDATE $account SET balance += $vm.locked_nano;
}; };
INSERT RELATION INTO deleted_vm ( $deleted_vm ); INSERT RELATION INTO deleted_vm ( $deleted_vm );
DELETE $vm.id; RETURN DELETE $vm.id RETURN BEFORE;
}; };
DEFINE FUNCTION OVERWRITE fn::app_price_per_minute( DEFINE FUNCTION OVERWRITE fn::app_price_per_minute(
@ -35,3 +35,20 @@ DEFINE FUNCTION OVERWRITE fn::app_price_per_minute(
($app.disk_size_gb / 10)) ($app.disk_size_gb / 10))
* $app.price_per_unit; * $app.price_per_unit;
}; };
DEFINE FUNCTION OVERWRITE fn::delete_app(
$app_id: record
) {
LET $app = (select * from $app_id)[0];
LET $account = $app.in;
LET $deleted_app = $app.patch([{
'op': 'replace',
'path': 'id',
'value': type::record("deleted_app:" + record::id($app.id))
}]);
IF $app.locked_nano >= 0 {
UPDATE $account SET balance += $app.locked_nano;
};
INSERT RELATION INTO deleted_app ( $deleted_app );
RETURN DELETE $app.id RETURN BEFORE;
};

@ -27,3 +27,5 @@ FOR $contract IN (select * from active_vm fetch out) {
fn::delete_vm($contract.id); fn::delete_vm($contract.id);
}; };
}; };
-- TODO: implement for active_app

@ -0,0 +1,31 @@
use anyhow::Result;
use detee_shared::app_proto::{
brain_app_cli_client::BrainAppCliClient, AppResource, NewAppReq, NewAppRes,
};
use tonic::transport::Channel;
use crate::common::test_utils::Key;
pub async fn create_new_app(
key: &Key,
node_pubkey: &str,
brain_channel: &Channel,
) -> Result<NewAppRes> {
let new_app_req = NewAppReq {
admin_pubkey: key.pubkey.clone(),
node_pubkey: node_pubkey.to_string(),
price_per_unit: 1200,
resource: Some(AppResource { ports: vec![8080, 8081], ..Default::default() }),
locked_nano: 100,
..Default::default()
};
let mut client_app_cli = BrainAppCliClient::new(brain_channel.clone());
let new_app_resp =
client_app_cli.new_app(key.sign_request(new_app_req.clone())?).await?.into_inner();
assert!(new_app_resp.error.is_empty());
assert!(new_app_resp.uuid.len() == 40);
Ok(new_app_resp)
}

@ -41,7 +41,7 @@ pub async fn register_app_node(
client: &mut BrainAppDaemonClient<Channel>, client: &mut BrainAppDaemonClient<Channel>,
key: &Key, key: &Key,
operator_wallet: &str, operator_wallet: &str,
) -> Result<Vec<app_proto::AppContract>> { ) -> Result<Vec<app_proto::DelAppReq>> {
log::info!("Registering app_node: {}", key.pubkey); log::info!("Registering app_node: {}", key.pubkey);
let node_pubkey = key.pubkey.clone(); let node_pubkey = key.pubkey.clone();
@ -133,8 +133,8 @@ pub async fn daemon_engine(
}; };
tx.send(res).await?; tx.send(res).await?;
} }
Some(app_proto::brain_message_app::Msg::DeleteAppReq(_del_app_req)) => { Some(app_proto::brain_message_app::Msg::DeleteAppReq(del_app_req)) => {
todo!() println!("MOCK_APP_DAEMON:: delete app request for {}", del_app_req.uuid);
} }
None => todo!(), None => todo!(),
} }

@ -9,3 +9,6 @@ pub mod vm_daemon_utils;
#[allow(dead_code)] #[allow(dead_code)]
pub mod app_daemon_utils; pub mod app_daemon_utils;
#[allow(dead_code)]
pub mod app_cli_utils;

@ -1,10 +1,14 @@
use anyhow::Result; use anyhow::Result;
use detee_shared::app_proto as sgx_proto; use detee_shared::app_proto as sgx_proto;
use detee_shared::general_proto::brain_general_cli_client::BrainGeneralCliClient;
use detee_shared::general_proto::AirdropReq;
use detee_shared::vm_proto as snp_proto; use detee_shared::vm_proto as snp_proto;
use ed25519_dalek::{Signer, SigningKey}; use ed25519_dalek::{Signer, SigningKey};
use itertools::Itertools; use itertools::Itertools;
use std::sync::OnceLock; use std::sync::OnceLock;
use surreal_brain::constants::TOKEN_DECIMAL;
use tonic::metadata::AsciiMetadataValue; use tonic::metadata::AsciiMetadataValue;
use tonic::transport::Channel;
use tonic::Request; use tonic::Request;
pub static ADMIN_KEYS: OnceLock<Vec<Key>> = OnceLock::new(); pub static ADMIN_KEYS: OnceLock<Vec<Key>> = OnceLock::new();
@ -83,3 +87,14 @@ impl Key {
Ok(sgx_proto::DaemonAuth { timestamp, pubkey, contracts, signature }) Ok(sgx_proto::DaemonAuth { timestamp, pubkey, contracts, signature })
} }
} }
pub async fn airdrop(brain_channel: &Channel, wallet: &str, amount: u64) -> Result<()> {
let mut client = BrainGeneralCliClient::new(brain_channel.clone());
let airdrop_req = AirdropReq { pubkey: wallet.to_string(), tokens: amount * TOKEN_DECIMAL };
let admin_key = admin_keys()[0].clone();
client.airdrop(admin_key.sign_request(airdrop_req.clone())?).await?;
Ok(())
}

@ -1,29 +1,18 @@
use super::test_utils::{admin_keys, Key}; use super::test_utils::Key;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use detee_shared::app_proto; use detee_shared::app_proto;
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::{Account, AirdropReq, RegOperatorReq, ReportNodeReq}; use detee_shared::general_proto::{Account, 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 futures::StreamExt; use futures::StreamExt;
use surreal_brain::constants::{ACTIVE_VM, NEW_VM_REQ, TOKEN_DECIMAL}; use surreal_brain::constants::{ACTIVE_VM, NEW_VM_REQ};
use surreal_brain::db::prelude as db; use surreal_brain::db::prelude as db;
use surrealdb::engine::remote::ws::Client; use surrealdb::engine::remote::ws::Client;
use surrealdb::Surreal; use surrealdb::Surreal;
use tonic::transport::Channel; use tonic::transport::Channel;
pub async fn airdrop(brain_channel: &Channel, wallet: &str, amount: u64) -> Result<()> {
let mut client = BrainGeneralCliClient::new(brain_channel.clone());
let airdrop_req = AirdropReq { pubkey: wallet.to_string(), tokens: amount * TOKEN_DECIMAL };
let admin_key = admin_keys()[0].clone();
client.airdrop(admin_key.sign_request(airdrop_req.clone())?).await?;
Ok(())
}
pub async fn create_new_vm( pub async fn create_new_vm(
db: &Surreal<Client>, db: &Surreal<Client>,
key: &Key, key: &Key,

@ -136,8 +136,8 @@ pub async fn daemon_engine(
Some(vm_proto::brain_vm_message::Msg::UpdateVmReq(_update_vm_req)) => { Some(vm_proto::brain_vm_message::Msg::UpdateVmReq(_update_vm_req)) => {
todo!() todo!()
} }
Some(vm_proto::brain_vm_message::Msg::DeleteVm(_del_vm_req)) => { Some(vm_proto::brain_vm_message::Msg::DeleteVm(del_vm_req)) => {
todo!() println!("MOCK_VM_DAEMON:: delete vm request for {}", del_vm_req.uuid);
} }
None => todo!(), None => todo!(),
} }

@ -1,13 +1,14 @@
use common::app_daemon_utils::mock_app_daemon; use common::app_daemon_utils::mock_app_daemon;
use common::prepare_test_env::{prepare_test_db, run_service_for_stream}; use common::prepare_test_env::{prepare_test_db, run_service_for_stream};
use common::test_utils::Key; use common::test_utils::{airdrop, Key};
use common::vm_cli_utils::airdrop;
use detee_shared::app_proto;
use detee_shared::app_proto::brain_app_cli_client::BrainAppCliClient; use detee_shared::app_proto::brain_app_cli_client::BrainAppCliClient;
use detee_shared::app_proto::{self, DelAppReq};
use std::vec; use std::vec;
use surreal_brain::constants::{ACCOUNT, ACTIVE_APP, NEW_APP_REQ, TOKEN_DECIMAL}; use surreal_brain::constants::{ACCOUNT, ACTIVE_APP, DELETED_APP, NEW_APP_REQ, TOKEN_DECIMAL};
use surreal_brain::db::prelude as db; use surreal_brain::db::prelude as db;
use crate::common::app_cli_utils::create_new_app;
mod common; mod common;
#[tokio::test] #[tokio::test]
@ -89,3 +90,93 @@ async fn test_app_creation() {
assert_eq!(acc_db.balance, airdrop_amount * TOKEN_DECIMAL - (locking_nano + 100)); assert_eq!(acc_db.balance, airdrop_amount * TOKEN_DECIMAL - (locking_nano + 100));
assert_eq!(acc_db.tmp_locked, 0); assert_eq!(acc_db.tmp_locked, 0);
} }
#[tokio::test]
async fn test_timeout_app_creation() {
let _ = prepare_test_db().await.unwrap();
let brain_channel = run_service_for_stream().await.unwrap();
let daemon_key = Key::new().pubkey.clone();
let key = Key::new();
let new_app_req = app_proto::NewAppReq {
admin_pubkey: key.pubkey.clone(),
node_pubkey: daemon_key.clone(),
price_per_unit: 1200,
resource: Some(app_proto::AppResource { ports: vec![8080, 8081], ..Default::default() }),
locked_nano: 100,
..Default::default()
};
airdrop(&brain_channel, &key.pubkey, 10).await.unwrap();
let mut client_app_cli = BrainAppCliClient::new(brain_channel.clone());
let timeout_resp = client_app_cli.new_app(key.sign_request(new_app_req.clone()).unwrap()).await;
assert!(timeout_resp.is_err());
let timeout_err = timeout_resp.err().unwrap();
assert_eq!(
timeout_err.message(),
"Network timeout. Please try again later or contact the DeTEE devs team."
);
}
#[tokio::test]
async fn test_app_deletion() {
let db = prepare_test_db().await.unwrap();
let brain_channel = run_service_for_stream().await.unwrap();
let daemon_key = mock_app_daemon(&brain_channel, None).await.unwrap();
let key = Key::new();
airdrop(&brain_channel, &key.pubkey, 10).await.unwrap();
let new_app_res = create_new_app(&key, &daemon_key, &brain_channel).await.unwrap();
let mut client_app_cli = BrainAppCliClient::new(brain_channel.clone());
let del_app_req = DelAppReq { admin_pubkey: key.pubkey.clone(), uuid: new_app_res.uuid };
let _ = client_app_cli.delete_app(key.sign_request(del_app_req).unwrap()).await.unwrap();
let key_02 = Key::new();
// delete random app
let mut del_app_req = DelAppReq {
admin_pubkey: key_02.pubkey.clone(),
uuid: "9ae3VH8nJg2i8pqTQ6mJtvYuS2kd9n1XLLco8GUPfT95".to_string(),
};
let del_err = client_app_cli
.delete_app(key_02.sign_request(del_app_req.clone()).unwrap())
.await
.err()
.unwrap();
assert_eq!(del_err.message(), "Unauthorized");
let new_app_res_02 = create_new_app(&key, &daemon_key, &brain_channel).await.unwrap();
del_app_req.uuid = new_app_res_02.uuid;
let del_err = client_app_cli
.delete_app(key_02.sign_request(del_app_req.clone()).unwrap())
.await
.err()
.unwrap();
assert_eq!(del_err.message(), "Unauthorized");
// test refund
let key_03 = Key::new();
airdrop(&brain_channel, &key_03.pubkey, 10).await.unwrap();
let new_app_res_03 = create_new_app(&key_03, &daemon_key, &brain_channel).await.unwrap();
let del_app_req =
DelAppReq { admin_pubkey: key_03.pubkey.clone(), uuid: new_app_res_03.uuid.clone() };
let _ = client_app_cli.delete_app(key_03.sign_request(del_app_req).unwrap()).await.unwrap();
let acc: db::Account = db.select((ACCOUNT, key_03.pubkey.clone())).await.unwrap().unwrap();
assert_eq!(acc.balance, 10 * TOKEN_DECIMAL);
let deleted_app =
db.select::<Option<db::DeletedApp>>((DELETED_APP, new_app_res_03.uuid)).await.unwrap();
assert!(deleted_app.is_some());
}
// TODO: test register app node, delete app contract while node offline, kick, etc..

@ -1,10 +1,10 @@
use common::prepare_test_env::{ 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, airdrop, Key};
use common::vm_cli_utils::{ use common::vm_cli_utils::{
airdrop, create_new_vm, list_accounts, list_all_app_contracts, list_all_vm_contracts, create_new_vm, list_accounts, list_all_app_contracts, list_all_vm_contracts, register_operator,
register_operator, report_node, 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};

@ -1,14 +1,14 @@
use common::prepare_test_env::{prepare_test_db, run_service_for_stream}; use common::prepare_test_env::{prepare_test_db, run_service_for_stream};
use common::test_utils::Key; use common::test_utils::{airdrop, Key};
use common::vm_cli_utils::{airdrop, create_new_vm, user_list_vm_contracts}; use common::vm_cli_utils::{create_new_vm, user_list_vm_contracts};
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::vm_proto;
use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient; use detee_shared::vm_proto::brain_vm_cli_client::BrainVmCliClient;
use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient; use detee_shared::vm_proto::brain_vm_daemon_client::BrainVmDaemonClient;
use detee_shared::vm_proto::{self, DeleteVmReq};
use detee_shared::vm_proto::{ExtendVmReq, ListVmContractsReq, NewVmReq}; use detee_shared::vm_proto::{ExtendVmReq, ListVmContractsReq, NewVmReq};
use futures::StreamExt; use futures::StreamExt;
use std::vec; use std::vec;
use surreal_brain::constants::{ACCOUNT, ACTIVE_VM, NEW_VM_REQ, TOKEN_DECIMAL}; use surreal_brain::constants::{ACCOUNT, ACTIVE_VM, DELETED_VM, NEW_VM_REQ, TOKEN_DECIMAL};
use surreal_brain::db::prelude as db; use surreal_brain::db::prelude as db;
mod common; mod common;
@ -125,6 +125,65 @@ async fn test_timeout_vm_creation() {
) )
} }
#[tokio::test]
async fn test_vm_deletion() {
let db = prepare_test_db().await.unwrap();
let brain_channel = run_service_for_stream().await.unwrap();
let daemon_key = mock_vm_daemon(&brain_channel, None).await.unwrap();
let key = Key::new();
airdrop(&brain_channel, &key.pubkey, 10).await.unwrap();
let new_vm_uuid = create_new_vm(&db, &key, &daemon_key, &brain_channel).await.unwrap();
let mut client_vm_cli = BrainVmCliClient::new(brain_channel.clone());
let del_app_req = DeleteVmReq { admin_pubkey: key.pubkey.clone(), uuid: new_vm_uuid };
let _ = client_vm_cli.delete_vm(key.sign_request(del_app_req).unwrap()).await.unwrap();
let key_02 = Key::new();
// delete random vm
let mut del_vm_req = DeleteVmReq {
admin_pubkey: key_02.pubkey.clone(),
uuid: "9ae3VH8nJg2i8pqTQ6mJtvYuS2kd9n1XLLco8GUPfT95".to_string(),
};
let del_err = client_vm_cli
.delete_vm(key_02.sign_request(del_vm_req.clone()).unwrap())
.await
.err()
.unwrap();
assert_eq!(del_err.message(), "Unauthorized");
let new_vm_uuid_02 = create_new_vm(&db, &key, &daemon_key, &brain_channel).await.unwrap();
del_vm_req.uuid = new_vm_uuid_02;
let del_err = client_vm_cli
.delete_vm(key_02.sign_request(del_vm_req.clone()).unwrap())
.await
.err()
.unwrap();
assert_eq!(del_err.message(), "Unauthorized");
// test refund
let key_03 = Key::new();
airdrop(&brain_channel, &key_03.pubkey, 10).await.unwrap();
let new_vm_uuid_03 = create_new_vm(&db, &key_03, &daemon_key, &brain_channel).await.unwrap();
let del_vm_req =
DeleteVmReq { admin_pubkey: key_03.pubkey.clone(), uuid: new_vm_uuid_03.clone() };
let _ = client_vm_cli.delete_vm(key_03.sign_request(del_vm_req).unwrap()).await.unwrap();
let acc: db::Account = db.select((ACCOUNT, key_03.pubkey.clone())).await.unwrap().unwrap();
assert_eq!(acc.balance, 10 * TOKEN_DECIMAL);
let deleted_vm =
db.select::<Option<db::DeletedVm>>((DELETED_VM, new_vm_uuid_03)).await.unwrap();
assert!(deleted_vm.is_some());
}
#[tokio::test] #[tokio::test]
// TODO: create vm for this user before testing this // TODO: create vm for this user before testing this
async fn test_list_vm_contracts() { async fn test_list_vm_contracts() {
@ -240,3 +299,5 @@ async fn test_extend_vm() {
let acc: db::Account = db_conn.select((ACCOUNT, key.pubkey.clone())).await.unwrap().unwrap(); let acc: db::Account = db_conn.select((ACCOUNT, key.pubkey.clone())).await.unwrap().unwrap();
assert_eq!(acc.balance, expected_bal_02); assert_eq!(acc.balance, expected_bal_02);
} }
// TODO: test register vm node, delete vm contract while node offline, kick, etc..

@ -1,8 +1,7 @@
use common::prepare_test_env::{ 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::Key; use common::test_utils::{airdrop, Key};
use common::vm_cli_utils::airdrop;
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::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;