diff --git a/Cargo.lock b/Cargo.lock index abd2e50..1ae6dea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,7 +1,6 @@ -# SPDX-License-Identifier: Apache-2.0 - # This file is automatically @generated by Cargo. # It is not intended for manual editing. +# SPDX-License-Identifier: Apache-2.0 version = 4 [[package]] @@ -1184,7 +1183,7 @@ dependencies = [ [[package]] name = "detee-shared" version = "0.1.0" -source = "git+ssh://git@gitea.detee.cloud/testnet/proto.git?branch=surreal_brain_app#0b195b4589e4ec689af7ddca27dc051716ecee78" +source = "git+ssh://git@gitea.detee.cloud/testnet/proto.git?branch=credits-v2#f344c171c5a8d7ae8cad1628396e6b3a1af0f1ba" dependencies = [ "bincode", "prost", diff --git a/Cargo.toml b/Cargo.toml index 757ae85..824da56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ tokio-retry = "0.3.0" detee-sgx = { git = "ssh://git@gitea.detee.cloud/testnet/detee-sgx.git", branch = "hratls", features=["hratls", "qvl"] } shadow-rs = { version = "1.1.1", features = ["metadata"] } -detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto.git", branch = "surreal_brain_app" } +detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto.git", branch = "credits-v2" } # detee-shared = { path = "../detee-shared" } [build-dependencies] diff --git a/src/bin/detee-cli.rs b/src/bin/detee-cli.rs index 484aedd..7b221db 100644 --- a/src/bin/detee-cli.rs +++ b/src/bin/detee-cli.rs @@ -160,7 +160,7 @@ fn clap_cmd() -> Command { .help("for how many hours should the app run") .default_value("1") .value_parser(clap::value_parser!(u64).range(1..5000)) - .long_help("How long should the app run for so it locks up LP accordingly") + .long_help("How long should the app run for so it locks up credits accordingly") ) .arg( Arg::new("price") @@ -347,16 +347,16 @@ fn clap_cmd() -> Command { .arg( Arg::new("memory") .long("memory") - .default_value("1000") - .value_parser(clap::value_parser!(u32).range(800..123000)) - .help("memory in MB") + .default_value("1") + .value_parser(clap::value_parser!(u32).range(1..500)) + .help("memory in GiB") ) .arg( Arg::new("disk") .long("disk") .default_value("10") .value_parser(clap::value_parser!(u32).range(5..500)) - .help("disk size in GB") + .help("disk size in GiB") ) .arg( Arg::new("distribution") @@ -375,8 +375,8 @@ fn clap_cmd() -> Command { .arg( Arg::new("price") .long("price") - .help("price per unit per minute; check docs") - .default_value("20000") + .help("maxium accepted price per unit per minute") + .default_value("4000") .value_parser(clap::value_parser!(u64).range(1..50000000)) ) .arg( @@ -437,7 +437,7 @@ fn clap_cmd() -> Command { .long_about("Allows you to update the hardware or the lifetime".to_string() + "\nAny hardware modifiations will restart the VM." + "\nChanging the lifetime of a VM will not restart." + - "\nIf changing the lifetime to a higher value, LP will locked accordingly.") + "\nIf changing the lifetime to a higher value, credits will locked accordingly.") .arg( Arg::new("uuid") .help("supply the uuid of the VM you wish to upgrade") @@ -537,7 +537,7 @@ fn clap_cmd() -> Command { .arg( Arg::new("escrow") .long("escrow") - .help("At least 5000 LP is required as escrow") + .help("At least 5000 credits is required as escrow") .long_help("Escrow is used by node operators to guarantee quality.".to_owned() + "\nBefore adding escrow, make sure you booted a node under your account." + "\nWhen all your nodes got decomissioned, your escrow gets automatically returned.") diff --git a/src/config.rs b/src/config.rs index ab08576..30735ec 100644 --- a/src/config.rs +++ b/src/config.rs @@ -36,10 +36,10 @@ impl super::HumanOutput for AccountData { } if !self.wallet_path.is_empty() { println!("The address of your DeTEE wallet is {}", self.wallet_address); - println!("The balance of your account is {} LP", self.account_balance); + println!("The balance of your account is {} credits", self.account_balance); if self.locked_funds != 0.0 { println!( - "WARNING! {} LP is temporary locked, waiting for a Contract.", + "WARNING! {} credits is temporary locked, waiting for a Contract.", self.locked_funds ); } diff --git a/src/general/grpc.rs b/src/general/grpc.rs index 540db7b..7117337 100644 --- a/src/general/grpc.rs +++ b/src/general/grpc.rs @@ -96,7 +96,7 @@ pub async fn kick_contract(contract_uuid: String, reason: String) -> Result Result<(), Error> { diff --git a/src/general/operators.rs b/src/general/operators.rs index 39c4194..229508a 100644 --- a/src/general/operators.rs +++ b/src/general/operators.rs @@ -35,7 +35,7 @@ pub fn register(escrow: u64, email: String) -> Result Result, grpc::Er pub fn kick(contract_uuid: String, reason: String) -> Result { let nano_lp = block_on(grpc::kick_contract(contract_uuid, reason))?; Ok(crate::SimpleOutput::from( - format!("Successfully terminated contract. Refunded {} nanoLP.", nano_lp).as_str(), + format!("Successfully terminated contract. Refunded {} nanocredits.", nano_lp).as_str(), )) } diff --git a/src/sgx/grpc_brain.rs b/src/sgx/grpc_brain.rs index c507980..bbba26f 100644 --- a/src/sgx/grpc_brain.rs +++ b/src/sgx/grpc_brain.rs @@ -67,7 +67,7 @@ impl crate::HumanOutput for AppContract { "The App has {} vCPUS, {}MB of memory and a disk of {} GB.", app_resource.vcpus, app_resource.memory_mb, app_resource.disk_size_gb ); - println!("You have locked {} nanoLP in the contract, that get collected at a rate of {} nanoLP per minute.", + println!("You have locked {} nanocredits in the contract, that get collected at a rate of {} nanocredits per minute.", self.locked_nano, self.nano_per_minute); } } diff --git a/src/sgx/mod.rs b/src/sgx/mod.rs index 6c1ea0c..d1c7759 100644 --- a/src/sgx/mod.rs +++ b/src/sgx/mod.rs @@ -48,7 +48,7 @@ pub struct AppContract { pub memory_mb: u32, #[tabled(rename = "Disk (GB)")] pub disk_size_gb: u32, - #[tabled(rename = "LP/h")] + #[tabled(rename = "credits/h")] pub cost_h: String, #[tabled(rename = "time left", display_with = "display_mins")] pub time_left: u64, @@ -268,7 +268,7 @@ impl From for TabledAppNode { operator: brain_node.operator, location: brain_node.city + ", " + &brain_node.region + ", " + &brain_node.country, public_ip: brain_node.ip, - price: format!("{} nanoLP/min", brain_node.price), + price: format!("{} nanocredits/min", brain_node.price), reports: brain_node.reports.len(), } } diff --git a/src/snp/cli_handler.rs b/src/snp/cli_handler.rs index db206ea..76c16fb 100644 --- a/src/snp/cli_handler.rs +++ b/src/snp/cli_handler.rs @@ -69,8 +69,8 @@ fn handle_vm_deploy(matches: &ArgMatches) -> Result("vcpus").unwrap(), - memory_mb: *matches.get_one::("memory").unwrap(), - disk_size_gb: *matches.get_one::("disk").unwrap(), + memory_gib: *matches.get_one::("memory").unwrap(), + disk_size_gib: *matches.get_one::("disk").unwrap(), dtrfs: None, hours: *matches.get_one::("hours").unwrap(), price: *matches.get_one::("price").unwrap(), diff --git a/src/snp/deploy.rs b/src/snp/deploy.rs index 8d11d29..1541bb1 100644 --- a/src/snp/deploy.rs +++ b/src/snp/deploy.rs @@ -4,8 +4,7 @@ use super::{ grpc::{self, proto}, injector, Distro, Dtrfs, Error, VmSshArgs, DEFAULT_ARCHLINUX, DEFAULT_DTRFS, }; -use crate::config::Config; -use crate::utils::block_on; +use crate::{config::Config, utils::block_on}; use log::{debug, info}; use serde::{Deserialize, Serialize}; @@ -51,8 +50,8 @@ pub struct Request { pub ipv4: IPv4Config, pub public_ipv6: bool, pub vcpus: u32, - pub memory_mb: u32, - pub disk_size_gb: u32, + pub memory_gib: u32, + pub disk_size_gib: u32, pub dtrfs: Option, pub distro: Option, } @@ -70,8 +69,8 @@ impl Request { } pub fn deploy(&self) -> Result { - let (node_ip, new_vm_resp) = self.send_vm_request()?; - info!("Got confirmation from the node {node_ip} that VM started."); + let (vcpus, new_vm_resp) = self.calculate_and_send_request()?; + info!("Got confirmation from the node that the VM started."); debug!("IPs and ports assigned by node are: {new_vm_resp:#?}"); if !new_vm_resp.error.is_empty() { return Err(Error::Node(new_vm_resp.error)); @@ -83,7 +82,7 @@ impl Request { let args = new_vm_resp.args.ok_or(Error::NoMeasurement)?; let measurement_args = injector::Args { uuid: new_vm_resp.uuid.clone(), - vcpus: self.vcpus, + vcpus, kernel: kernel_sha, initrd: dtrfs_sha, args: args.clone(), @@ -107,10 +106,107 @@ impl Request { Ok(ssh_args) } - // returns node IP and data regarding the new VM - fn send_vm_request(&self) -> Result<(String, proto::NewVmResp), Error> { - let admin_pubkey = Config::get_detee_wallet()?; - let node = self.get_node()?; + /// returns number of vCPUs and response from the daemon + fn calculate_and_send_request(&self) -> Result<(u32, proto::NewVmResp), Error> { + let new_vm_req = self.get_cheapest_offer()?; + let vcpus = new_vm_req.vcpus; + + eprintln!( + "Locking {} credits for {} hours of the following HW spec: {} vCPUs, {} MiB Mem, {} MiB Disk", + new_vm_req.locked_nano as f64 / 1_000_000_000_f64, + self.hours, + new_vm_req.vcpus, + new_vm_req.memory_mib, + new_vm_req.disk_size_mib + ); + + // eprint!( + // "Node price: {}/unit/minute. Total Units for hardware requested: {}. ", + // node_price as f64 / 1_000_000_000.0, + // total_units, + // ); + // eprintln!( + // "Locking {} LP (offering the VM for {} hours).", + // locked_nano as f64 / 1_000_000_000.0, + // hours + // ); + + let new_vm_resp = block_on(grpc::create_vm(new_vm_req))?; + if !new_vm_resp.error.is_empty() { + return Err(Error::Node(new_vm_resp.error)); + } + Ok((vcpus, new_vm_resp)) + } + + fn get_cheapest_offer(&self) -> Result { + let (free_ports, offers_ipv4) = match &self.ipv4 { + IPv4Config::PublishPorts(vec) => (vec.len() as u32, false), + IPv4Config::PublicIPv4 => (0, true), + }; + let filters = proto::VmNodeFilters { + free_ports, + offers_ipv4, + offers_ipv6: self.public_ipv6, + vcpus: self.vcpus, + memory_mib: self.memory_gib * 1024, + storage_mib: self.disk_size_gib * 1024, + country: self.location.country.clone().unwrap_or_default(), + region: self.location.region.clone().unwrap_or_default(), + city: self.location.city.clone().unwrap_or_default(), + ip: self.location.node_ip.clone().unwrap_or_default(), + node_pubkey: String::new(), + }; + let node_list = match block_on(grpc::get_node_list(filters)) { + Ok(node_list) => Ok(node_list), + Err(e) => { + log::error!("Coult not get node from brain: {e:?}"); + Err(Error::NoValidNodeFound) + } + }?; + + let mut node_list_iter = node_list.iter(); + let mut final_request = + self.calculate_vm_request(Config::get_detee_wallet()?, node_list_iter.next().unwrap()); + while let Some(node) = node_list_iter.next() { + let new_vm_req = self.calculate_vm_request(Config::get_detee_wallet()?, node); + if new_vm_req.locked_nano < final_request.locked_nano { + final_request = new_vm_req; + } + } + + Ok(final_request) + } + + fn calculate_vm_request( + &self, + admin_pubkey: String, + node: &proto::VmNodeListResp, + ) -> proto::NewVmReq { + let memory_per_cpu = node.memory_mib / node.vcpus; + let disk_per_cpu = node.disk_mib / node.vcpus; + let mut vcpus = self.vcpus; + if vcpus < (self.memory_gib * 1024).div_ceil(memory_per_cpu as u32) { + vcpus = (self.memory_gib * 1024).div_ceil(memory_per_cpu as u32); + } + if vcpus < (self.disk_size_gib * 1024).div_ceil(disk_per_cpu as u32) { + vcpus = (self.disk_size_gib * 1024).div_ceil(disk_per_cpu as u32); + } + + let memory_mib = vcpus * memory_per_cpu as u32; + let disk_size_mib = vcpus * disk_per_cpu as u32; + + let nanocredits = super::calculate_nanocredits( + vcpus, + memory_mib, + disk_size_mib, + match self.ipv4 { + IPv4Config::PublishPorts(_) => false, + IPv4Config::PublicIPv4 => true, + }, + self.hours, + node.price, + ); + let (extra_ports, public_ipv4): (Vec, bool) = match &self.ipv4 { IPv4Config::PublishPorts(vec) => (vec.to_vec(), false), IPv4Config::PublicIPv4 => (Vec::new(), true), @@ -124,63 +220,31 @@ impl Request { DEFAULT_DTRFS.dtrfs_sha.clone(), ), }; - let locked_nano = super::calculate_nanolp( - self.vcpus, - self.memory_mb, - self.disk_size_gb, - public_ipv4, - self.hours, - self.price, - ); + let brain_req = proto::NewVmReq { uuid: String::new(), hostname: self.hostname.clone(), admin_pubkey, - node_pubkey: node.node_pubkey, + node_pubkey: node.node_pubkey.clone(), extra_ports, public_ipv4, public_ipv6: self.public_ipv6, - disk_size_gb: self.disk_size_gb, - vcpus: self.vcpus, - memory_mb: self.memory_mb, + disk_size_mib, + vcpus, + memory_mib, kernel_url, kernel_sha, dtrfs_url, dtrfs_sha, - price_per_unit: self.price, - locked_nano, + price_per_unit: node.price, + locked_nano: nanocredits, }; - let new_vm_resp = block_on(grpc::create_vm(brain_req))?; - if !new_vm_resp.error.is_empty() { - return Err(Error::Node(new_vm_resp.error)); - } - Ok((node.ip, new_vm_resp)) - } - pub fn get_node(&self) -> Result { - let (free_ports, offers_ipv4) = match &self.ipv4 { - IPv4Config::PublishPorts(vec) => (vec.len() as u32, false), - IPv4Config::PublicIPv4 => (0, true), - }; - let filters = proto::VmNodeFilters { - free_ports, - offers_ipv4, - offers_ipv6: self.public_ipv6, - vcpus: self.vcpus, - memory_mb: self.memory_mb, - storage_gb: self.disk_size_gb, - country: self.location.country.clone().unwrap_or_default(), - region: self.location.region.clone().unwrap_or_default(), - city: self.location.city.clone().unwrap_or_default(), - ip: self.location.node_ip.clone().unwrap_or_default(), - node_pubkey: String::new(), - }; - match block_on(grpc::get_one_node(filters)) { - Ok(node) => Ok(node), - Err(e) => { - log::error!("Coult not get node from brain: {e:?}"); - Err(Error::NoValidNodeFound) - } - } + debug!( + "Node {} can offer the VM at {} nanocredits for {} hours. Spec: {} vCPUs, {} MiB mem, {} MiB disk.", + node.ip, brain_req.locked_nano, self.hours, brain_req.vcpus, brain_req.memory_mib, brain_req.disk_size_mib + ); + + brain_req } } diff --git a/src/snp/grpc.rs b/src/snp/grpc.rs index 45f0549..cade76f 100644 --- a/src/snp/grpc.rs +++ b/src/snp/grpc.rs @@ -74,7 +74,7 @@ impl crate::HumanOutput for VmContract { "The VM has {} vCPUS, {}MB of memory and a disk of {} GB.", self.vcpus, self.memory_mb, self.disk_size_gb ); - println!("You have locked {} nanoLP in the contract, that get collected at a rate of {} nanoLP per minute.", + println!("You have locked {} nanocredits in the contract, that get collected at a rate of {} nanocredits per minute.", self.locked_nano, self.nano_per_minute); } } @@ -182,7 +182,7 @@ pub async fn extend_vm(uuid: String, admin_pubkey: String, locked_nano: u64) -> Ok(confirmation) => { log::debug!("VM contract extension confirmation: {confirmation:?}"); log::info!( - "VM contract got updated. It now has {} LP locked for the VM.", + "VM contract got updated. It now has {} credits locked for the VM.", locked_nano as f64 / 1_000_000_000.0 ); } diff --git a/src/snp/mod.rs b/src/snp/mod.rs index 3de89ba..60abb63 100644 --- a/src/snp/mod.rs +++ b/src/snp/mod.rs @@ -6,11 +6,10 @@ pub mod grpc; mod injector; pub mod update; -use crate::utils::block_on; -use crate::utils::shorten_string; use crate::{ config::{self, Config}, snp, + utils::{block_on, shorten_string, display_mib_or_gib}, }; use grpc::proto; use lazy_static::lazy_static; @@ -157,12 +156,12 @@ pub struct VmContract { pub uuid: String, pub hostname: String, #[tabled(rename = "Cores")] - pub vcpus: u32, - #[tabled(rename = "Mem (MB)")] - pub mem: u32, - #[tabled(rename = "Disk")] - pub disk: u32, - #[tabled(rename = "LP/h")] + pub vcpus: u64, + #[tabled(rename = "Mem", display_with = "display_mib_or_gib")] + pub mem: u64, + #[tabled(rename = "Disk", display_with = "display_mib_or_gib")] + pub disk: u64, + #[tabled(rename = "credits/h")] pub cost_h: f64, #[tabled(rename = "time left", display_with = "display_mins")] pub time_left: u64, @@ -189,9 +188,9 @@ impl From for VmContract { Self { uuid: brain_contract.uuid, hostname: brain_contract.hostname, - vcpus: brain_contract.vcpus, - mem: brain_contract.memory_mb, - disk: brain_contract.disk_size_gb, + vcpus: brain_contract.vcpus as u64, + mem: brain_contract.memory_mb as u64, + disk: brain_contract.disk_size_gb as u64, location: brain_contract.location, cost_h: (brain_contract.nano_per_minute * 60) as f64 / 1_000_000_000.0, time_left: brain_contract.locked_nano / brain_contract.nano_per_minute, @@ -201,10 +200,16 @@ impl From for VmContract { #[derive(Tabled, Debug, Serialize, Deserialize)] pub struct TabledVmNode { - #[tabled(rename = "Operator")] + #[tabled(rename = "Operator", display_with = "shorten_string")] pub operator: String, #[tabled(rename = "City, Region, Country")] pub location: String, + #[tabled(rename = "Cores")] + pub vcpus: u64, + #[tabled(rename = "Mem", display_with = "display_mib_or_gib")] + pub memory_mib: u64, + #[tabled(rename = "Disk", display_with = "display_mib_or_gib")] + pub disk_mib: u64, #[tabled(rename = "IP")] pub public_ip: String, #[tabled(rename = "Price per unit")] @@ -219,8 +224,11 @@ impl From for TabledVmNode { operator: brain_node.operator, location: brain_node.city + ", " + &brain_node.region + ", " + &brain_node.country, public_ip: brain_node.ip, - price: format!("{} nanoLP/min", brain_node.price), + price: format!("{} nano/min", brain_node.price), reports: brain_node.reports.len(), + vcpus: brain_node.vcpus, + memory_mib: brain_node.memory_mib, + disk_mib: brain_node.disk_mib, } } } @@ -340,10 +348,10 @@ pub fn inspect_node(ip: String) -> Result { Ok(block_on(grpc::get_one_node(req))?) } -pub fn calculate_nanolp( +pub fn calculate_nanocredits( vcpus: u32, memory_mb: u32, - disk_size_gb: u32, + disk_size_mib: u32, public_ipv4: bool, hours: u32, node_price: u64, @@ -351,19 +359,9 @@ pub fn calculate_nanolp( // this calculation needs to match the calculation of the network let total_units = (vcpus as u64 * 10) + ((memory_mb + 256) as u64 / 200) - + (disk_size_gb as u64 / 10) + + (disk_size_mib as u64 / 1024 / 10) + (public_ipv4 as u64 * 10); let locked_nano = hours as u64 * 60 * total_units * node_price; - eprint!( - "Node price: {}/unit/minute. Total Units for hardware requested: {}. ", - node_price as f64 / 1_000_000_000.0, - total_units, - ); - eprintln!( - "Locking {} LP (offering the VM for {} hours).", - locked_nano as f64 / 1_000_000_000.0, - hours - ); locked_nano } diff --git a/src/snp/update.rs b/src/snp/update.rs index 3a68e82..5eba59b 100644 --- a/src/snp/update.rs +++ b/src/snp/update.rs @@ -12,8 +12,8 @@ use log::{debug, info}; pub struct Request { hostname: String, vcpus: u32, - memory_mb: u32, - disk_size_gb: u32, + memory_mib: u32, + disk_size_mib: u32, dtrfs: Option, } @@ -34,7 +34,7 @@ impl Request { Some(Dtrfs::load_from_file(path)?) } }; - let req = Self { hostname, vcpus, memory_mb, disk_size_gb, dtrfs }; + let req = Self { hostname, vcpus, memory_mib: memory_mb, disk_size_mib: disk_size_gb, dtrfs }; if req == Self::default() { log::info!("Skipping hardware upgrade (no arguments specified)."); return Ok(()); @@ -90,9 +90,9 @@ impl Request { uuid: uuid.to_string(), hostname: self.hostname.clone(), admin_pubkey: Config::get_detee_wallet()?, - disk_size_gb: self.disk_size_gb, + disk_size_mib: self.disk_size_mib * 1024, vcpus: self.vcpus, - memory_mb: self.memory_mb, + memory_mib: self.memory_mib * 1024, kernel_url, kernel_sha, dtrfs_url, diff --git a/src/utils.rs b/src/utils.rs index 9b32a71..a45774c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -45,6 +45,14 @@ pub fn shorten_string(my_string: &String) -> String { } } +pub fn display_mib_or_gib(value: &u64) -> String { + if *value > 1024 { + format!("{}G", value / 1024) + } else { + format!("{}M", value) + } +} + #[macro_export] macro_rules! call_with_follow_redirect { (