adding payments for VM contracts
This commit is contained in:
parent
5c74962ac6
commit
4888fe3825
84
src/db/vm.rs
84
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<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(())
|
||||
}
|
||||
|
||||
@ -750,7 +801,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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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 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;
|
Loading…
Reference in New Issue
Block a user