switch from LP to credits and allow slots

This commit is contained in:
ghe0 2025-06-23 04:22:59 +03:00
parent 13a00e2318
commit d25cc1a699
Signed by: ghe0
GPG Key ID: 451028EE56A0FBB4
8 changed files with 120 additions and 72 deletions

5
Cargo.lock generated

@ -1,7 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
# SPDX-License-Identifier: Apache-2.0
version = 4 version = 4
[[package]] [[package]]
@ -1184,7 +1183,7 @@ dependencies = [
[[package]] [[package]]
name = "detee-shared" name = "detee-shared"
version = "0.1.0" 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 = [ dependencies = [
"bincode", "bincode",
"prost", "prost",

@ -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"] } 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"] } 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" } # detee-shared = { path = "../detee-shared" }
[build-dependencies] [build-dependencies]

@ -347,16 +347,16 @@ fn clap_cmd() -> Command {
.arg( .arg(
Arg::new("memory") Arg::new("memory")
.long("memory") .long("memory")
.default_value("1000") .default_value("1")
.value_parser(clap::value_parser!(u32).range(800..123000)) .value_parser(clap::value_parser!(u32).range(1..500))
.help("memory in MB") .help("memory in GiB")
) )
.arg( .arg(
Arg::new("disk") Arg::new("disk")
.long("disk") .long("disk")
.default_value("10") .default_value("10")
.value_parser(clap::value_parser!(u32).range(5..500)) .value_parser(clap::value_parser!(u32).range(5..500))
.help("disk size in GB") .help("disk size in GiB")
) )
.arg( .arg(
Arg::new("distribution") Arg::new("distribution")
@ -375,8 +375,8 @@ fn clap_cmd() -> Command {
.arg( .arg(
Arg::new("price") Arg::new("price")
.long("price") .long("price")
.help("price per unit per minute; check docs") .help("maxium accepted price per unit per minute")
.default_value("20000") .default_value("4000")
.value_parser(clap::value_parser!(u64).range(1..50000000)) .value_parser(clap::value_parser!(u64).range(1..50000000))
) )
.arg( .arg(

@ -96,7 +96,7 @@ pub async fn kick_contract(contract_uuid: String, reason: String) -> Result<u64,
})?) })?)
.await? .await?
.into_inner() .into_inner()
.nano_lp) .nano_credits)
} }
pub async fn ban_user(user_wallet: String) -> Result<(), Error> { pub async fn ban_user(user_wallet: String) -> Result<(), Error> {

@ -69,8 +69,8 @@ fn handle_vm_deploy(matches: &ArgMatches) -> Result<snp::VmSshArgs, Box<dyn Erro
ipv4, ipv4,
public_ipv6: false, public_ipv6: false,
vcpus: *matches.get_one::<u32>("vcpus").unwrap(), vcpus: *matches.get_one::<u32>("vcpus").unwrap(),
memory_mb: *matches.get_one::<u32>("memory").unwrap(), memory_gib: *matches.get_one::<u32>("memory").unwrap(),
disk_size_gb: *matches.get_one::<u32>("disk").unwrap(), disk_size_gib: *matches.get_one::<u32>("disk").unwrap(),
dtrfs: None, dtrfs: None,
hours: *matches.get_one::<u32>("hours").unwrap(), hours: *matches.get_one::<u32>("hours").unwrap(),
price: *matches.get_one::<u64>("price").unwrap(), price: *matches.get_one::<u64>("price").unwrap(),

@ -4,8 +4,7 @@ use super::{
grpc::{self, proto}, grpc::{self, proto},
injector, Distro, Dtrfs, Error, VmSshArgs, DEFAULT_ARCHLINUX, DEFAULT_DTRFS, injector, Distro, Dtrfs, Error, VmSshArgs, DEFAULT_ARCHLINUX, DEFAULT_DTRFS,
}; };
use crate::config::Config; use crate::{config::Config, utils::block_on};
use crate::utils::block_on;
use log::{debug, info}; use log::{debug, info};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -51,8 +50,8 @@ pub struct Request {
pub ipv4: IPv4Config, pub ipv4: IPv4Config,
pub public_ipv6: bool, pub public_ipv6: bool,
pub vcpus: u32, pub vcpus: u32,
pub memory_mb: u32, pub memory_gib: u32,
pub disk_size_gb: u32, pub disk_size_gib: u32,
pub dtrfs: Option<Dtrfs>, pub dtrfs: Option<Dtrfs>,
pub distro: Option<Distro>, pub distro: Option<Distro>,
} }
@ -70,8 +69,8 @@ impl Request {
} }
pub fn deploy(&self) -> Result<VmSshArgs, Error> { pub fn deploy(&self) -> Result<VmSshArgs, Error> {
let (node_ip, new_vm_resp) = self.send_vm_request()?; let new_vm_resp = self.calculate_and_send_request()?;
info!("Got confirmation from the node {node_ip} that VM started."); info!("Got confirmation from the node that the VM started.");
debug!("IPs and ports assigned by node are: {new_vm_resp:#?}"); debug!("IPs and ports assigned by node are: {new_vm_resp:#?}");
if !new_vm_resp.error.is_empty() { if !new_vm_resp.error.is_empty() {
return Err(Error::Node(new_vm_resp.error)); return Err(Error::Node(new_vm_resp.error));
@ -108,9 +107,96 @@ impl Request {
} }
// returns node IP and data regarding the new VM // returns node IP and data regarding the new VM
fn send_vm_request(&self) -> Result<(String, proto::NewVmResp), Error> { fn calculate_and_send_request(&self) -> Result<proto::NewVmResp, Error> {
let admin_pubkey = Config::get_detee_wallet()?; let new_vm_req = self.get_cheapest_offer()?;
let node = self.get_node()?;
info!(
"The best offer (from node {}) is for {} nanocredits (covering all {} hours),
and includes the following HW spec: {} vCPUs, {} MiB Mem, {} MiB Disk",
new_vm_req.node_pubkey,
self.hours,
new_vm_req.locked_nano,
new_vm_req.vcpus,
new_vm_req.memory_mib,
new_vm_req.disk_size_mib
);
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(new_vm_resp)
}
fn get_cheapest_offer(&self) -> Result<proto::NewVmReq, Error> {
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 offer_map = std::collections::BTreeMap::new();
for node in node_list.iter() {
let (price, new_vm_req) = self.calculate_vm_request(Config::get_detee_wallet()?, node);
debug!(
"Node {} can offer the VM at {} nanocredits for {} hours. Spec: {} vCPUs, {} MiB mem, {} MiB disk.",
node.ip, price, self.hours, new_vm_req.vcpus, new_vm_req.memory_mib, new_vm_req.disk_size_mib
);
offer_map.insert(price, new_vm_req);
}
Ok(offer_map.pop_first().unwrap().1.clone())
}
fn calculate_vm_request(
&self,
admin_pubkey: String,
node: &proto::VmNodeListResp,
) -> (u64, 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_mib = vcpus * disk_per_cpu as u32;
let nanocredits = super::calculate_nanocredits(
vcpus,
memory_mib,
disk_mib,
match self.ipv4 {
IPv4Config::PublishPorts(_) => false,
IPv4Config::PublicIPv4 => true,
},
self.hours,
node.price,
);
let (extra_ports, public_ipv4): (Vec<u32>, bool) = match &self.ipv4 { let (extra_ports, public_ipv4): (Vec<u32>, bool) = match &self.ipv4 {
IPv4Config::PublishPorts(vec) => (vec.to_vec(), false), IPv4Config::PublishPorts(vec) => (vec.to_vec(), false),
IPv4Config::PublicIPv4 => (Vec::new(), true), IPv4Config::PublicIPv4 => (Vec::new(), true),
@ -124,63 +210,26 @@ impl Request {
DEFAULT_DTRFS.dtrfs_sha.clone(), 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 { let brain_req = proto::NewVmReq {
uuid: String::new(), uuid: String::new(),
hostname: self.hostname.clone(), hostname: self.hostname.clone(),
admin_pubkey, admin_pubkey,
node_pubkey: node.node_pubkey, node_pubkey: node.node_pubkey.clone(),
extra_ports, extra_ports,
public_ipv4, public_ipv4,
public_ipv6: self.public_ipv6, public_ipv6: self.public_ipv6,
disk_size_gb: self.disk_size_gb, disk_size_mib: disk_mib * 1024,
vcpus: self.vcpus, vcpus,
memory_mb: self.memory_mb, memory_mib: memory_mib * 1024,
kernel_url, kernel_url,
kernel_sha, kernel_sha,
dtrfs_url, dtrfs_url,
dtrfs_sha, dtrfs_sha,
price_per_unit: self.price, price_per_unit: self.price,
locked_nano, 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<proto::VmNodeListResp, Error> { (nanocredits, brain_req)
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)
}
}
} }
} }

@ -340,7 +340,7 @@ pub fn inspect_node(ip: String) -> Result<proto::VmNodeListResp, Error> {
Ok(block_on(grpc::get_one_node(req))?) Ok(block_on(grpc::get_one_node(req))?)
} }
pub fn calculate_nanolp( pub fn calculate_nanocredits(
vcpus: u32, vcpus: u32,
memory_mb: u32, memory_mb: u32,
disk_size_gb: u32, disk_size_gb: u32,

@ -12,8 +12,8 @@ use log::{debug, info};
pub struct Request { pub struct Request {
hostname: String, hostname: String,
vcpus: u32, vcpus: u32,
memory_mb: u32, memory_mib: u32,
disk_size_gb: u32, disk_size_mib: u32,
dtrfs: Option<Dtrfs>, dtrfs: Option<Dtrfs>,
} }
@ -34,7 +34,7 @@ impl Request {
Some(Dtrfs::load_from_file(path)?) 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() { if req == Self::default() {
log::info!("Skipping hardware upgrade (no arguments specified)."); log::info!("Skipping hardware upgrade (no arguments specified).");
return Ok(()); return Ok(());
@ -90,9 +90,9 @@ impl Request {
uuid: uuid.to_string(), uuid: uuid.to_string(),
hostname: self.hostname.clone(), hostname: self.hostname.clone(),
admin_pubkey: Config::get_detee_wallet()?, admin_pubkey: Config::get_detee_wallet()?,
disk_size_gb: self.disk_size_gb, disk_size_mib: self.disk_size_mib * 1024,
vcpus: self.vcpus, vcpus: self.vcpus,
memory_mb: self.memory_mb, memory_mib: self.memory_mib * 1024,
kernel_url, kernel_url,
kernel_sha, kernel_sha,
dtrfs_url, dtrfs_url,