Compare commits
6 Commits
810c1b1966
...
57d3807a17
| Author | SHA1 | Date | |
|---|---|---|---|
| 57d3807a17 | |||
| 929530a4c5 | |||
| 4c647eef6a | |||
| 6a85acda9e | |||
| 32b587c6c5 | |||
| 0fc9b8003a |
2
Cargo.lock
generated
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#da0f3269a31e0ebfb7328e2115e212aabe4d984a"
|
source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain#d6ca058d2de78b5257517034bca2b2c7d5929db8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode 2.0.1",
|
"bincode 2.0.1",
|
||||||
"prost",
|
"prost",
|
||||||
|
|||||||
@ -13,7 +13,7 @@ serde_yaml = "0.9.34"
|
|||||||
surrealdb = "2.2.2"
|
surrealdb = "2.2.2"
|
||||||
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
|
||||||
tonic = { version = "0.12", features = ["tls"] }
|
tonic = { version = "0.12", features = ["tls"] }
|
||||||
detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto", branch = "surreal_brain_app" }
|
detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto", branch = "surreal_brain" }
|
||||||
ed25519-dalek = "2.1.1"
|
ed25519-dalek = "2.1.1"
|
||||||
bs58 = "0.5.1"
|
bs58 = "0.5.1"
|
||||||
tokio-stream = "0.1.17"
|
tokio-stream = "0.1.17"
|
||||||
|
|||||||
@ -1,91 +0,0 @@
|
|||||||
DEFINE TABLE account SCHEMAFULL;
|
|
||||||
DEFINE FIELD balance ON TABLE account TYPE int;
|
|
||||||
DEFINE FIELD tmp_locked ON TABLE account TYPE int;
|
|
||||||
DEFINE FIELD escrow ON TABLE account TYPE int;
|
|
||||||
DEFINE FIELD email ON TABLE account TYPE string;
|
|
||||||
DEFINE FIELD hratls_pubkey ON TABLE account TYPE string;
|
|
||||||
DEFINE FIELD vm_nodes ON TABLE account TYPE array<record>;
|
|
||||||
DEFINE FIELD app_nodes ON TABLE account TYPE array<record>;
|
|
||||||
|
|
||||||
DEFINE TABLE package SCHEMAFULL;
|
|
||||||
DEFINE FIELD url ON TABLE package TYPE array<string>;
|
|
||||||
|
|
||||||
DEFINE TABLE kernel SCHEMAFULL;
|
|
||||||
DEFINE FIELD url ON TABLE kernel TYPE array<string>;
|
|
||||||
|
|
||||||
DEFINE TABLE dtrfs SCHEMAFULL;
|
|
||||||
DEFINE FIELD url ON TABLE dtrfs TYPE array<string>;
|
|
||||||
DEFINE FIELD kernel ON TABLE dtrfs TYPE record<kernel>;
|
|
||||||
|
|
||||||
DEFINE TABLE vm_node SCHEMAFULL;
|
|
||||||
DEFINE FIELD country ON TABLE vm_node TYPE string;
|
|
||||||
DEFINE FIELD region ON TABLE vm_node TYPE string;
|
|
||||||
DEFINE FIELD city ON TABLE vm_node TYPE string;
|
|
||||||
DEFINE FIELD ip ON TABLE vm_node TYPE string;
|
|
||||||
DEFINE FIELD avail_mem_mb ON TABLE vm_node TYPE int;
|
|
||||||
DEFINE FIELD avail_vcpus ON TABLE vm_node TYPE int;
|
|
||||||
DEFINE FIELD avail_storage_gbs ON TABLE vm_node TYPE int;
|
|
||||||
DEFINE FIELD avail_ipv4 ON TABLE vm_node TYPE int;
|
|
||||||
DEFINE FIELD avail_ipv6 ON TABLE vm_node TYPE int;
|
|
||||||
DEFINE FIELD avail_ports ON TABLE vm_node TYPE int;
|
|
||||||
DEFINE FIELD max_ports_per_vm ON TABLE vm_node TYPE int;
|
|
||||||
DEFINE FIELD price ON TABLE vm_node TYPE int;
|
|
||||||
DEFINE FIELD offline_minutes ON TABLE vm_node TYPE int;
|
|
||||||
|
|
||||||
DEFINE TABLE vm_contract TYPE RELATION FROM account TO vm_node SCHEMAFULL;
|
|
||||||
DEFINE FIELD state ON TABLE vm_contract TYPE string;
|
|
||||||
DEFINE FIELD hostname ON TABLE vm_contract TYPE string;
|
|
||||||
DEFINE FIELD mapped_ports ON TABLE vm_contract TYPE array<[int, int]>;
|
|
||||||
DEFINE FIELD public_ipv4 ON TABLE vm_contract TYPE string;
|
|
||||||
DEFINE FIELD public_ipv6 ON TABLE vm_contract TYPE string;
|
|
||||||
DEFINE FIELD disk_size_gb ON TABLE vm_contract TYPE int;
|
|
||||||
DEFINE FIELD vcpus ON TABLE vm_contract TYPE int;
|
|
||||||
DEFINE FIELD memory_mb ON TABLE vm_contract TYPE int;
|
|
||||||
DEFINE FIELD dtrfs ON TABLE vm_contract TYPE record<dtrfs>;
|
|
||||||
DEFINE FIELD created_at ON TABLE vm_contract TYPE datetime;
|
|
||||||
DEFINE FIELD updated_at ON TABLE vm_contract TYPE datetime;
|
|
||||||
DEFINE FIELD price_per_unit ON TABLE vm_contract TYPE int;
|
|
||||||
DEFINE FIELD locked_nano ON TABLE vm_contract TYPE int;
|
|
||||||
DEFINE FIELD collected_at ON TABLE vm_contract TYPE datetime;
|
|
||||||
|
|
||||||
DEFINE TABLE app_node SCHEMAFULL;
|
|
||||||
DEFINE FIELD country ON TABLE app_node TYPE string;
|
|
||||||
DEFINE FIELD region ON TABLE app_node TYPE string;
|
|
||||||
DEFINE FIELD city ON TABLE app_node TYPE string;
|
|
||||||
DEFINE FIELD ip ON TABLE app_node TYPE string;
|
|
||||||
DEFINE FIELD avail_mem_mb ON TABLE app_node TYPE int;
|
|
||||||
DEFINE FIELD avail_vcpus ON TABLE app_node TYPE int;
|
|
||||||
DEFINE FIELD avail_storage_gbs ON TABLE app_node TYPE int;
|
|
||||||
DEFINE FIELD avail_ports ON TABLE app_node TYPE int;
|
|
||||||
DEFINE FIELD max_ports_per_app ON TABLE app_node TYPE int;
|
|
||||||
DEFINE FIELD price ON TABLE app_node TYPE int;
|
|
||||||
DEFINE FIELD offline_minutes ON TABLE app_node TYPE int;
|
|
||||||
|
|
||||||
DEFINE TABLE app_contract TYPE RELATION FROM account TO app_node SCHEMAFULL;
|
|
||||||
DEFINE FIELD state ON TABLE app_contract TYPE string;
|
|
||||||
DEFINE FIELD app_name ON TABLE app_contract TYPE string;
|
|
||||||
DEFINE FIELD mapped_ports ON TABLE app_contract TYPE array<[int, int]>;
|
|
||||||
DEFINE FIELD host_ipv4 ON TABLE app_contract TYPE string;
|
|
||||||
DEFINE FIELD vcpus ON TABLE app_contract TYPE int;
|
|
||||||
DEFINE FIELD memory_mb ON TABLE app_contract TYPE int;
|
|
||||||
DEFINE FIELD disk_size_gb ON TABLE app_contract TYPE int;
|
|
||||||
DEFINE FIELD created_at ON TABLE app_contract TYPE datetime;
|
|
||||||
DEFINE FIELD updated_at ON TABLE app_contract TYPE datetime;
|
|
||||||
DEFINE FIELD price_per_unit ON TABLE app_contract TYPE int;
|
|
||||||
DEFINE FIELD locked_nano ON TABLE app_contract TYPE int;
|
|
||||||
DEFINE FIELD collected_at ON TABLE app_contract TYPE datetime;
|
|
||||||
DEFINE FIELD mr_enclave ON TABLE app_contract TYPE record<package>;
|
|
||||||
|
|
||||||
DEFINE TABLE ban TYPE RELATION FROM account TO account;
|
|
||||||
DEFINE FIELD created_at ON TABLE ban TYPE datetime;
|
|
||||||
|
|
||||||
DEFINE TABLE kick TYPE RELATION FROM account TO account;
|
|
||||||
DEFINE FIELD created_at ON TABLE kick TYPE datetime;
|
|
||||||
DEFINE FIELD reason ON TABLE kick TYPE string;
|
|
||||||
DEFINE FIELD contract ON TABLE kick TYPE record<vm_contract|app_contract>;
|
|
||||||
|
|
||||||
DEFINE TABLE report TYPE RELATION FROM account TO vm_node|app_node;
|
|
||||||
DEFINE FIELD created_at ON TABLE ban TYPE datetime;
|
|
||||||
DEFINE FIELD reason ON TABLE ban TYPE string;
|
|
||||||
|
|
||||||
DEFINE TABLE operator TYPE RELATION FROM account TO vm_node|app_node;
|
|
||||||
@ -17,5 +17,10 @@ ssh $server systemctl stop detee-brain.service
|
|||||||
scp target/release/brain $server:/usr/local/bin/detee-brain
|
scp target/release/brain $server:/usr/local/bin/detee-brain
|
||||||
ssh $server mkdir -p /etc/detee/brain/
|
ssh $server mkdir -p /etc/detee/brain/
|
||||||
scp scripts/detee-brain.service $server:/etc/systemd/system/detee-brain.service
|
scp scripts/detee-brain.service $server:/etc/systemd/system/detee-brain.service
|
||||||
|
scp surql/detee-brain-contracts.service $server:/etc/systemd/system/detee-brain-contracts.service
|
||||||
|
scp surql/detee-brain-contracts.timer $server:/etc/systemd/system/detee-brain-contracts.timer
|
||||||
|
scp surql/brain-timer.sh $server:/etc/detee/brain/brain-timer.sh
|
||||||
|
scp surql/timer.sql $server:/etc/detee/brain/timer.surql
|
||||||
ssh $server systemctl daemon-reload
|
ssh $server systemctl daemon-reload
|
||||||
ssh $server systemctl start detee-brain.service
|
ssh $server systemctl start detee-brain.service
|
||||||
|
ssh $server systemctl enable --now detee-brain-contracts.timer
|
||||||
|
|||||||
@ -23,6 +23,8 @@ pub static ADMIN_ACCOUNTS: LazyLock<Vec<String>> = LazyLock::new(|| {
|
|||||||
pub const OLD_BRAIN_DATA_PATH: &str = "./saved_data.yaml";
|
pub const OLD_BRAIN_DATA_PATH: &str = "./saved_data.yaml";
|
||||||
|
|
||||||
pub const ACCOUNT: &str = "account";
|
pub const ACCOUNT: &str = "account";
|
||||||
|
pub const KICK: &str = "kick";
|
||||||
|
|
||||||
pub const VM_NODE: &str = "vm_node";
|
pub const VM_NODE: &str = "vm_node";
|
||||||
pub const ACTIVE_VM: &str = "active_vm";
|
pub const ACTIVE_VM: &str = "active_vm";
|
||||||
pub const VM_UPDATE_EVENT: &str = "vm_update_event";
|
pub const VM_UPDATE_EVENT: &str = "vm_update_event";
|
||||||
@ -42,3 +44,6 @@ pub const ID_ALPHABET: [char; 62] = [
|
|||||||
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
||||||
'V', 'W', 'X', 'Y', 'Z',
|
'V', 'W', 'X', 'Y', 'Z',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub const MIN_ESCROW: u64 = 5000;
|
||||||
|
pub const TOKEN_DECIMAL: u64 = 1_000_000_000;
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use surrealdb::sql::Datetime;
|
|||||||
use surrealdb::{Notification, RecordId, Surreal};
|
use surrealdb::{Notification, RecordId, Surreal};
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct AppNode {
|
pub struct AppNode {
|
||||||
pub id: RecordId,
|
pub id: RecordId,
|
||||||
pub operator: RecordId,
|
pub operator: RecordId,
|
||||||
@ -32,8 +32,9 @@ pub struct AppNode {
|
|||||||
impl AppNode {
|
impl AppNode {
|
||||||
pub async fn register(self, db: &Surreal<Client>) -> Result<AppNode, Error> {
|
pub async fn register(self, db: &Surreal<Client>) -> Result<AppNode, Error> {
|
||||||
db::Account::get_or_create(db, &self.operator.key().to_string()).await?;
|
db::Account::get_or_create(db, &self.operator.key().to_string()).await?;
|
||||||
let app_node: Option<AppNode> = db.upsert(self.id.clone()).content(self).await?;
|
let app_node_id = self.id.clone();
|
||||||
app_node.ok_or(Error::FailedToCreateDBEntry)
|
let app_node: Option<AppNode> = db.upsert(app_node_id.clone()).content(self).await?;
|
||||||
|
app_node.ok_or(Error::FailedToCreateDBEntry(format!("{APP_NODE}:{app_node_id}")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ impl From<DeletedApp> for AppDaemonMsg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct NewAppReq {
|
pub struct NewAppReq {
|
||||||
pub id: RecordId,
|
pub id: RecordId,
|
||||||
#[serde(rename = "in")]
|
#[serde(rename = "in")]
|
||||||
@ -97,6 +98,7 @@ impl NewAppReq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn submit(self, db: &Surreal<Client>) -> Result<Vec<Self>, Error> {
|
pub async fn submit(self, db: &Surreal<Client>) -> Result<Vec<Self>, Error> {
|
||||||
|
// TODO: handle financial transaction
|
||||||
let new_app_req: Vec<Self> = db.insert(NEW_APP_REQ).relation(self).await?;
|
let new_app_req: Vec<Self> = db.insert(NEW_APP_REQ).relation(self).await?;
|
||||||
Ok(new_app_req)
|
Ok(new_app_req)
|
||||||
}
|
}
|
||||||
@ -164,7 +166,7 @@ impl AppNodeWithReports {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ActiveApp {
|
pub struct ActiveApp {
|
||||||
pub id: RecordId,
|
pub id: RecordId,
|
||||||
#[serde(rename = "in")]
|
#[serde(rename = "in")]
|
||||||
@ -210,6 +212,15 @@ impl From<ActiveApp> for DeletedApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ActiveApp {
|
impl ActiveApp {
|
||||||
|
pub fn price_per_minute(&self) -> u64 {
|
||||||
|
(self.total_units() * self.price_per_unit as f64) as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn total_units(&self) -> f64 {
|
||||||
|
// TODO: Optimize this based on price of hardware.
|
||||||
|
(self.vcpus as f64 * 5f64) + (self.memory_mb as f64 / 200f64) + (self.disk_size_gb as f64)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn activate(db: &Surreal<Client>, id: &str) -> Result<(), Error> {
|
pub async fn activate(db: &Surreal<Client>, id: &str) -> Result<(), Error> {
|
||||||
let new_app_req = match NewAppReq::get(db, id).await? {
|
let new_app_req = match NewAppReq::get(db, id).await? {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
@ -293,7 +304,7 @@ impl ActiveApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ActiveAppWithNode {
|
pub struct ActiveAppWithNode {
|
||||||
pub id: RecordId,
|
pub id: RecordId,
|
||||||
#[serde(rename = "in")]
|
#[serde(rename = "in")]
|
||||||
@ -315,6 +326,29 @@ pub struct ActiveAppWithNode {
|
|||||||
pub hratls_pubkey: String,
|
pub hratls_pubkey: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActiveAppWithNode> for ActiveApp {
|
||||||
|
fn from(val: ActiveAppWithNode) -> Self {
|
||||||
|
Self {
|
||||||
|
id: val.id,
|
||||||
|
admin: val.admin,
|
||||||
|
app_node: val.app_node.id,
|
||||||
|
app_name: val.app_name,
|
||||||
|
mapped_ports: val.mapped_ports,
|
||||||
|
host_ipv4: val.host_ipv4,
|
||||||
|
vcpus: val.vcpus,
|
||||||
|
memory_mb: val.memory_mb,
|
||||||
|
disk_size_gb: val.disk_size_gb,
|
||||||
|
created_at: val.created_at,
|
||||||
|
price_per_unit: val.price_per_unit,
|
||||||
|
locked_nano: val.locked_nano,
|
||||||
|
collected_at: val.collected_at,
|
||||||
|
mr_enclave: val.mr_enclave,
|
||||||
|
package_url: val.package_url,
|
||||||
|
hratls_pubkey: val.hratls_pubkey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveAppWithNode {
|
impl ActiveAppWithNode {
|
||||||
pub async fn get_by_uuid(db: &Surreal<Client>, uuid: &str) -> Result<Option<Self>, Error> {
|
pub async fn get_by_uuid(db: &Surreal<Client>, uuid: &str) -> Result<Option<Self>, Error> {
|
||||||
let contract: Option<Self> =
|
let contract: Option<Self> =
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use crate::constants::ACCOUNT;
|
|
||||||
use crate::db::prelude::*;
|
|
||||||
|
|
||||||
use super::Error;
|
use super::Error;
|
||||||
|
use crate::constants::{ACCOUNT, KICK, MIN_ESCROW, TOKEN_DECIMAL};
|
||||||
|
use crate::db::prelude::*;
|
||||||
use crate::old_brain;
|
use crate::old_brain;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use surrealdb::engine::remote::ws::Client;
|
use surrealdb::engine::remote::ws::Client;
|
||||||
@ -37,7 +36,7 @@ impl Account {
|
|||||||
Some(account) => Ok(account),
|
Some(account) => Ok(account),
|
||||||
None => {
|
None => {
|
||||||
let account: Option<Self> = db.create(id).await?;
|
let account: Option<Self> = db.create(id).await?;
|
||||||
account.ok_or(Error::FailedToCreateDBEntry)
|
account.ok_or(Error::FailedToCreateDBEntry(ACCOUNT.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,6 +48,33 @@ impl Account {
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn save(self, db: &Surreal<Client>) -> Result<Option<Self>, Error> {
|
||||||
|
let account: Option<Self> = db.upsert(self.id.clone()).content(self).await?;
|
||||||
|
Ok(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn operator_reg(
|
||||||
|
db: &Surreal<Client>,
|
||||||
|
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 {
|
impl Account {
|
||||||
@ -120,6 +146,24 @@ pub struct Kick {
|
|||||||
created_at: Datetime,
|
created_at: Datetime,
|
||||||
reason: String,
|
reason: String,
|
||||||
contract: RecordId,
|
contract: RecordId,
|
||||||
|
node: RecordId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kick {
|
||||||
|
pub async fn kicked_in_a_day(db: &Surreal<Client>, account: &str) -> Result<Vec<Self>, Error> {
|
||||||
|
let mut result = db
|
||||||
|
.query(format!(
|
||||||
|
"select * from {KICK} where out = {ACCOUNT}:{account} and created_at > time::now() - 24h;"
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
let kicks: Vec<Self> = result.take(0)?;
|
||||||
|
Ok(kicks)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn submit(self, db: &Surreal<Client>) -> Result<(), Error> {
|
||||||
|
let _: Vec<Self> = db.insert(KICK).relation(self).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
@ -158,7 +202,7 @@ impl Report {
|
|||||||
|
|
||||||
/// This is the operator obtained from the DB,
|
/// This is the operator obtained from the DB,
|
||||||
/// however the relation is defined using OperatorRelation
|
/// however the relation is defined using OperatorRelation
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Operator {
|
pub struct Operator {
|
||||||
pub account: RecordId,
|
pub account: RecordId,
|
||||||
pub app_nodes: u64,
|
pub app_nodes: u64,
|
||||||
@ -192,8 +236,8 @@ impl Operator {
|
|||||||
pub async fn inspect(db: &Surreal<Client>, account: &str) -> Result<Option<Self>, Error> {
|
pub async fn inspect(db: &Surreal<Client>, account: &str) -> Result<Option<Self>, Error> {
|
||||||
let mut result = db
|
let mut result = db
|
||||||
.query(format!(
|
.query(format!(
|
||||||
"$vm_nodes = (select id from vm_node where operator = account:{account}).id;
|
"LET $vm_nodes = (select id from vm_node where operator = account:{account}).id;
|
||||||
$app_nodes = (select id from app_node where operator = account:{account}).id;
|
LET $app_nodes = (select id from app_node where operator = account:{account}).id;
|
||||||
select *,
|
select *,
|
||||||
id as account,
|
id as account,
|
||||||
email,
|
email,
|
||||||
@ -231,3 +275,149 @@ impl Operator {
|
|||||||
Ok((operator, vm_nodes, app_nodes))
|
Ok((operator, vm_nodes, app_nodes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum WrapperContract {
|
||||||
|
Vm(ActiveVmWithNode),
|
||||||
|
App(ActiveAppWithNode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrapperContract {
|
||||||
|
pub async fn kick_contract(
|
||||||
|
db: &Surreal<Client>,
|
||||||
|
operator_wallet: &str,
|
||||||
|
contract_uuid: &str,
|
||||||
|
reason: &str,
|
||||||
|
) -> Result<u64, Error> {
|
||||||
|
let (
|
||||||
|
operator_id,
|
||||||
|
admin_id,
|
||||||
|
contract_id,
|
||||||
|
collected_at,
|
||||||
|
price_per_mint,
|
||||||
|
deleted_table,
|
||||||
|
platform_specific_query,
|
||||||
|
) = 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,
|
||||||
|
active_vm.admin,
|
||||||
|
active_vm.id,
|
||||||
|
active_vm.collected_at,
|
||||||
|
price_per_minute,
|
||||||
|
"deleted_vm",
|
||||||
|
"
|
||||||
|
hostname = $contract.hostname,
|
||||||
|
public_ipv4 = $contract.public_ipv4,
|
||||||
|
public_ipv6 = $contract.public_ipv6,
|
||||||
|
dtrfs_sha = $contract.dtrfs_sha,
|
||||||
|
kernel_sha = $contract.kernel_sha,
|
||||||
|
",
|
||||||
|
)
|
||||||
|
} else if let Some(active_app) = ActiveAppWithNode::get_by_uuid(db, contract_uuid).await? {
|
||||||
|
let price_per_minute = Into::<ActiveApp>::into(active_app.clone()).price_per_minute();
|
||||||
|
|
||||||
|
(
|
||||||
|
active_app.app_node.operator,
|
||||||
|
active_app.admin,
|
||||||
|
active_app.id,
|
||||||
|
active_app.collected_at,
|
||||||
|
price_per_minute,
|
||||||
|
"deleted_app",
|
||||||
|
"
|
||||||
|
app_name = $contract.app_name,
|
||||||
|
host_ipv4 = $contract.host_ipv4,
|
||||||
|
mr_enclave = $contract.mr_enclave,
|
||||||
|
package_url= $contract.package_url,
|
||||||
|
hratls_pubkey = $contract.hratls_pubkey,
|
||||||
|
",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Err(Error::ContractNotFound);
|
||||||
|
};
|
||||||
|
|
||||||
|
let operator = operator_id.key().to_string();
|
||||||
|
let admin = admin_id.key().to_string();
|
||||||
|
|
||||||
|
if 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 refund_amount = minutes_to_refund * price_per_mint;
|
||||||
|
|
||||||
|
log::debug!("Removing {refund_amount} escrow from {} and giving it to {}", operator, admin);
|
||||||
|
|
||||||
|
let transaction_query = format!(
|
||||||
|
"
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
LET $contract = {contract_id};
|
||||||
|
LET $operator_account = {operator_id};
|
||||||
|
LET $reason = '{reason}';
|
||||||
|
LET $refund_amount = {refund_amount};
|
||||||
|
LET $deleted_contract = {deleted_table}:{contract_uuid};
|
||||||
|
LET $id = record::id($contract.id);
|
||||||
|
LET $admin = $contract.in;
|
||||||
|
LET $node = $contract.out;
|
||||||
|
|
||||||
|
-- move contract into deleted state
|
||||||
|
|
||||||
|
RELATE $admin->{deleted_table}->$node
|
||||||
|
SET id = $id,
|
||||||
|
{platform_specific_query}
|
||||||
|
mapped_ports = $contract.mapped_ports,
|
||||||
|
disk_size_gb = $contract.disk_size_gb,
|
||||||
|
vcpus = $contract.vcpus,
|
||||||
|
memory_mb = $contract.memory_mb,
|
||||||
|
created_at = $contract.created_at,
|
||||||
|
deleted_at = time::now(),
|
||||||
|
price_per_unit = $contract.price_per_unit
|
||||||
|
;
|
||||||
|
|
||||||
|
DELETE $contract;
|
||||||
|
|
||||||
|
-- calculating refund amount
|
||||||
|
LET $refund = IF SELECT * FROM {KICK} WHERE out = $admin.id AND created_at > time::now() - 24h {{
|
||||||
|
0
|
||||||
|
}} ELSE IF $operator_account.escrow <= $refund_amount {{
|
||||||
|
$operator_account.escrow
|
||||||
|
}} ELSE {{
|
||||||
|
$refund_amount;
|
||||||
|
}};
|
||||||
|
|
||||||
|
|
||||||
|
RELATE $operator_account->{KICK}->$admin
|
||||||
|
SET id = $id,
|
||||||
|
reason = $reason,
|
||||||
|
contract = $deleted_contract,
|
||||||
|
node = $node,
|
||||||
|
created_at = time::now()
|
||||||
|
;
|
||||||
|
|
||||||
|
-- update balances
|
||||||
|
UPDATE $operator_account SET escrow -= $refund;
|
||||||
|
IF $operator_account.escrow < 0 {{
|
||||||
|
THROW 'Insufficient funds.'
|
||||||
|
}};
|
||||||
|
UPDATE $admin SET balance += $refund;
|
||||||
|
|
||||||
|
SELECT * FROM $refund;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
log::trace!("kick_contract transaction_query: {}", &transaction_query);
|
||||||
|
let refunded: Option<u64> = db.query(transaction_query).await?.take(14)?;
|
||||||
|
let refunded_amount = refunded.ok_or(Error::FailedToCreateDBEntry("Refund".to_string()))?;
|
||||||
|
|
||||||
|
Ok(refunded_amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,7 +2,9 @@ pub mod app;
|
|||||||
pub mod general;
|
pub mod general;
|
||||||
pub mod vm;
|
pub mod vm;
|
||||||
|
|
||||||
use crate::constants::{APP_NODE, DELETED_APP, DELETED_VM, NEW_APP_REQ, NEW_VM_REQ, UPDATE_VM_REQ};
|
use crate::constants::{
|
||||||
|
APP_NODE, DELETED_APP, DELETED_VM, MIN_ESCROW, NEW_APP_REQ, NEW_VM_REQ, UPDATE_VM_REQ,
|
||||||
|
};
|
||||||
use crate::old_brain;
|
use crate::old_brain;
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -22,14 +24,24 @@ pub enum Error {
|
|||||||
StdIo(#[from] std::io::Error),
|
StdIo(#[from] std::io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
TimeOut(#[from] tokio::time::error::Elapsed),
|
TimeOut(#[from] tokio::time::error::Elapsed),
|
||||||
#[error("Failed to create account")]
|
#[error("Failed to create {0}")]
|
||||||
FailedToCreateDBEntry,
|
FailedToCreateDBEntry(String),
|
||||||
#[error("Unknown Table: {0}")]
|
#[error("Unknown Table: {0}")]
|
||||||
UnknownTable(String),
|
UnknownTable(String),
|
||||||
#[error("Daemon channel got closed: {0}")]
|
#[error("Daemon channel got closed: {0}")]
|
||||||
AppDaemonConnection(#[from] tokio::sync::mpsc::error::SendError<AppDaemonMsg>),
|
AppDaemonConnection(#[from] tokio::sync::mpsc::error::SendError<AppDaemonMsg>),
|
||||||
#[error("AppDaemon Error {0}")]
|
#[error("AppDaemon Error {0}")]
|
||||||
NewAppDaemonResp(String),
|
NewAppDaemonResp(String),
|
||||||
|
#[error("Minimum escrow amount is {MIN_ESCROW}")]
|
||||||
|
MinimalEscrow,
|
||||||
|
#[error("Insufficient funds, deposit more tokens")]
|
||||||
|
InsufficientFunds,
|
||||||
|
#[error("Contract not found")]
|
||||||
|
ContractNotFound,
|
||||||
|
#[error("Access denied")]
|
||||||
|
AccessDenied,
|
||||||
|
#[error("Failed to delete contract {0}")]
|
||||||
|
FailedToDeleteContract(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
|||||||
107
src/db/vm.rs
107
src/db/vm.rs
@ -30,7 +30,27 @@ pub struct VmNode {
|
|||||||
pub avail_ports: u32,
|
pub avail_ports: u32,
|
||||||
pub max_ports_per_vm: u32,
|
pub max_ports_per_vm: u32,
|
||||||
pub price: u64,
|
pub price: u64,
|
||||||
pub offline_minutes: u64,
|
pub connected_at: Datetime,
|
||||||
|
pub disconnected_at: Datetime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VmNode {
|
||||||
|
pub async fn register(self, db: &Surreal<Client>) -> Result<(), Error> {
|
||||||
|
Account::get_or_create(db, &self.operator.key().to_string()).await?;
|
||||||
|
let _: Option<VmNode> = db.upsert(self.id.clone()).content(self).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_online(db: &Surreal<Client>, vm_node_id: &str) -> Result<(), Error> {
|
||||||
|
db.query(format!("UPDATE {VM_NODE}:{vm_node_id} SET connected_at = time::now();")).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_offline(db: &Surreal<Client>, vm_node_id: &str) -> Result<(), Error> {
|
||||||
|
db.query(format!("UPDATE {VM_NODE}:{vm_node_id} SET disconnected_at = time::now();"))
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@ -51,14 +71,6 @@ impl VmNodeResources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VmNode {
|
|
||||||
pub async fn register(self, db: &Surreal<Client>) -> Result<(), Error> {
|
|
||||||
Account::get_or_create(db, &self.operator.key().to_string()).await?;
|
|
||||||
let _: Option<VmNode> = db.upsert(self.id.clone()).content(self).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct VmNodeWithReports {
|
pub struct VmNodeWithReports {
|
||||||
pub id: RecordId,
|
pub id: RecordId,
|
||||||
@ -75,13 +87,10 @@ pub struct VmNodeWithReports {
|
|||||||
pub avail_ports: u32,
|
pub avail_ports: u32,
|
||||||
pub max_ports_per_vm: u32,
|
pub max_ports_per_vm: u32,
|
||||||
pub price: u64,
|
pub price: u64,
|
||||||
pub offline_minutes: u64,
|
|
||||||
pub reports: Vec<Report>,
|
pub reports: Vec<Report>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VmNodeWithReports {
|
impl VmNodeWithReports {
|
||||||
// TODO: find a more elegant way to do this than importing gRPC in the DB module
|
|
||||||
// https://en.wikipedia.org/wiki/Dependency_inversion_principle
|
|
||||||
pub async fn find_by_filters(
|
pub async fn find_by_filters(
|
||||||
db: &Surreal<Client>,
|
db: &Surreal<Client>,
|
||||||
filters: vm_proto::VmNodeFilters,
|
filters: vm_proto::VmNodeFilters,
|
||||||
@ -115,7 +124,7 @@ impl VmNodeWithReports {
|
|||||||
if !filters.ip.is_empty() {
|
if !filters.ip.is_empty() {
|
||||||
query += &format!("&& ip = '{}' ", filters.ip);
|
query += &format!("&& ip = '{}' ", filters.ip);
|
||||||
}
|
}
|
||||||
query += ";";
|
query += " && connected_at > disconnected_at;";
|
||||||
let mut result = db.query(query).await?;
|
let mut result = db.query(query).await?;
|
||||||
let vm_nodes: Vec<Self> = result.take(0)?;
|
let vm_nodes: Vec<Self> = result.take(0)?;
|
||||||
Ok(vm_nodes)
|
Ok(vm_nodes)
|
||||||
@ -191,7 +200,46 @@ impl NewVmReq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn submit(self, db: &Surreal<Client>) -> Result<(), Error> {
|
pub async fn submit(self, db: &Surreal<Client>) -> Result<(), Error> {
|
||||||
let _: Vec<Self> = db.insert(NEW_VM_REQ).relation(self).await?;
|
let locked_nano = self.locked_nano;
|
||||||
|
let account = self.admin.key().to_string();
|
||||||
|
let vm_id = self.id.key().to_string();
|
||||||
|
let vm_node = self.vm_node.key().to_string();
|
||||||
|
// TODO: check for possible injection and maybe use .bind()
|
||||||
|
let query = format!(
|
||||||
|
"
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
UPDATE account:{account} SET balance -= {locked_nano};
|
||||||
|
IF account:{account}.balance < 0 {{
|
||||||
|
THROW 'Insufficient funds.'
|
||||||
|
}};
|
||||||
|
UPDATE account:{account} SET tmp_locked += {locked_nano};
|
||||||
|
RELATE
|
||||||
|
account:{account}
|
||||||
|
->new_vm_req:{vm_id}
|
||||||
|
->vm_node:{vm_node}
|
||||||
|
CONTENT {{
|
||||||
|
created_at: time::now(), hostname: '{}', vcpus: {}, memory_mb: {}, disk_size_gb: {},
|
||||||
|
extra_ports: {}, public_ipv4: {:?}, public_ipv6: {:?},
|
||||||
|
dtrfs_url: '{}', dtrfs_sha: '{}', kernel_url: '{}', kernel_sha: '{}',
|
||||||
|
price_per_unit: {}, locked_nano: {locked_nano}, error: ''
|
||||||
|
}};
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
",
|
||||||
|
self.hostname,
|
||||||
|
self.vcpus,
|
||||||
|
self.memory_mb,
|
||||||
|
self.disk_size_gb,
|
||||||
|
format!("{:?}", self.extra_ports,),
|
||||||
|
self.public_ipv4,
|
||||||
|
self.public_ipv6,
|
||||||
|
self.dtrfs_url,
|
||||||
|
self.dtrfs_sha,
|
||||||
|
self.kernel_url,
|
||||||
|
self.kernel_sha,
|
||||||
|
self.price_per_unit
|
||||||
|
);
|
||||||
|
//let _: Vec<Self> = db.insert(NEW_VM_REQ).relation(self).await?;
|
||||||
|
db.query(query).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -366,9 +414,12 @@ impl ActiveVm {
|
|||||||
collected_at: new_vm_req.created_at,
|
collected_at: new_vm_req.created_at,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let admin_account = active_vm.admin.key().to_string();
|
||||||
|
let locked_nano = active_vm.locked_nano;
|
||||||
let _: Vec<ActiveVm> = db.insert(()).relation(active_vm).await?;
|
let _: Vec<ActiveVm> = db.insert(()).relation(active_vm).await?;
|
||||||
|
|
||||||
NewVmReq::delete(db, id).await?;
|
NewVmReq::delete(db, id).await?;
|
||||||
|
db.query(format!("UPDATE {ACCOUNT}:{admin_account} SET tmp_locked -= {locked_nano};"))
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -665,6 +716,29 @@ pub struct ActiveVmWithNode {
|
|||||||
pub collected_at: Datetime,
|
pub collected_at: Datetime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActiveVmWithNode> for ActiveVm {
|
||||||
|
fn from(val: ActiveVmWithNode) -> Self {
|
||||||
|
Self {
|
||||||
|
id: val.id,
|
||||||
|
admin: val.admin,
|
||||||
|
vm_node: val.vm_node.id,
|
||||||
|
hostname: val.hostname,
|
||||||
|
mapped_ports: val.mapped_ports,
|
||||||
|
public_ipv4: val.public_ipv4,
|
||||||
|
public_ipv6: val.public_ipv6,
|
||||||
|
disk_size_gb: val.disk_size_gb,
|
||||||
|
vcpus: val.vcpus,
|
||||||
|
memory_mb: val.memory_mb,
|
||||||
|
dtrfs_sha: val.dtrfs_sha,
|
||||||
|
kernel_sha: val.kernel_sha,
|
||||||
|
created_at: val.created_at,
|
||||||
|
price_per_unit: val.price_per_unit,
|
||||||
|
locked_nano: val.locked_nano,
|
||||||
|
collected_at: val.collected_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveVmWithNode {
|
impl ActiveVmWithNode {
|
||||||
pub async fn get_by_uuid(db: &Surreal<Client>, uuid: &str) -> Result<Option<Self>, Error> {
|
pub async fn get_by_uuid(db: &Surreal<Client>, uuid: &str) -> Result<Option<Self>, Error> {
|
||||||
let contract: Option<Self> =
|
let contract: Option<Self> =
|
||||||
@ -750,7 +824,8 @@ impl From<&old_brain::BrainData> for Vec<VmNode> {
|
|||||||
avail_ports: old_node.avail_ports,
|
avail_ports: old_node.avail_ports,
|
||||||
max_ports_per_vm: old_node.max_ports_per_vm,
|
max_ports_per_vm: old_node.max_ports_per_vm,
|
||||||
price: old_node.price,
|
price: old_node.price,
|
||||||
offline_minutes: old_node.offline_minutes,
|
disconnected_at: Datetime::default(),
|
||||||
|
connected_at: Datetime::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
nodes
|
nodes
|
||||||
|
|||||||
@ -107,24 +107,52 @@ impl BrainGeneralCli for GeneralCliServer {
|
|||||||
|
|
||||||
async fn register_operator(
|
async fn register_operator(
|
||||||
&self,
|
&self,
|
||||||
_req: Request<RegOperatorReq>,
|
req: Request<RegOperatorReq>,
|
||||||
) -> Result<Response<Empty>, Status> {
|
) -> Result<Response<Empty>, Status> {
|
||||||
todo!();
|
let req = check_sig_from_req(req)?;
|
||||||
// let req = check_sig_from_req(req)?;
|
log::info!("Regitering new operator: {req:?}");
|
||||||
// info!("Regitering new operator: {req:?}");
|
match db::Account::operator_reg(&self.db, &req.pubkey, &req.email, req.escrow).await {
|
||||||
// match self.data.register_operator(req) {
|
Ok(()) => Ok(Response::new(Empty {})),
|
||||||
// Ok(()) => Ok(Response::new(Empty {})),
|
Err(e) if matches!(e, db::Error::InsufficientFunds | db::Error::MinimalEscrow) => {
|
||||||
// Err(e) => Err(Status::failed_precondition(e.to_string())),
|
Err(Status::failed_precondition(e.to_string()))
|
||||||
// }
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::info!("Failed to register operator: {e:?}");
|
||||||
|
Err(Status::unknown(
|
||||||
|
"Unknown error. Please try again or contact the DeTEE devs team.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn kick_contract(&self, _req: Request<KickReq>) -> Result<Response<KickResp>, Status> {
|
async fn kick_contract(&self, req: Request<KickReq>) -> Result<Response<KickResp>, Status> {
|
||||||
todo!();
|
let req = check_sig_from_req(req)?;
|
||||||
// let req = check_sig_from_req(req)?;
|
match db::WrapperContract::kick_contract(
|
||||||
// match self.data.kick_contract(&req.operator_wallet, &req.contract_uuid, &req.reason).await {
|
&self.db,
|
||||||
// Ok(nano_lp) => Ok(Response::new(KickResp { nano_lp })),
|
&req.operator_wallet,
|
||||||
// Err(e) => Err(Status::permission_denied(e.to_string())),
|
&req.contract_uuid,
|
||||||
// }
|
&req.reason,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(nano_lp) => Ok(Response::new(KickResp { nano_lp })),
|
||||||
|
Err(e)
|
||||||
|
if matches!(
|
||||||
|
e,
|
||||||
|
db::Error::ContractNotFound
|
||||||
|
| db::Error::AccessDenied
|
||||||
|
| db::Error::FailedToDeleteContract(_)
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
Err(Status::failed_precondition(e.to_string()))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::info!("Failed to kick contract: {e:?}");
|
||||||
|
Err(Status::unknown(
|
||||||
|
"Unknown error. Please try again or contact the DeTEE devs team.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ban_user(&self, _req: Request<BanUserReq>) -> Result<Response<Empty>, Status> {
|
async fn ban_user(&self, _req: Request<BanUserReq>) -> Result<Response<Empty>, Status> {
|
||||||
|
|||||||
@ -30,7 +30,7 @@ impl VmDaemonServer {
|
|||||||
#[tonic::async_trait]
|
#[tonic::async_trait]
|
||||||
impl BrainVmDaemon for VmDaemonServer {
|
impl BrainVmDaemon for VmDaemonServer {
|
||||||
type BrainMessagesStream = Pin<Box<dyn Stream<Item = Result<BrainVmMessage, Status>> + Send>>;
|
type BrainMessagesStream = Pin<Box<dyn Stream<Item = Result<BrainVmMessage, Status>> + Send>>;
|
||||||
type RegisterVmNodeStream = Pin<Box<dyn Stream<Item = Result<VmContract, Status>> + Send>>;
|
type RegisterVmNodeStream = Pin<Box<dyn Stream<Item = Result<DeleteVmReq, Status>> + Send>>;
|
||||||
|
|
||||||
async fn register_vm_node(
|
async fn register_vm_node(
|
||||||
&self,
|
&self,
|
||||||
@ -53,17 +53,18 @@ impl BrainVmDaemon for VmDaemonServer {
|
|||||||
avail_ipv6: 0,
|
avail_ipv6: 0,
|
||||||
avail_ports: 0,
|
avail_ports: 0,
|
||||||
max_ports_per_vm: 0,
|
max_ports_per_vm: 0,
|
||||||
offline_minutes: 0,
|
disconnected_at: surrealdb::sql::Datetime::default(),
|
||||||
|
connected_at: surrealdb::sql::Datetime::default(),
|
||||||
}
|
}
|
||||||
.register(&self.db)
|
.register(&self.db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
info!("Sending existing contracts to {}", req.node_pubkey);
|
info!("Sending deleted contracts to {}", req.node_pubkey);
|
||||||
let contracts = db::ActiveVmWithNode::list_by_node(&self.db, &req.node_pubkey).await?;
|
let deleted_vms = db::DeletedVm::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_vm in deleted_vms {
|
||||||
let _ = tx.send(Ok(contract.into())).await;
|
let _ = tx.send(Ok(deleted_vm.into())).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let output_stream = ReceiverStream::new(rx);
|
let output_stream = ReceiverStream::new(rx);
|
||||||
@ -83,6 +84,7 @@ impl BrainVmDaemon for VmDaemonServer {
|
|||||||
&auth.signature,
|
&auth.signature,
|
||||||
)?;
|
)?;
|
||||||
info!("Daemon {} connected to receive brain messages", pubkey);
|
info!("Daemon {} connected to receive brain messages", pubkey);
|
||||||
|
let _ = db::VmNode::set_online(&self.db, &pubkey).await;
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(6);
|
let (tx, rx) = mpsc::channel(6);
|
||||||
{
|
{
|
||||||
@ -194,7 +196,8 @@ impl BrainVmDaemon for VmDaemonServer {
|
|||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("Daemon disconnected: {e:?}");
|
log::warn!("Daemon disconnected for {pubkey}: {e:?}");
|
||||||
|
let _ = db::VmNode::set_offline(&self.db, &pubkey).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
surql/README.md
Normal file
2
surql/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
This is actually SurrealQL (`.surql`), but the files have the `.sql`
|
||||||
|
extension to enable syntax coloring.
|
||||||
12
surql/brain-timer.sh
Executable file
12
surql/brain-timer.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source /etc/detee/brain/config.ini
|
||||||
|
|
||||||
|
import="docker run -i --rm --net=host \
|
||||||
|
--volume "/etc/detee/brain/timer.surql:/timer.surql" \
|
||||||
|
surrealdb/surrealdb:latest import \
|
||||||
|
--endpoint "http://${DB_URL}" \
|
||||||
|
--username $DB_USER --password "$DB_PASS" \
|
||||||
|
--namespace $DB_NAMESPACE --database $DB_NAME"
|
||||||
|
|
||||||
|
$import timer.surql
|
||||||
6
surql/detee-brain-contracts.service
Normal file
6
surql/detee-brain-contracts.service
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Process brain contracts
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/etc/detee/brain/brain-timer.sh
|
||||||
9
surql/detee-brain-contracts.timer
Normal file
9
surql/detee-brain-contracts.timer
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Run detee-brain-contracts.service every minute
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* *:*:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
27
surql/functions.sql
Normal file
27
surql/functions.sql
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
DEFINE FUNCTION OVERWRITE fn::vm_price_per_minute(
|
||||||
|
$vm_id: record
|
||||||
|
) {
|
||||||
|
LET $vm = (select * from $vm_id)[0];
|
||||||
|
LET $ip_price = IF $vm.public_ipv4.len() > 0 { 10 } ELSE { 0 };
|
||||||
|
RETURN (
|
||||||
|
($vm.vcpus * 10) + (($vm.memory_mb + 256) / 200) + ($vm.disk_size_gb / 10) + $ip_price)
|
||||||
|
* $vm.price_per_unit;
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE FUNCTION OVERWRITE fn::delete_vm(
|
||||||
|
$vm_id: record
|
||||||
|
) {
|
||||||
|
LET $vm = (select * from $vm_id)[0];
|
||||||
|
LET $account = $vm.in;
|
||||||
|
LET $deleted_vm = $vm.patch([{
|
||||||
|
'op': 'replace',
|
||||||
|
'path': 'id',
|
||||||
|
'value': type::record("deleted_vm:" + record::id($vm.id))
|
||||||
|
}]);
|
||||||
|
IF $vm.locked_nano >= 0 {
|
||||||
|
UPDATE $account SET balance += $vm.locked_nano;
|
||||||
|
};
|
||||||
|
INSERT RELATION INTO deleted_vm ( $deleted_vm );
|
||||||
|
DELETE $vm.id;
|
||||||
|
};
|
||||||
|
|
||||||
@ -18,7 +18,8 @@ DEFINE FIELD avail_ipv6 ON TABLE vm_node TYPE int;
|
|||||||
DEFINE FIELD avail_ports ON TABLE vm_node TYPE int;
|
DEFINE FIELD avail_ports ON TABLE vm_node TYPE int;
|
||||||
DEFINE FIELD max_ports_per_vm ON TABLE vm_node TYPE int;
|
DEFINE FIELD max_ports_per_vm ON TABLE vm_node TYPE int;
|
||||||
DEFINE FIELD price ON TABLE vm_node TYPE int;
|
DEFINE FIELD price ON TABLE vm_node TYPE int;
|
||||||
DEFINE FIELD offline_minutes ON TABLE vm_node TYPE int;
|
DEFINE FIELD connected_at ON TABLE vm_node TYPE datetime;
|
||||||
|
DEFINE FIELD disconnected_at ON TABLE vm_node TYPE datetime;
|
||||||
|
|
||||||
DEFINE TABLE new_vm_req TYPE RELATION FROM account TO vm_node SCHEMAFULL;
|
DEFINE TABLE new_vm_req TYPE RELATION FROM account TO vm_node SCHEMAFULL;
|
||||||
DEFINE FIELD hostname ON TABLE new_vm_req TYPE string;
|
DEFINE FIELD hostname ON TABLE new_vm_req TYPE string;
|
||||||
@ -74,7 +75,7 @@ DEFINE FIELD memory_mb ON TABLE deleted_vm TYPE int;
|
|||||||
DEFINE FIELD dtrfs_sha ON TABLE deleted_vm TYPE string;
|
DEFINE FIELD dtrfs_sha ON TABLE deleted_vm TYPE string;
|
||||||
DEFINE FIELD kernel_sha ON TABLE deleted_vm TYPE string;
|
DEFINE FIELD kernel_sha ON TABLE deleted_vm TYPE string;
|
||||||
DEFINE FIELD created_at ON TABLE deleted_vm TYPE datetime;
|
DEFINE FIELD created_at ON TABLE deleted_vm TYPE datetime;
|
||||||
DEFINE FIELD deleted_at ON TABLE deleted_vm TYPE datetime;
|
DEFINE FIELD deleted_at ON TABLE deleted_vm TYPE datetime DEFAULT time::now();
|
||||||
DEFINE FIELD price_per_unit ON TABLE deleted_vm TYPE int;
|
DEFINE FIELD price_per_unit ON TABLE deleted_vm TYPE int;
|
||||||
|
|
||||||
DEFINE TABLE app_node SCHEMAFULL;
|
DEFINE TABLE app_node SCHEMAFULL;
|
||||||
@ -130,8 +131,6 @@ DEFINE FIELD disk_size_gb ON TABLE deleted_app TYPE int;
|
|||||||
DEFINE FIELD created_at ON TABLE deleted_app TYPE datetime;
|
DEFINE FIELD created_at ON TABLE deleted_app TYPE datetime;
|
||||||
DEFINE FIELD deleted_at ON TABLE deleted_app TYPE datetime;
|
DEFINE FIELD deleted_at ON TABLE deleted_app TYPE datetime;
|
||||||
DEFINE FIELD price_per_unit ON TABLE deleted_app TYPE int;
|
DEFINE FIELD price_per_unit ON TABLE deleted_app TYPE int;
|
||||||
DEFINE FIELD locked_nano ON TABLE deleted_app TYPE int;
|
|
||||||
DEFINE FIELD collected_at ON TABLE deleted_app TYPE datetime;
|
|
||||||
DEFINE FIELD mr_enclave ON TABLE deleted_app TYPE string;
|
DEFINE FIELD mr_enclave ON TABLE deleted_app TYPE string;
|
||||||
DEFINE FIELD package_url ON TABLE deleted_app TYPE string;
|
DEFINE FIELD package_url ON TABLE deleted_app TYPE string;
|
||||||
DEFINE FIELD hratls_pubkey ON TABLE deleted_app TYPE string;
|
DEFINE FIELD hratls_pubkey ON TABLE deleted_app TYPE string;
|
||||||
@ -143,6 +142,7 @@ DEFINE TABLE kick TYPE RELATION FROM account TO account;
|
|||||||
DEFINE FIELD created_at ON TABLE kick TYPE datetime;
|
DEFINE FIELD created_at ON TABLE kick TYPE datetime;
|
||||||
DEFINE FIELD reason ON TABLE kick TYPE string;
|
DEFINE FIELD reason ON TABLE kick TYPE string;
|
||||||
DEFINE FIELD contract ON TABLE kick TYPE record<deleted_vm|deleted_app>;
|
DEFINE FIELD contract ON TABLE kick TYPE record<deleted_vm|deleted_app>;
|
||||||
|
DEFINE FIELD node ON TABLE kick TYPE record<vm_node|app_name>;
|
||||||
|
|
||||||
DEFINE TABLE report TYPE RELATION FROM account TO vm_node|app_node;
|
DEFINE TABLE report TYPE RELATION FROM account TO vm_node|app_node;
|
||||||
DEFINE FIELD created_at ON TABLE report TYPE datetime;
|
DEFINE FIELD created_at ON TABLE report TYPE datetime;
|
||||||
160
surql/testing/data.sql
Normal file
160
surql/testing/data.sql
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
INSERT {
|
||||||
|
id: vm_node:online_node,
|
||||||
|
avail_ipv4: 0,
|
||||||
|
avail_ipv6: 0,
|
||||||
|
avail_mem_mb: 25000,
|
||||||
|
avail_ports: 19999,
|
||||||
|
avail_storage_gbs: 700,
|
||||||
|
avail_vcpus: 27,
|
||||||
|
city: 'Pula',
|
||||||
|
connected_at: time::now(),
|
||||||
|
country: 'HR',
|
||||||
|
disconnected_at: time::now() - 1m,
|
||||||
|
ip: '184.107.169.199',
|
||||||
|
max_ports_per_vm: 5,
|
||||||
|
operator: account:operator1,
|
||||||
|
price: 18000,
|
||||||
|
region: 'Istria'
|
||||||
|
};
|
||||||
|
|
||||||
|
INSERT {
|
||||||
|
id: vm_node:offline_node,
|
||||||
|
avail_ipv4: 0,
|
||||||
|
avail_ipv6: 0,
|
||||||
|
avail_mem_mb: 25000,
|
||||||
|
avail_ports: 19999,
|
||||||
|
avail_storage_gbs: 700,
|
||||||
|
avail_vcpus: 27,
|
||||||
|
city: 'Pula',
|
||||||
|
connected_at: time::now() - 1m,
|
||||||
|
country: 'HR',
|
||||||
|
disconnected_at: time::now(),
|
||||||
|
ip: '184.107.200.100',
|
||||||
|
max_ports_per_vm: 5,
|
||||||
|
operator: account:operator2,
|
||||||
|
price: 18000,
|
||||||
|
region: 'Istria'
|
||||||
|
};
|
||||||
|
|
||||||
|
INSERT {
|
||||||
|
id: vm_node:online_node2,
|
||||||
|
avail_ipv4: 0,
|
||||||
|
avail_ipv6: 0,
|
||||||
|
avail_mem_mb: 25000,
|
||||||
|
avail_ports: 19999,
|
||||||
|
avail_storage_gbs: 700,
|
||||||
|
avail_vcpus: 27,
|
||||||
|
city: 'Pula',
|
||||||
|
connected_at: time::now() - 1m,
|
||||||
|
country: 'HR',
|
||||||
|
disconnected_at: time::now() - 10m,
|
||||||
|
ip: '184.2.200.100',
|
||||||
|
max_ports_per_vm: 5,
|
||||||
|
operator: account:operator3,
|
||||||
|
price: 18000,
|
||||||
|
region: 'Istria'
|
||||||
|
};
|
||||||
|
|
||||||
|
INSERT {
|
||||||
|
balance: 10000000000,
|
||||||
|
email: '',
|
||||||
|
escrow: 0,
|
||||||
|
id: account:user1,
|
||||||
|
tmp_locked: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
INSERT {
|
||||||
|
balance: 10000000000,
|
||||||
|
email: '',
|
||||||
|
escrow: 0,
|
||||||
|
id: account:user2,
|
||||||
|
tmp_locked: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
INSERT {
|
||||||
|
id: account:operator1,
|
||||||
|
balance: 10000000000,
|
||||||
|
email: '',
|
||||||
|
escrow: 5_000_000_000_000,
|
||||||
|
tmp_locked: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
INSERT {
|
||||||
|
id: account:operator2,
|
||||||
|
balance: 10000000000,
|
||||||
|
email: '',
|
||||||
|
escrow: 5000000000000,
|
||||||
|
tmp_locked: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
INSERT {
|
||||||
|
id: account:operator3,
|
||||||
|
balance: 0,
|
||||||
|
email: '',
|
||||||
|
escrow: 0,
|
||||||
|
tmp_locked: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
INSERT RELATION {
|
||||||
|
id: active_vm:vm1,
|
||||||
|
in: account:user1,
|
||||||
|
out: vm_node:online_node,
|
||||||
|
collected_at: time::now() - 1h,
|
||||||
|
created_at: time::now() - 1h,
|
||||||
|
disk_size_gb: 400,
|
||||||
|
dtrfs_sha: 'd207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990',
|
||||||
|
hostname: 'vm1',
|
||||||
|
kernel_sha: 'e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919',
|
||||||
|
locked_nano: 1_000_000_000,
|
||||||
|
mapped_ports: [],
|
||||||
|
memory_mb: 80000,
|
||||||
|
price_per_unit: 20000,
|
||||||
|
public_ipv4: '192.168.10.10',
|
||||||
|
public_ipv6: '',
|
||||||
|
vcpus: 40
|
||||||
|
};
|
||||||
|
|
||||||
|
INSERT RELATION {
|
||||||
|
id: active_vm:vm2,
|
||||||
|
in: account:user2,
|
||||||
|
out: vm_node:offline_node,
|
||||||
|
collected_at: time::now() - 10m,
|
||||||
|
created_at: time::now() - 1h,
|
||||||
|
disk_size_gb: 10,
|
||||||
|
dtrfs_sha: 'd207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990',
|
||||||
|
hostname: 'vm1',
|
||||||
|
kernel_sha: 'e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919',
|
||||||
|
locked_nano: 80000000,
|
||||||
|
mapped_ports: [
|
||||||
|
[
|
||||||
|
44551,
|
||||||
|
22
|
||||||
|
]
|
||||||
|
],
|
||||||
|
memory_mb: 5000,
|
||||||
|
price_per_unit: 20000,
|
||||||
|
public_ipv4: '',
|
||||||
|
public_ipv6: '',
|
||||||
|
vcpus: 4
|
||||||
|
};
|
||||||
|
|
||||||
|
INSERT RELATION {
|
||||||
|
id: active_vm:vm3,
|
||||||
|
in: account:user1,
|
||||||
|
out: vm_node:online_node2,
|
||||||
|
collected_at: time::now() - 30d,
|
||||||
|
created_at: time::now() - 60d,
|
||||||
|
disk_size_gb: 10,
|
||||||
|
dtrfs_sha: 'd207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990',
|
||||||
|
hostname: 'vm1',
|
||||||
|
kernel_sha: 'e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919',
|
||||||
|
locked_nano: 25_000_000_000,
|
||||||
|
mapped_ports: [],
|
||||||
|
memory_mb: 1000,
|
||||||
|
price_per_unit: 20000,
|
||||||
|
public_ipv4: '192.168.10.20',
|
||||||
|
public_ipv6: '',
|
||||||
|
vcpus: 1
|
||||||
|
};
|
||||||
33
surql/testing/run_test1.sh
Executable file
33
surql/testing/run_test1.sh
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
|
||||||
|
import="docker run -i --rm --net=host \
|
||||||
|
--volume "$(pwd)/../:/opt/scripts/" \
|
||||||
|
surrealdb/surrealdb:latest import \
|
||||||
|
--endpoint http://127.0.0.1:8000 \
|
||||||
|
--username root --password root \
|
||||||
|
--namespace testing --database testbrain"
|
||||||
|
|
||||||
|
sql="docker run -i --rm --net=host \
|
||||||
|
surrealdb/surrealdb:latest sql \
|
||||||
|
--hide-welcome \
|
||||||
|
--endpoint http://127.0.0.1:8000 \
|
||||||
|
--username root --password root \
|
||||||
|
--namespace testing --database testbrain"
|
||||||
|
|
||||||
|
echo DELETING EXISTING DATA:
|
||||||
|
echo "REMOVE DATABASE testbrain;" | $sql
|
||||||
|
|
||||||
|
echo CREATING TABLES:
|
||||||
|
$import /opt/scripts/tables.sql
|
||||||
|
|
||||||
|
echo LOADING FUNCTIONS:
|
||||||
|
$import /opt/scripts/functions.sql
|
||||||
|
|
||||||
|
echo LOADING MOCK DATA:
|
||||||
|
$import /opt/scripts/testing/data.sql
|
||||||
|
|
||||||
|
echo RUN TIMER FUNCTION:
|
||||||
|
$import /opt/scripts/timer.sql
|
||||||
|
|
||||||
|
echo CHECK IF DATA GOT MODIFIED CORRECTLY:
|
||||||
|
$import /opt/scripts/testing/verification.sql
|
||||||
19
surql/testing/verification.sql
Normal file
19
surql/testing/verification.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
if (select balance from account:operator1)[0].balance != 15_000_000_000 {
|
||||||
|
throw("Operator 1 did't get paid for serving a VM.")
|
||||||
|
};
|
||||||
|
if (select balance from account:user2)[0].balance != 10013400000 {
|
||||||
|
throw("User 2 did't get compensated for his VM going down.")
|
||||||
|
};
|
||||||
|
|
||||||
|
if (select escrow from account:operator2)[0].escrow != 4999986600000 {
|
||||||
|
throw("Operator 2 didn't get punished for not serving a VM.")
|
||||||
|
};
|
||||||
|
if (select id from deleted_vm:vm1).len() == 0 {
|
||||||
|
throw("VM1 didn't get deleted.")
|
||||||
|
};
|
||||||
|
if (select id from active_vm:vm3).len() =! 1 {
|
||||||
|
throw("A mini VM costs more than 25LP per month.")
|
||||||
|
};
|
||||||
|
if (select balance from account:operator3)[0].balance != 23328000000 {
|
||||||
|
throw("Operators without escrow still get a bonus.")
|
||||||
|
};
|
||||||
29
surql/timer.sql
Normal file
29
surql/timer.sql
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
FOR $contract IN (select * from active_vm fetch out) {
|
||||||
|
LET $operator = (select * from $contract.out.operator)[0];
|
||||||
|
LET $node_is_online = $contract.out.connected_at > $contract.out.disconnected_at;
|
||||||
|
LET $price_per_minute = fn::vm_price_per_minute($contract.id);
|
||||||
|
LET $amount_due = (time::now() - $contract.collected_at).mins() * $price_per_minute;
|
||||||
|
LET $amount_paid = IF $amount_due > $contract.locked_nano {
|
||||||
|
$contract.locked_nano
|
||||||
|
} ELSE {
|
||||||
|
$amount_due
|
||||||
|
};
|
||||||
|
LET $escrow_multiplier = IF $operator.escrow < 5_000_000_000_000 { 1 } ELSE { 5 };
|
||||||
|
IF $node_is_online {
|
||||||
|
UPDATE $operator.id SET balance += $amount_paid * $escrow_multiplier;
|
||||||
|
UPDATE $contract.id SET
|
||||||
|
locked_nano -= $amount_paid,
|
||||||
|
collected_at = time::now();
|
||||||
|
} ELSE {
|
||||||
|
LET $compensation = IF $amount_due > $operator.escrow {
|
||||||
|
$operator.escrow
|
||||||
|
} ELSE {
|
||||||
|
$amount_due
|
||||||
|
};
|
||||||
|
UPDATE $operator.id SET escrow -= $compensation;
|
||||||
|
UPDATE $contract.in SET balance += $compensation;
|
||||||
|
};
|
||||||
|
IF $amount_paid >= $contract.locked_nano {
|
||||||
|
fn::delete_vm($contract.id);
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -211,3 +211,37 @@ async fn test_inspect_operator() {
|
|||||||
assert!(!inspect_response.vm_nodes.is_empty());
|
assert!(!inspect_response.vm_nodes.is_empty());
|
||||||
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_kick_contract() {
|
||||||
|
// TODO: implement seed data to test
|
||||||
|
// possibilities
|
||||||
|
// 1. vm contract
|
||||||
|
// 2. app contract
|
||||||
|
// 3. non existent contract
|
||||||
|
// 4. other operator's contract
|
||||||
|
// 5. contract collected more than a week
|
||||||
|
// 6. refund amount calculation
|
||||||
|
// 7. refund of multiple contract kick in a day for same user
|
||||||
|
|
||||||
|
env_logger::builder()
|
||||||
|
.filter_level(log::LevelFilter::Trace)
|
||||||
|
.filter_module("tungstenite", log::LevelFilter::Debug)
|
||||||
|
.filter_module("tokio_tungstenite", log::LevelFilter::Debug)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let db_conn = prepare_test_db().await.unwrap();
|
||||||
|
let operator_wallet = "BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS";
|
||||||
|
let contract_uuid = "26577f1c98674a1780a86cf0490f1270";
|
||||||
|
let reason = "test reason";
|
||||||
|
|
||||||
|
let kick_response = surreal_brain::db::general::WrapperContract::kick_contract(
|
||||||
|
&db_conn,
|
||||||
|
&operator_wallet,
|
||||||
|
&contract_uuid,
|
||||||
|
&reason,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
dbg!(kick_response.unwrap());
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user