Finding cheapest offer for app deployment

Refactors the app deployment logic to search for and select the cheapest available offer from the SGX nodes.
This commit is contained in:
Noor 2025-06-30 20:30:30 +05:30
parent eb8cac48f2
commit f170c09a02
Signed by: noormohammedb
GPG Key ID: D83EFB8B3B967146
3 changed files with 93 additions and 43 deletions

@ -3,10 +3,12 @@ use crate::{
name_generator::random_app_name, name_generator::random_app_name,
sgx::{ sgx::{
append_uuid_list, 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}, grpc_dtpm::{dtpm_client, set_config_pb, upload_files_pb},
package_entry_from_name, 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, AppDeployResponse, Error, PackageElement,
}, },
snp, snp,
@ -41,42 +43,32 @@ pub struct Reqwest {
impl Reqwest { impl Reqwest {
pub async fn deploy(self) -> Result<AppDeployResponse, Error> { pub async fn deploy(self) -> Result<AppDeployResponse, Error> {
let node = self.get_app_node().await?; let mut req = self.get_cheapest_offer().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 PackageElement { package_url, mr_enclave, launch_config_url, .. } = let PackageElement { package_url, mr_enclave, launch_config_url, .. } =
package_entry_from_name(&self.package_name).expect("Unknown package name"); package_entry_from_name(&self.package_name).expect("Unknown package name");
let resource = Some(AppResource { let AppResource { vcpus, memory_mib, disk_size_mib, .. } =
vcpus: self.vcpus, req.resource.clone().unwrap_or_default();
memory_mib: self.memory_mib,
disk_size_mib: self.disk_size_mib,
ports: self.port.clone(),
});
let req = NewAppReq { req.public_package_mr_enclave = Some(mr_enclave.to_vec());
package_url: package_url, req.admin_pubkey = Config::get_detee_wallet()?;
node_pubkey: node.node_pubkey, req.hratls_pubkey = Config::get_hratls_pubkey_hex()?;
resource, req.package_url = package_url;
uuid: "".to_string(),
admin_pubkey: Config::get_detee_wallet()?, eprintln!(
price_per_unit: self.price, "Node {} can offer the app at {} nanocredits for {} hours. Spec: {vcpus} vCPUs, {memory_mib} MiB mem, {disk_size_mib} MiB disk.",
locked_nano, &req.node_pubkey, req.locked_nano, self.hours
hratls_pubkey: Config::get_hratls_pubkey_hex()?, );
public_package_mr_enclave: Some(mr_enclave.to_vec()),
app_name: self.app_name,
};
let new_app_res = new_app(req).await?; 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) = let (hratls_uri, mr_enclave) =
hratls_url_and_mr_enclave_from_app_id(&new_app_res.uuid).await?; 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() { if new_app_res.error.is_empty() {
let launch_config = fetch_config(&launch_config_url).await?; let launch_config = fetch_config(&launch_config_url).await?;
eprint!("Deploying..."); eprint!("Deploying...");
tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await;
let dtpm_client = Retry::spawn(FixedInterval::from_millis(1000).take(30), || { let dtpm_client = Retry::spawn(FixedInterval::from_millis(1000).take(30), || {
log::debug!("retrying attestation and launch config update"); log::debug!("retrying attestation and launch config update");
eprint!("."); eprint!(".");
@ -102,14 +93,14 @@ impl Reqwest {
let req = DtpmSetConfigReq { config_data, ..Default::default() }; let req = DtpmSetConfigReq { config_data, ..Default::default() };
set_config_pb(req, &dtpm_client).await?; set_config_pb(req, &dtpm_client).await?;
append_uuid_list(&new_app_res.uuid, &app_name)?; append_uuid_list(&new_app_res.uuid, &self.app_name)?;
Ok((new_app_res, app_name).into()) Ok((new_app_res, self.app_name).into())
} else { } else {
Err(Error::Deployment(new_app_res.error)) Err(Error::Deployment(new_app_res.error))
} }
} }
pub async fn get_app_node(&self) -> Result<AppNodeListResp, Error> { pub async fn get_cheapest_offer(&self) -> Result<NewAppReq, Error> {
let location = snp::Location::from(self.location.as_str()); let location = snp::Location::from(self.location.as_str());
let app_node_filter = AppNodeFilters { let app_node_filter = AppNodeFilters {
vcpus: self.vcpus, vcpus: self.vcpus,
@ -122,6 +113,70 @@ impl Reqwest {
node_pubkey: String::new(), node_pubkey: String::new(),
free_ports: (self.port.len() + 1) as u32, 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
} }
} }

@ -28,6 +28,8 @@ pub enum Error {
AppContractNotFound(String), AppContractNotFound(String),
#[error("Brain returned the following error: {0}")] #[error("Brain returned the following error: {0}")]
Brain(#[from] grpc_brain::Error), Brain(#[from] grpc_brain::Error),
#[error("Did not find a SGX node that matches your criteria")]
NoValidNodeFound,
#[error("{0}")] #[error("{0}")]
Dtpm(#[from] crate::sgx::grpc_dtpm::Error), Dtpm(#[from] crate::sgx::grpc_dtpm::Error),
#[error("Could not read file from disk: {0}")] #[error("Could not read file from disk: {0}")]

@ -37,7 +37,7 @@ pub async fn fetch_config(url: &str) -> Result<DtpmConfig, Error> {
Ok(launch_config) Ok(launch_config)
} }
pub fn calculate_nanolp_for_app( pub fn calculate_nanocredits_for_app(
vcpus: u32, vcpus: u32,
memory_mib: u32, memory_mib: u32,
disk_size_mib: u32, disk_size_mib: u32,
@ -49,13 +49,6 @@ pub fn calculate_nanolp_for_app(
+ (memory_mib as f64 / 200f64) + (memory_mib as f64 / 200f64)
+ (disk_size_mib as f64 / 1024f64 / 10f64); + (disk_size_mib as f64 / 1024f64 / 10f64);
let locked_nano = (hours as f64 * 60f64 * total_units * node_price as f64) as u64; 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 locked_nano
} }