Compare commits

..

1 Commits

Author SHA1 Message Date
af3c3103e2
add support for operators 2025-02-13 17:55:46 +02:00
3 changed files with 99 additions and 24 deletions

@ -18,6 +18,10 @@ pub enum Error {
InsufficientFunds,
#[error("Could not find contract {0}")]
VmContractNotFound(String),
#[error("This error should never happen.")]
ImpossibleError,
#[error("You don't have the required permissions for this operation.")]
AccessDenied,
}
#[derive(Clone, Default)]
@ -26,6 +30,7 @@ pub struct AccountData {
pub tmp_locked: u64,
// holds reasons why VMs of this account got kicked
pub kicked_for: Vec<String>,
pub last_kick: chrono::DateTime<Utc>,
// holds accounts that banned this account
pub banned_by: HashSet<String>,
}
@ -99,14 +104,15 @@ pub struct VmContract {
pub dtrfs_sha: String,
pub created_at: chrono::DateTime<Utc>,
pub updated_at: chrono::DateTime<Utc>,
// price per unit per minute
// recommended value is 20000
/// price per unit per minute
pub price_per_unit: u64,
pub locked_nano: u64,
pub collected_at: chrono::DateTime<Utc>,
}
impl VmContract {
/// total hardware units of this VM
fn total_units(&self) -> u64 {
// TODO: Optimize this based on price of hardware.
// I tried, but this can be done better.
@ -117,7 +123,7 @@ impl VmContract {
+ (!self.public_ipv4.is_empty() as u64 * 10)
}
// Returns price per minute in nanoLP
/// Returns price per minute in nanoLP
fn price_per_minute(&self) -> u64 {
self.total_units() * self.price_per_unit
}
@ -182,6 +188,7 @@ impl BrainData {
tmp_locked: 0,
kicked_for: Vec::new(),
banned_by: HashSet::new(),
last_kick: chrono::Utc::now(),
};
return balance;
}
@ -271,6 +278,63 @@ impl BrainData {
nodes.push(node);
}
// todo: this should also support Apps
/// Receives: operator, contract uuid, reason of kick
pub async fn kick_contract(
&self,
operator: &str,
uuid: &str,
reason: &str,
) -> Result<(), Error> {
let contract = self.find_contract_by_uuid(uuid)?;
let mut operator_data = self
.operators
.get_mut(operator)
.ok_or(Error::AccessDenied)?;
if !operator_data.vm_nodes.contains(&contract.node_pubkey) {
return Err(Error::AccessDenied);
}
let mut minutes_to_refund = chrono::Utc::now()
.signed_duration_since(contract.updated_at)
.num_minutes()
.abs() as u64;
// cap refund at 1 week
if minutes_to_refund > 10080 {
minutes_to_refund = 10080;
}
let mut refund_ammount = minutes_to_refund * contract.price_per_minute();
let mut user = self
.accounts
.get_mut(&contract.admin_pubkey)
.ok_or(Error::ImpossibleError)?;
// check if he got kicked within the last day
if !chrono::Utc::now()
.signed_duration_since(user.last_kick)
.gt(&chrono::Duration::days(1))
{
refund_ammount = 0;
}
if operator_data.escrow < refund_ammount {
refund_ammount = operator_data.escrow;
}
user.balance += refund_ammount;
user.kicked_for.push(reason.to_string());
operator_data.escrow -= refund_ammount;
self.delete_vm(grpc::DeleteVmReq {
uuid: contract.uuid,
admin_pubkey: contract.admin_pubkey,
})
.await?;
Ok(())
}
pub fn report_node(&self, admin_pubkey: String, node: &str, report: String) {
let mut nodes = self.vm_nodes.write().unwrap();
if let Some(node) = nodes.iter_mut().find(|n| n.public_key == node) {
@ -363,17 +427,10 @@ impl BrainData {
}
pub async fn delete_vm(&self, delete_vm: grpc::DeleteVmReq) -> Result<(), Error> {
let contract = match self.find_contract_by_uuid(&delete_vm.uuid) {
Some(contract) => {
if contract.admin_pubkey != delete_vm.admin_pubkey {
return Err(Error::VmContractNotFound(delete_vm.uuid));
}
contract
}
None => {
return Err(Error::VmContractNotFound(delete_vm.uuid));
}
};
let contract = self.find_contract_by_uuid(&delete_vm.uuid)?;
if contract.admin_pubkey != delete_vm.admin_pubkey {
return Err(Error::AccessDenied);
}
info!("Found vm {}. Deleting...", delete_vm.uuid);
if let Some(daemon_tx) = self.daemon_tx.get(&contract.node_pubkey) {
debug!(
@ -582,7 +639,7 @@ impl BrainData {
let uuid = req.uuid.clone();
info!("Inserting new vm update request in memory: {req:?}");
let node_pubkey = match self.find_contract_by_uuid(&req.uuid) {
Some(contract) => {
Ok(contract) => {
if contract.admin_pubkey != req.admin_pubkey {
let _ = tx.send(grpc::UpdateVmResp {
uuid,
@ -593,7 +650,7 @@ impl BrainData {
}
contract.node_pubkey
}
None => {
Err(_) => {
log::warn!(
"Received UpdateVMReq for a contract that does not exist: {}",
req.uuid
@ -753,9 +810,13 @@ impl BrainData {
.cloned()
}
pub fn find_contract_by_uuid(&self, uuid: &str) -> Option<VmContract> {
pub fn find_contract_by_uuid(&self, uuid: &str) -> Result<VmContract, Error> {
let contracts = self.vm_contracts.read().unwrap();
contracts.iter().cloned().find(|c| c.uuid == uuid)
contracts
.iter()
.cloned()
.find(|c| c.uuid == uuid)
.ok_or(Error::VmContractNotFound(uuid.to_string()))
}
pub fn list_all_contracts(&self) -> Vec<VmContract> {

@ -216,7 +216,7 @@ impl BrainCli for BrainCliMock {
async fn report_node(&self, req: Request<ReportNodeReq>) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?;
match self.data.find_contract_by_uuid(&req.contract) {
Some(contract)
Ok(contract)
if contract.admin_pubkey == req.admin_pubkey
&& contract.node_pubkey == req.node_pubkey =>
{
@ -238,8 +238,8 @@ impl BrainCli for BrainCliMock {
info!("CLI {} requested ListVMVmContractsStream", req.admin_pubkey);
let contracts = match req.uuid.is_empty() {
false => match self.data.find_contract_by_uuid(&req.uuid) {
Some(contract) => vec![contract],
None => Vec::new(),
Ok(contract) => vec![contract],
_ => Vec::new(),
},
true => self.data.find_vm_contracts_by_admin(&req.admin_pubkey),
};
@ -291,8 +291,8 @@ impl BrainCli for BrainCliMock {
async fn register_operator(
&self,
req: tonic::Request<RegOperatorReq>,
) -> std::result::Result<tonic::Response<Empty>, tonic::Status> {
req: Request<RegOperatorReq>,
) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?;
info!("Regitering new operator: {req:?}");
match self.data.register_operator(req) {
@ -301,6 +301,18 @@ impl BrainCli for BrainCliMock {
}
}
async fn kick_contract(&self, req: Request<KickReq>) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?;
match self
.data
.kick_contract(&req.operator_wallet, &req.contract_uuid, &req.reason)
.await
{
Ok(()) => Ok(Response::new(Empty {})),
Err(e) => Err(Status::permission_denied(e.to_string())),
}
}
type ListOperatorsStream =
Pin<Box<dyn Stream<Item = Result<ListOperatorsResp, Status>> + Send>>;
async fn list_operators(
@ -419,6 +431,7 @@ impl_pubkey_getter!(ReportNodeReq, admin_pubkey);
impl_pubkey_getter!(ListVmContractsReq, admin_pubkey);
impl_pubkey_getter!(RegisterVmNodeReq, node_pubkey);
impl_pubkey_getter!(RegOperatorReq, pubkey);
impl_pubkey_getter!(KickReq, operator_wallet);
impl_pubkey_getter!(VmNodeFilters);
impl_pubkey_getter!(Empty);

@ -234,8 +234,9 @@ message ReportNodeReq {
}
message KickReq {
string uuid = 1;
string reason = 2;
string operator_wallet = 1;
string contract_uuid = 2;
string reason = 3;
}
service BrainCli {