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
This commit is contained in:
		
							parent
							
								
									929530a4c5
								
							
						
					
					
						commit
						57d3807a17
					
				| @ -98,6 +98,7 @@ impl NewAppReq { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub async fn submit(self, db: &Surreal<Client>) -> Result<Vec<Self>, Error> { |     pub async fn submit(self, db: &Surreal<Client>) -> Result<Vec<Self>, Error> { | ||||||
|  |         // TODO: handle financial transaction
 | ||||||
|         let new_app_req: Vec<Self> = db.insert(NEW_APP_REQ).relation(self).await?; |         let new_app_req: Vec<Self> = db.insert(NEW_APP_REQ).relation(self).await?; | ||||||
|         Ok(new_app_req) |         Ok(new_app_req) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -151,10 +151,9 @@ pub struct Kick { | |||||||
| 
 | 
 | ||||||
| impl Kick { | impl Kick { | ||||||
|     pub async fn kicked_in_a_day(db: &Surreal<Client>, account: &str) -> Result<Vec<Self>, Error> { |     pub async fn kicked_in_a_day(db: &Surreal<Client>, account: &str) -> Result<Vec<Self>, Error> { | ||||||
|         let yesterday = chrono::Utc::now() - chrono::Duration::days(1); |  | ||||||
|         let mut result = db |         let mut result = db | ||||||
|             .query(format!( |             .query(format!( | ||||||
|                 "select * from {KICK} where in = {ACCOUNT}:{account} and created_at > {yesterday};" |                 "select * from {KICK} where out = {ACCOUNT}:{account} and created_at > time::now() - 24h;" | ||||||
|             )) |             )) | ||||||
|             .await?; |             .await?; | ||||||
|         let kicks: Vec<Self> = result.take(0)?; |         let kicks: Vec<Self> = result.take(0)?; | ||||||
| @ -289,39 +288,58 @@ impl WrapperContract { | |||||||
|         contract_uuid: &str, |         contract_uuid: &str, | ||||||
|         reason: &str, |         reason: &str, | ||||||
|     ) -> Result<u64, Error> { |     ) -> Result<u64, Error> { | ||||||
|         let (node_operator, admin, contract_id, node_id, collected_at, price_per_mint, is_vm) = |         let ( | ||||||
|             if let Some(active_vm) = ActiveVmWithNode::get_by_uuid(db, contract_uuid).await? { |             operator_id, | ||||||
|                 let price_per_minute = active_vm.price_per_minute(); |             admin_id, | ||||||
|  |             contract_id, | ||||||
|  |             collected_at, | ||||||
|  |             price_per_mint, | ||||||
|  |             deleted_table, | ||||||
|  |             platform_specific_query, | ||||||
|  |         ) = if let Some(active_vm) = ActiveVmWithNode::get_by_uuid(db, contract_uuid).await? { | ||||||
|  |             let price_per_minute = active_vm.price_per_minute(); | ||||||
| 
 | 
 | ||||||
|                 ( |             ( | ||||||
|                     active_vm.vm_node.operator.to_string(), |                 active_vm.vm_node.operator, | ||||||
|                     active_vm.admin.key().to_string(), |                 active_vm.admin, | ||||||
|                     active_vm.id, |                 active_vm.id, | ||||||
|                     active_vm.vm_node.id, |                 active_vm.collected_at, | ||||||
|                     active_vm.collected_at, |                 price_per_minute, | ||||||
|                     price_per_minute, |                 "deleted_vm", | ||||||
|                     true, |                 " | ||||||
|                 ) |                 hostname = $contract.hostname, | ||||||
|             } else if let Some(active_app) = |                 public_ipv4 = $contract.public_ipv4, | ||||||
|                 ActiveAppWithNode::get_by_uuid(db, contract_uuid).await? |                 public_ipv6 = $contract.public_ipv6, | ||||||
|             { |                 dtrfs_sha = $contract.dtrfs_sha, | ||||||
|                 let price_per_minute = |                 kernel_sha = $contract.kernel_sha, | ||||||
|                     Into::<ActiveApp>::into(active_app.clone()).price_per_minute(); |                 ",
 | ||||||
|  |             ) | ||||||
|  |         } else if let Some(active_app) = ActiveAppWithNode::get_by_uuid(db, contract_uuid).await? { | ||||||
|  |             let price_per_minute = Into::<ActiveApp>::into(active_app.clone()).price_per_minute(); | ||||||
| 
 | 
 | ||||||
|                 ( |             ( | ||||||
|                     active_app.app_node.operator.to_string(), |                 active_app.app_node.operator, | ||||||
|                     active_app.admin.key().to_string(), |                 active_app.admin, | ||||||
|                     active_app.id, |                 active_app.id, | ||||||
|                     active_app.app_node.id, |                 active_app.collected_at, | ||||||
|                     active_app.collected_at, |                 price_per_minute, | ||||||
|                     price_per_minute, |                 "deleted_app", | ||||||
|                     false, |                 " | ||||||
|                 ) |                 app_name = $contract.app_name, | ||||||
|             } else { |                 host_ipv4 = $contract.host_ipv4, | ||||||
|                 return Err(Error::ContractNotFound); |                 mr_enclave = $contract.mr_enclave, | ||||||
|             }; |                 package_url= $contract.package_url, | ||||||
|  |                 hratls_pubkey = $contract.hratls_pubkey, | ||||||
|  |                 ",
 | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             return Err(Error::ContractNotFound); | ||||||
|  |         }; | ||||||
| 
 | 
 | ||||||
|         if node_operator != operator_wallet { |         let operator = operator_id.key().to_string(); | ||||||
|  |         let admin = admin_id.key().to_string(); | ||||||
|  | 
 | ||||||
|  |         if operator != operator_wallet { | ||||||
|             return Err(Error::AccessDenied); |             return Err(Error::AccessDenied); | ||||||
|         } |         } | ||||||
|         let mut minutes_to_refund = |         let mut minutes_to_refund = | ||||||
| @ -333,50 +351,73 @@ impl WrapperContract { | |||||||
|             minutes_to_refund = one_week_minute; |             minutes_to_refund = one_week_minute; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let mut refund_amount = minutes_to_refund * price_per_mint; |         let refund_amount = minutes_to_refund * price_per_mint; | ||||||
| 
 | 
 | ||||||
|         if !Kick::kicked_in_a_day(db, &admin).await?.is_empty() { |         log::debug!("Removing {refund_amount} escrow from {} and giving it to {}", operator, admin); | ||||||
|             refund_amount = 0; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         let mut operator_account = Account::get(db, &node_operator).await?; |         let transaction_query = format!( | ||||||
|         let mut admin_account = Account::get(db, &admin).await?; |             " | ||||||
|  |             BEGIN TRANSACTION; | ||||||
|  |             LET $contract = {contract_id}; | ||||||
|  |             LET $operator_account = {operator_id}; | ||||||
|  |             LET $reason = '{reason}'; | ||||||
|  |             LET $refund_amount = {refund_amount}; | ||||||
|  |             LET $deleted_contract = {deleted_table}:{contract_uuid}; | ||||||
|  |             LET $id = record::id($contract.id); | ||||||
|  |             LET $admin = $contract.in; | ||||||
|  |             LET $node = $contract.out; | ||||||
| 
 | 
 | ||||||
|         if operator_account.escrow < refund_amount { |             -- move contract into deleted state | ||||||
|             refund_amount = operator_account.escrow; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         log::debug!( |             RELATE $admin->{deleted_table}->$node | ||||||
|             "Removing {refund_amount} escrow from {} and giving it to {}", |             SET id = $id, | ||||||
|             node_operator, |                 {platform_specific_query} | ||||||
|             admin |                 mapped_ports = $contract.mapped_ports, | ||||||
|  |                 disk_size_gb = $contract.disk_size_gb, | ||||||
|  |                 vcpus = $contract.vcpus, | ||||||
|  |                 memory_mb = $contract.memory_mb, | ||||||
|  |                 created_at = $contract.created_at, | ||||||
|  |                 deleted_at = time::now(), | ||||||
|  |                 price_per_unit = $contract.price_per_unit | ||||||
|  |             ; | ||||||
|  | 
 | ||||||
|  |             DELETE $contract; | ||||||
|  | 
 | ||||||
|  |             -- calculating refund amount | ||||||
|  |             LET $refund =  IF SELECT * FROM {KICK} WHERE out = $admin.id AND created_at > time::now() - 24h {{ | ||||||
|  |                 0 | ||||||
|  |             }} ELSE IF $operator_account.escrow <= $refund_amount {{ | ||||||
|  |                 $operator_account.escrow | ||||||
|  |             }} ELSE {{ | ||||||
|  |                 $refund_amount; | ||||||
|  |             }}; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             RELATE $operator_account->{KICK}->$admin | ||||||
|  |             SET id = $id, | ||||||
|  |                 reason = $reason, | ||||||
|  |                 contract = $deleted_contract, | ||||||
|  |                 node = $node, | ||||||
|  |                 created_at = time::now() | ||||||
|  |             ; | ||||||
|  | 
 | ||||||
|  |             -- update balances | ||||||
|  |             UPDATE $operator_account SET escrow -= $refund; | ||||||
|  |             IF $operator_account.escrow < 0 {{ | ||||||
|  |                 THROW 'Insufficient funds.' | ||||||
|  |             }}; | ||||||
|  |             UPDATE $admin SET balance += $refund; | ||||||
|  | 
 | ||||||
|  |             SELECT * FROM $refund; | ||||||
|  | 
 | ||||||
|  |             COMMIT TRANSACTION; | ||||||
|  |             ",
 | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         admin_account.balance = admin_account.balance.saturating_add(refund_amount); |         log::trace!("kick_contract transaction_query: {}", &transaction_query); | ||||||
|         operator_account.escrow = operator_account.escrow.saturating_sub(refund_amount); |         let refunded: Option<u64> = db.query(transaction_query).await?.take(14)?; | ||||||
|  |         let refunded_amount = refunded.ok_or(Error::FailedToCreateDBEntry("Refund".to_string()))?; | ||||||
| 
 | 
 | ||||||
|         let kick = Kick { |         Ok(refunded_amount) | ||||||
|             id: RecordId::from((KICK, contract_uuid)), |  | ||||||
|             from_account: operator_account.id.clone(), |  | ||||||
|             to_account: admin_account.id.clone(), |  | ||||||
|             created_at: Datetime::default(), |  | ||||||
|             reason: reason.to_string(), |  | ||||||
|             contract: contract_id.clone(), |  | ||||||
|             node: node_id, |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         kick.submit(db).await?; |  | ||||||
|         operator_account.save(db).await?; |  | ||||||
|         admin_account.save(db).await?; |  | ||||||
| 
 |  | ||||||
|         let contract_id = contract_id.to_string(); |  | ||||||
| 
 |  | ||||||
|         if is_vm && !ActiveVm::delete(db, &contract_id).await? { |  | ||||||
|             return Err(Error::FailedToDeleteContract(format!("vm:{contract_id}"))); |  | ||||||
|         } else if !is_vm && !ActiveApp::delete(db, &contract_id).await? { |  | ||||||
|             return Err(Error::FailedToDeleteContract(format!("app:{contract_id}"))); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(refund_amount) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -131,8 +131,6 @@ DEFINE FIELD disk_size_gb ON TABLE deleted_app TYPE int; | |||||||
| DEFINE FIELD created_at ON TABLE deleted_app TYPE datetime; | DEFINE FIELD created_at ON TABLE deleted_app TYPE datetime; | ||||||
| DEFINE FIELD deleted_at ON TABLE deleted_app TYPE datetime; | DEFINE FIELD deleted_at ON TABLE deleted_app TYPE datetime; | ||||||
| DEFINE FIELD price_per_unit ON TABLE deleted_app TYPE int; | DEFINE FIELD price_per_unit ON TABLE deleted_app TYPE int; | ||||||
| DEFINE FIELD locked_nano ON TABLE deleted_app TYPE int; |  | ||||||
| DEFINE FIELD collected_at ON TABLE deleted_app TYPE datetime; |  | ||||||
| DEFINE FIELD mr_enclave ON TABLE deleted_app TYPE string; | DEFINE FIELD mr_enclave ON TABLE deleted_app TYPE string; | ||||||
| DEFINE FIELD package_url ON TABLE deleted_app TYPE string; | DEFINE FIELD package_url ON TABLE deleted_app TYPE string; | ||||||
| DEFINE FIELD hratls_pubkey ON TABLE deleted_app TYPE string; | DEFINE FIELD hratls_pubkey ON TABLE deleted_app TYPE string; | ||||||
|  | |||||||
| @ -211,3 +211,37 @@ async fn test_inspect_operator() { | |||||||
|     assert!(!inspect_response.vm_nodes.is_empty()); |     assert!(!inspect_response.vm_nodes.is_empty()); | ||||||
|     assert_eq!(&inspect_response.vm_nodes[0].operator, &operator_key.pubkey); |     assert_eq!(&inspect_response.vm_nodes[0].operator, &operator_key.pubkey); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[tokio::test] | ||||||
|  | async fn test_kick_contract() { | ||||||
|  |     // TODO: implement seed data to test
 | ||||||
|  |     // possibilities
 | ||||||
|  |     // 1. vm contract
 | ||||||
|  |     // 2. app contract
 | ||||||
|  |     // 3. non existent contract
 | ||||||
|  |     // 4. other operator's contract
 | ||||||
|  |     // 5. contract collected more than a week
 | ||||||
|  |     // 6. refund amount calculation
 | ||||||
|  |     // 7. refund of multiple contract kick in a day for same user
 | ||||||
|  | 
 | ||||||
|  |     env_logger::builder() | ||||||
|  |         .filter_level(log::LevelFilter::Trace) | ||||||
|  |         .filter_module("tungstenite", log::LevelFilter::Debug) | ||||||
|  |         .filter_module("tokio_tungstenite", log::LevelFilter::Debug) | ||||||
|  |         .init(); | ||||||
|  | 
 | ||||||
|  |     let db_conn = prepare_test_db().await.unwrap(); | ||||||
|  |     let operator_wallet = "BFopWmwcZAMF1h2PFECZNdEucdZfnZZ32p6R9ZaBiVsS"; | ||||||
|  |     let contract_uuid = "26577f1c98674a1780a86cf0490f1270"; | ||||||
|  |     let reason = "test reason"; | ||||||
|  | 
 | ||||||
|  |     let kick_response = surreal_brain::db::general::WrapperContract::kick_contract( | ||||||
|  |         &db_conn, | ||||||
|  |         &operator_wallet, | ||||||
|  |         &contract_uuid, | ||||||
|  |         &reason, | ||||||
|  |     ) | ||||||
|  |     .await; | ||||||
|  | 
 | ||||||
|  |     dbg!(kick_response.unwrap()); | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user