adding payments for VM contracts
This commit is contained in:
parent
5c74962ac6
commit
e417e1e73d
@ -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;
|
|
84
src/db/vm.rs
84
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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -750,7 +801,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
|
||||||
|
@ -53,7 +53,8 @@ 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?;
|
||||||
@ -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.
|
28
surql/cron.sql
Normal file
28
surql/cron.sql
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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 = $price_per_minute;
|
||||||
|
IF $amount_due < $contract.locked_nano {
|
||||||
|
$amount_due = $contract.locked_nano;
|
||||||
|
};
|
||||||
|
LET $escrow_multiplier = 1;
|
||||||
|
IF $operator.escrow > 5000 {
|
||||||
|
$escrow_multiplier = 5;
|
||||||
|
};
|
||||||
|
IF $node_is_online {
|
||||||
|
UPDATE $contract.id SET locked_nano -= $amount_due;
|
||||||
|
UPDATE $operator.id SET locked_nano += $amount_due * $escrow_multiplier;
|
||||||
|
} ELSE {
|
||||||
|
LET $compensation = $amount_due * 5;
|
||||||
|
IF $compensation > $operator.escrow {
|
||||||
|
$compensation = $operator.escrow ;
|
||||||
|
};
|
||||||
|
UPDATE $operator.id SET escrow -= $compensation;
|
||||||
|
UPDATE $contract.in SET balance += $compensation;
|
||||||
|
};
|
||||||
|
IF $amount_due == $contract.locked_nano {
|
||||||
|
fn::delete_vm($contract.id);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
28
surql/functions.sql
Normal file
28
surql/functions.sql
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
REMOVE FUNCTION fn::vm_price_per_minute;
|
||||||
|
DEFINE FUNCTION 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
REMOVE FUNCTION fn::delete_vm;
|
||||||
|
DEFINE FUNCTION 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))
|
||||||
|
}]);
|
||||||
|
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;
|
Loading…
Reference in New Issue
Block a user