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