Compare commits

..

6 Commits

Author SHA1 Message Date
57d3807a17
kick contract transaction
one single transaction query to execute all the kick operation
Remove unused fields from deleted_app schema
a basic test for kick and possibilities
2025-05-20 14:07:11 +05:30
929530a4c5
kick contract implemented
app pricing calculation
add node in kick schema and type
improved handling
Clone on all app types
handle expected error on kick contract
validate both app and vm contracts
2025-05-20 14:07:11 +05:30
4c647eef6a
register operator 2025-05-20 14:07:11 +05:30
6a85acda9e
when node reconnects, send deleted VMs
previously, active VMs were sent
2025-05-20 01:45:31 +03:00
32b587c6c5
merged proto branch 2025-05-20 00:19:04 +03:00
0fc9b8003a
adding payments for VM contracts 2025-05-18 16:08:18 +03:00
17 changed files with 387 additions and 120 deletions

2
Cargo.lock generated

@ -1000,7 +1000,7 @@ dependencies = [
[[package]]
name = "detee-shared"
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 = [
"bincode 2.0.1",
"prost",

@ -13,7 +13,7 @@ serde_yaml = "0.9.34"
surrealdb = "2.2.2"
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
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"
bs58 = "0.5.1"
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
ssh $server mkdir -p /etc/detee/brain/
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 start detee-brain.service
ssh $server systemctl enable --now detee-brain-contracts.timer

@ -236,8 +236,8 @@ impl Operator {
pub async fn inspect(db: &Surreal<Client>, account: &str) -> Result<Option<Self>, 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;
"LET $vm_nodes = (select id from vm_node where operator = account:{account}).id;
LET $app_nodes = (select id from app_node where operator = account:{account}).id;
select *,
id as account,
email,

@ -30,7 +30,27 @@ pub struct VmNode {
pub avail_ports: u32,
pub max_ports_per_vm: u32,
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)]
@ -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)]
pub struct VmNodeWithReports {
pub id: RecordId,
@ -75,13 +87,10 @@ pub struct VmNodeWithReports {
pub avail_ports: u32,
pub max_ports_per_vm: u32,
pub price: u64,
pub offline_minutes: u64,
pub reports: Vec<Report>,
}
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(
db: &Surreal<Client>,
filters: vm_proto::VmNodeFilters,
@ -115,7 +124,7 @@ impl VmNodeWithReports {
if !filters.ip.is_empty() {
query += &format!("&& ip = '{}' ", filters.ip);
}
query += ";";
query += " && connected_at > disconnected_at;";
let mut result = db.query(query).await?;
let vm_nodes: Vec<Self> = result.take(0)?;
Ok(vm_nodes)
@ -191,7 +200,46 @@ impl NewVmReq {
}
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(())
}
}
@ -366,9 +414,12 @@ impl ActiveVm {
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?;
NewVmReq::delete(db, id).await?;
db.query(format!("UPDATE {ACCOUNT}:{admin_account} SET tmp_locked -= {locked_nano};"))
.await?;
Ok(())
}
@ -773,7 +824,8 @@ impl From<&old_brain::BrainData> for Vec<VmNode> {
avail_ports: old_node.avail_ports,
max_ports_per_vm: old_node.max_ports_per_vm,
price: old_node.price,
offline_minutes: old_node.offline_minutes,
disconnected_at: Datetime::default(),
connected_at: Datetime::default(),
});
}
nodes

@ -30,7 +30,7 @@ impl VmDaemonServer {
#[tonic::async_trait]
impl BrainVmDaemon for VmDaemonServer {
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(
&self,
@ -53,17 +53,18 @@ impl BrainVmDaemon for VmDaemonServer {
avail_ipv6: 0,
avail_ports: 0,
max_ports_per_vm: 0,
offline_minutes: 0,
disconnected_at: surrealdb::sql::Datetime::default(),
connected_at: surrealdb::sql::Datetime::default(),
}
.register(&self.db)
.await?;
info!("Sending existing contracts to {}", req.node_pubkey);
let contracts = db::ActiveVmWithNode::list_by_node(&self.db, &req.node_pubkey).await?;
info!("Sending deleted contracts to {}", req.node_pubkey);
let deleted_vms = db::DeletedVm::list_by_node(&self.db, &req.node_pubkey).await?;
let (tx, rx) = mpsc::channel(6);
tokio::spawn(async move {
for contract in contracts {
let _ = tx.send(Ok(contract.into())).await;
for deleted_vm in deleted_vms {
let _ = tx.send(Ok(deleted_vm.into())).await;
}
});
let output_stream = ReceiverStream::new(rx);
@ -83,6 +84,7 @@ impl BrainVmDaemon for VmDaemonServer {
&auth.signature,
)?;
info!("Daemon {} connected to receive brain messages", pubkey);
let _ = db::VmNode::set_online(&self.db, &pubkey).await;
let (tx, rx) = mpsc::channel(6);
{
@ -194,7 +196,8 @@ impl BrainVmDaemon for VmDaemonServer {
_ => {}
},
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

@ -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

@ -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

@ -0,0 +1,6 @@
[Unit]
Description=Process brain contracts
[Service]
Type=oneshot
ExecStart=/etc/detee/brain/brain-timer.sh

@ -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

@ -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 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 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 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 kernel_sha ON TABLE deleted_vm TYPE string;
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 TABLE app_node SCHEMAFULL;

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

@ -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

@ -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

@ -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);
};
};