diff --git a/src/sgx/deploy.rs b/src/sgx/deploy.rs index 670ed9a..27175c4 100644 --- a/src/sgx/deploy.rs +++ b/src/sgx/deploy.rs @@ -3,10 +3,12 @@ use crate::{ name_generator::random_app_name, sgx::{ append_uuid_list, - grpc_brain::{get_one_app_node, new_app}, + grpc_brain::{get_app_node_list, new_app}, grpc_dtpm::{dtpm_client, set_config_pb, upload_files_pb}, package_entry_from_name, - utils::{calculate_nanolp_for_app, fetch_config, hratls_url_and_mr_enclave_from_app_id}, + utils::{ + calculate_nanocredits_for_app, fetch_config, hratls_url_and_mr_enclave_from_app_id, + }, AppDeployResponse, Error, PackageElement, }, snp, @@ -41,42 +43,32 @@ pub struct Reqwest { impl Reqwest { pub async fn deploy(self) -> Result { - let node = self.get_app_node().await?; - let app_name = self.app_name.clone(); - - let locked_nano = calculate_nanolp_for_app( - self.vcpus, - self.memory_mib, - self.disk_size_mib, - self.hours, - self.price, - ); + let mut req = self.get_cheapest_offer().await?; let PackageElement { package_url, mr_enclave, launch_config_url, .. } = package_entry_from_name(&self.package_name).expect("Unknown package name"); - let resource = Some(AppResource { - vcpus: self.vcpus, - memory_mib: self.memory_mib, - disk_size_mib: self.disk_size_mib, - ports: self.port.clone(), - }); + let AppResource { vcpus, memory_mib, disk_size_mib, .. } = + req.resource.clone().unwrap_or_default(); - let req = NewAppReq { - package_url: package_url, - node_pubkey: node.node_pubkey, - resource, - uuid: "".to_string(), - admin_pubkey: Config::get_detee_wallet()?, - price_per_unit: self.price, - locked_nano, - hratls_pubkey: Config::get_hratls_pubkey_hex()?, - public_package_mr_enclave: Some(mr_enclave.to_vec()), - app_name: self.app_name, - }; + req.public_package_mr_enclave = Some(mr_enclave.to_vec()); + req.admin_pubkey = Config::get_detee_wallet()?; + req.hratls_pubkey = Config::get_hratls_pubkey_hex()?; + req.package_url = package_url; + + eprintln!( + "Node {} can offer the app at {} nanocredits for {} hours. Spec: {vcpus} vCPUs, {memory_mib} MiB mem, {disk_size_mib} MiB disk.", + &req.node_pubkey, req.locked_nano, self.hours + ); let new_app_res = new_app(req).await?; + if !new_app_res.error.is_empty() { + return Err(Error::Deployment(new_app_res.error)); + } + + tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await; + let (hratls_uri, mr_enclave) = hratls_url_and_mr_enclave_from_app_id(&new_app_res.uuid).await?; @@ -87,7 +79,6 @@ impl Reqwest { if new_app_res.error.is_empty() { let launch_config = fetch_config(&launch_config_url).await?; eprint!("Deploying..."); - tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await; let dtpm_client = Retry::spawn(FixedInterval::from_millis(1000).take(30), || { log::debug!("retrying attestation and launch config update"); eprint!("."); @@ -102,14 +93,14 @@ impl Reqwest { let req = DtpmSetConfigReq { config_data, ..Default::default() }; set_config_pb(req, &dtpm_client).await?; - append_uuid_list(&new_app_res.uuid, &app_name)?; - Ok((new_app_res, app_name).into()) + append_uuid_list(&new_app_res.uuid, &self.app_name)?; + Ok((new_app_res, self.app_name).into()) } else { Err(Error::Deployment(new_app_res.error)) } } - pub async fn get_app_node(&self) -> Result { + pub async fn get_cheapest_offer(&self) -> Result { let location = snp::Location::from(self.location.as_str()); let app_node_filter = AppNodeFilters { vcpus: self.vcpus, @@ -122,6 +113,70 @@ impl Reqwest { node_pubkey: String::new(), free_ports: (self.port.len() + 1) as u32, }; - Ok(get_one_app_node(app_node_filter).await?) + let node_list = get_app_node_list(app_node_filter).await?; + let mut node_iter = node_list.iter(); + + let mut final_req = + self.calculate_app_request(node_iter.next().ok_or(Error::NoValidNodeFound)?); + + while let Some(node) = node_iter.next() { + let new_app_req = self.calculate_app_request(node); + if new_app_req.locked_nano < final_req.locked_nano { + final_req = new_app_req; + } + } + + Ok(final_req) + } + + fn calculate_app_request(&self, node: &AppNodeListResp) -> NewAppReq { + let node_mem_per_cpu = node.memory_mib / node.vcpus; + let node_disk_per_cpu = node.disk_mib / node.vcpus; + + let mut req_vcpus = self.vcpus; + + if req_vcpus < self.memory_mib.div_ceil(node_mem_per_cpu as u32) { + req_vcpus = self.memory_mib.div_ceil(node_mem_per_cpu as u32); + } + + if req_vcpus < self.disk_size_mib.div_ceil(node_disk_per_cpu as u32) { + req_vcpus = self.disk_size_mib.div_ceil(node_disk_per_cpu as u32); + } + + let req_mem_mib = req_vcpus * node_mem_per_cpu as u32; + let req_disk_mib = req_vcpus * node_disk_per_cpu as u32; + + let nano_credits = calculate_nanocredits_for_app( + req_vcpus, + req_mem_mib, + req_disk_mib, + self.hours, + node.price, + ); + + let resource = Some(AppResource { + vcpus: req_vcpus, + memory_mib: req_mem_mib, + disk_size_mib: req_disk_mib, + ports: self.port.clone(), + }); + + let new_app_req = NewAppReq { + node_pubkey: node.node_pubkey.clone(), + resource, + uuid: "".to_string(), + price_per_unit: node.price, + locked_nano: nano_credits, + app_name: self.app_name.clone(), + + ..Default::default() + }; + + log::debug!( + "Node {} can offer the app at {} nanocredits for {} hours. Spec: {} vCPUs, {} MiB mem, {} MiB disk.", + node.ip, nano_credits, self.hours, req_vcpus, req_mem_mib, req_disk_mib + ); + + new_app_req } } diff --git a/src/sgx/mod.rs b/src/sgx/mod.rs index 3ffa689..b72bda8 100644 --- a/src/sgx/mod.rs +++ b/src/sgx/mod.rs @@ -28,6 +28,8 @@ pub enum Error { AppContractNotFound(String), #[error("Brain returned the following error: {0}")] Brain(#[from] grpc_brain::Error), + #[error("Did not find a SGX node that matches your criteria")] + NoValidNodeFound, #[error("{0}")] Dtpm(#[from] crate::sgx::grpc_dtpm::Error), #[error("Could not read file from disk: {0}")] diff --git a/src/sgx/utils.rs b/src/sgx/utils.rs index e2e6266..93e00db 100644 --- a/src/sgx/utils.rs +++ b/src/sgx/utils.rs @@ -37,7 +37,7 @@ pub async fn fetch_config(url: &str) -> Result { Ok(launch_config) } -pub fn calculate_nanolp_for_app( +pub fn calculate_nanocredits_for_app( vcpus: u32, memory_mib: u32, disk_size_mib: u32, @@ -49,13 +49,6 @@ pub fn calculate_nanolp_for_app( + (memory_mib as f64 / 200f64) + (disk_size_mib as f64 / 1024f64 / 10f64); let locked_nano = (hours as f64 * 60f64 * total_units * node_price as f64) as u64; - eprintln!( - "Node price: {}/unit/minute. Total Units for hardware requested: {:.4}. Locking {} LP (offering the App for {} hours).", - node_price as f64 / 1_000_000_000.0, - total_units, - locked_nano as f64 / 1_000_000_000.0, - hours - ); locked_nano }