From 5597588b58f463fdefa9832fc582c14636d240e2 Mon Sep 17 00:00:00 2001 From: ghe0 Date: Thu, 8 May 2025 13:03:21 +0300 Subject: [PATCH] adding payments for VM contracts --- final_tables.surql | 91 ------------------ scripts/deploy.sh | 5 + src/db/general.rs | 4 +- src/db/vm.rs | 84 +++++++++++++---- src/grpc/vm.rs | 7 +- surql/README.md | 2 + surql/brain-timer.sh | 12 +++ surql/detee-brain-contracts.service | 6 ++ surql/detee-brain-contracts.timer | 9 ++ surql/functions.sql | 30 ++++++ interim_tables.surql => surql/tables.sql | 5 +- surql/testing/data.sql | 114 +++++++++++++++++++++++ surql/testing/run_test1.sh | 42 +++++++++ surql/testing/verification.sql | 13 +++ surql/timer.sql | 26 ++++++ 15 files changed, 337 insertions(+), 113 deletions(-) delete mode 100644 final_tables.surql create mode 100644 surql/README.md create mode 100755 surql/brain-timer.sh create mode 100644 surql/detee-brain-contracts.service create mode 100644 surql/detee-brain-contracts.timer create mode 100644 surql/functions.sql rename interim_tables.surql => surql/tables.sql (97%) create mode 100644 surql/testing/data.sql create mode 100755 surql/testing/run_test1.sh create mode 100644 surql/testing/verification.sql create mode 100644 surql/timer.sql diff --git a/final_tables.surql b/final_tables.surql deleted file mode 100644 index 5b9643b..0000000 --- a/final_tables.surql +++ /dev/null @@ -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; -DEFINE FIELD app_nodes ON TABLE account TYPE array; - -DEFINE TABLE package SCHEMAFULL; -DEFINE FIELD url ON TABLE package TYPE array; - -DEFINE TABLE kernel SCHEMAFULL; -DEFINE FIELD url ON TABLE kernel TYPE array; - -DEFINE TABLE dtrfs SCHEMAFULL; -DEFINE FIELD url ON TABLE dtrfs TYPE array; -DEFINE FIELD kernel ON TABLE dtrfs TYPE record; - -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; -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; - -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; - -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; diff --git a/scripts/deploy.sh b/scripts/deploy.sh index b313280..25f21ab 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -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 diff --git a/src/db/general.rs b/src/db/general.rs index c61808d..62774ae 100644 --- a/src/db/general.rs +++ b/src/db/general.rs @@ -192,8 +192,8 @@ impl Operator { pub async fn inspect(db: &Surreal, account: &str) -> Result, Error> { let mut result = db .query(format!( - "$vm_nodes = (select id from vm_node where operator = account:{account}).id; - $app_nodes = (select id from app_node where operator = account:{account}).id; + "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, diff --git a/src/db/vm.rs b/src/db/vm.rs index 516c517..d0e7f91 100644 --- a/src/db/vm.rs +++ b/src/db/vm.rs @@ -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) -> Result<(), Error> { + Account::get_or_create(db, &self.operator.key().to_string()).await?; + let _: Option = db.upsert(self.id.clone()).content(self).await?; + Ok(()) + } + + pub async fn set_online(db: &Surreal, 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, 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) -> Result<(), Error> { - Account::get_or_create(db, &self.operator.key().to_string()).await?; - let _: Option = 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, } 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, 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 = result.take(0)?; Ok(vm_nodes) @@ -191,7 +200,46 @@ impl NewVmReq { } pub async fn submit(self, db: &Surreal) -> Result<(), Error> { - let _: Vec = 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 = 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 = 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(()) } @@ -750,7 +801,8 @@ impl From<&old_brain::BrainData> for Vec { 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 diff --git a/src/grpc/vm.rs b/src/grpc/vm.rs index d7f6430..7922615 100644 --- a/src/grpc/vm.rs +++ b/src/grpc/vm.rs @@ -53,7 +53,8 @@ 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?; @@ -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; } } } diff --git a/surql/README.md b/surql/README.md new file mode 100644 index 0000000..ce6672a --- /dev/null +++ b/surql/README.md @@ -0,0 +1,2 @@ +This is actually SurrealQL (`.surql`), but the files have the `.sql` +extension to enable syntax coloring. diff --git a/surql/brain-timer.sh b/surql/brain-timer.sh new file mode 100755 index 0000000..90d54a4 --- /dev/null +++ b/surql/brain-timer.sh @@ -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 diff --git a/surql/detee-brain-contracts.service b/surql/detee-brain-contracts.service new file mode 100644 index 0000000..c02e7bb --- /dev/null +++ b/surql/detee-brain-contracts.service @@ -0,0 +1,6 @@ +[Unit] +Description=Process brain contracts + +[Service] +Type=oneshot +ExecStart=/etc/detee/brain/brain-timer.sh diff --git a/surql/detee-brain-contracts.timer b/surql/detee-brain-contracts.timer new file mode 100644 index 0000000..751e01e --- /dev/null +++ b/surql/detee-brain-contracts.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Run detee-brain-contracts.service every minute + +[Timer] +OnCalendar=*-*-* *:*:00 +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/surql/functions.sql b/surql/functions.sql new file mode 100644 index 0000000..3c1b414 --- /dev/null +++ b/surql/functions.sql @@ -0,0 +1,30 @@ +DEFINE FUNCTION OVERWRITE fn::vm_price_per_minute( + $vm_id: record +) { + LET $vm = (select * from $vm_id)[0]; + LET $ip_price = 0; + IF $vm.public_ipv4.len() > 0 { + $ip_price = 10 + }; + 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; +}; + diff --git a/interim_tables.surql b/surql/tables.sql similarity index 97% rename from interim_tables.surql rename to surql/tables.sql index a0cf525..278c7a1 100644 --- a/interim_tables.surql +++ b/surql/tables.sql @@ -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; diff --git a/surql/testing/data.sql b/surql/testing/data.sql new file mode 100644 index 0000000..472be45 --- /dev/null +++ b/surql/testing/data.sql @@ -0,0 +1,114 @@ +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 { + balance: 10000000000, + email: '', + escrow: 0, + id: account:user1, + tmp_locked: 0 +}; + + +INSERT { + balance: 10000000000, + email: '', + escrow: 0, + id: account:user2, + tmp_locked: 0 +}; + +INSERT { + balance: 10000000000, + email: '', + escrow: 5000000000000, + id: account:operator1, + tmp_locked: 0 +}; + +INSERT { + balance: 10000000000, + email: '', + escrow: 5000000000000, + id: account:operator2, + tmp_locked: 0 +}; + +INSERT RELATION { + id: active_vm:vm1, + in: account:user1, + out: vm_node:online_node, + collected_at: time::now(), + created_at: time::now(), + disk_size_gb: 400, + dtrfs_sha: 'd207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990', + hostname: 'vm1', + kernel_sha: 'e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919', + locked_nano: 4000, + 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(), + created_at: time::now(), + 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 +}; + diff --git a/surql/testing/run_test1.sh b/surql/testing/run_test1.sh new file mode 100755 index 0000000..49e2a56 --- /dev/null +++ b/surql/testing/run_test1.sh @@ -0,0 +1,42 @@ +#!/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 RUNNING CRON for 10 minutes: +$import /opt/scripts/timer.sql +$import /opt/scripts/timer.sql +$import /opt/scripts/timer.sql +$import /opt/scripts/timer.sql +$import /opt/scripts/timer.sql +$import /opt/scripts/timer.sql +$import /opt/scripts/timer.sql +$import /opt/scripts/timer.sql +$import /opt/scripts/timer.sql +$import /opt/scripts/timer.sql + +echo CHECK IF DATA GOT MODIFIED CORRECTLY: +$import /opt/scripts/testing/verification.sql diff --git a/surql/testing/verification.sql b/surql/testing/verification.sql new file mode 100644 index 0000000..6a2662a --- /dev/null +++ b/surql/testing/verification.sql @@ -0,0 +1,13 @@ +if (select balance from account:operator1)[0].balance != 10000020000 { + throw("Operator 1 did't get paid for serving a VM.") +}; +if (select balance from account:user2)[0].balance != 10067000000 { + throw("User 2 did't get compensated for his VM going down.") +}; + +if (select escrow from account:operator2)[0].escrow != 4999933000000 { + 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.") +} diff --git a/surql/timer.sql b/surql/timer.sql new file mode 100644 index 0000000..08d2985 --- /dev/null +++ b/surql/timer.sql @@ -0,0 +1,26 @@ +FOR $contract IN (select * from active_vm fetch out) { + LET $operator = (select * from $contract.out.operator); + 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 = IF $price_per_minute > $contract.locked_nano { + $contract.locked_nano + } ELSE { + $price_per_minute + }; + LET $escrow_multiplier = IF $operator.escrow >= 5_000_000_000_000 { 5 } ELSE { 1 }; + IF $node_is_online { + UPDATE $contract.id SET locked_nano -= $amount_due; + UPDATE $operator.id SET balance += $amount_due * $escrow_multiplier; + } ELSE { + LET $compensation = IF $amount_due * 5 > $operator.escrow { + $operator.escrow + } ELSE { + $amount_due * 5 + }; + UPDATE $operator.id SET escrow -= $compensation; + UPDATE $contract.in SET balance += $compensation; + }; + IF $amount_due >= $contract.locked_nano { + fn::delete_vm($contract.id); + }; +};