Kick Contract #2
@ -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