use crate::config::Config; use crate::name_generator::random_app_name; use crate::sgx::grpc_brain::{get_app_node_list, new_app}; use crate::sgx::grpc_dtpm::{dtpm_client, set_config_pb, upload_files_pb}; use crate::sgx::utils::{ calculate_nanocredits_for_app, fetch_config, hratls_url_and_mr_enclave_from_app_id, }; use crate::sgx::{ append_app_id_list, package_entry_from_name, AppDeployResponse, Error, PackageElement, }; use crate::snp; use detee_shared::app_proto::{AppNodeFilters, AppNodeListResp, AppResource, NewAppReq}; use detee_shared::sgx::pb::dtpm_proto::DtpmSetConfigReq; use serde::{Deserialize, Serialize}; use serde_default_utils::*; use tokio_retry::strategy::FixedInterval; use tokio_retry::Retry; #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Reqwest { #[serde(default = "random_app_name")] pub app_name: String, pub package_name: String, pub vcpus: u32, pub memory_mib: u32, pub disk_size_mib: u32, pub port: Vec, #[serde(default = "default_u64::<1>")] pub hours: u64, #[serde(default)] pub price: u64, #[serde(default)] pub location: String, pub envs: Vec, pub args: Vec, } impl Reqwest { pub async fn deploy(self) -> Result { 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 AppResource { vcpus, memory_mib, disk_size_mib, .. } = req.resource.clone().unwrap_or_default(); 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.app_id).await?; let mr_enclave = mr_enclave.expect("App contract does not have a mr_enclave"); log::info!("hratls uri: {hratls_uri} mr_enclave: {mr_enclave:?}"); if new_app_res.error.is_empty() { let launch_config = fetch_config(&launch_config_url).await?; eprint!("Deploying..."); let dtpm_client = Retry::spawn(FixedInterval::from_millis(1000).take(30), || { log::debug!("retrying attestation and launch config update"); eprint!("."); dtpm_client(&hratls_uri, &mr_enclave) }) .await?; println!(""); upload_files_pb(launch_config.filesystems.clone(), &dtpm_client).await?; let config_data = Some(launch_config.into()); log::trace!("Decoded the configuration... {:?}", config_data); let req = DtpmSetConfigReq { config_data, ..Default::default() }; set_config_pb(req, &dtpm_client).await?; append_app_id_list(&new_app_res.app_id, &self.app_name)?; Ok((new_app_res, self.app_name).into()) } else { Err(Error::Deployment(new_app_res.error)) } } 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, memory_mib: self.memory_mib, storage_mib: self.disk_size_mib, country: location.country.clone().unwrap_or_default(), region: location.region.clone().unwrap_or_default(), city: location.city.clone().unwrap_or_default(), ip: location.node_ip.clone().unwrap_or_default(), node_pubkey: String::new(), free_ports: (self.port.len() + 1) as u32, }; 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, app_id: "".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 } }