Compare commits

..

1 Commits

Author SHA1 Message Date
6ca02e91b9
added app code from noor 2025-03-14 16:32:54 +02:00
6 changed files with 949 additions and 251 deletions

6
Cargo.lock generated

@ -246,7 +246,7 @@ dependencies = [
"prost-types",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
"tokio",
"tokio-stream",
@ -307,6 +307,7 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets",
]
@ -403,6 +404,7 @@ dependencies = [
"lock_api",
"once_cell",
"parking_lot_core",
"serde",
]
[[package]]
@ -418,7 +420,7 @@ dependencies = [
[[package]]
name = "detee-shared"
version = "0.1.0"
source = "git+ssh://git@gitea.detee.cloud/noormohammedb/detee-shared?branch=stable_01#b8f37dec1845d29ea0b69035712e6ebb214376f4"
source = "git+ssh://git@gitea.detee.cloud/noormohammedb/detee-shared?branch=stable_01#099f0a0488bce8e59c9c9e9a5e9b1f24998f1633"
dependencies = [
"base64",
"prost",

@ -5,16 +5,16 @@ edition = "2021"
[dependencies]
bs58 = "0.5.1"
chrono = "0.4.39"
dashmap = "6.1.0"
chrono = { version = "0.4.39", features = ["serde"] }
dashmap = { version = "6.1.0", features = ["serde"] }
ed25519-dalek = "2.1.1"
env_logger = "0.11.6"
log = "0.4.22"
prost = "0.13.4"
prost-types = "0.13.4"
reqwest = "0.12.10"
serde = { version = "1.0.216", features = ["derive"] }
serde_json = "1.0.134"
serde = { version = "1.0.217", features = ["derive"] }
serde_yaml = "0.9.34"
thiserror = "2.0.11"
tokio = { version = "1.42.0", features = ["macros", "rt-multi-thread"] }
tokio-stream = "0.1.17"

File diff suppressed because it is too large Load Diff

@ -1,5 +1,3 @@
#![allow(dead_code)]
pub mod snp_proto {
tonic::include_proto!("vm_proto");
}
@ -19,12 +17,13 @@ use tonic::{Request, Response, Status, Streaming};
use detee_shared::sgx::pb::brain::brain_app_cli_server::BrainAppCli;
use detee_shared::sgx::pb::brain::brain_app_daemon_server::BrainAppDaemon;
use detee_shared::sgx::pb::brain::{
AppContract, BrainMessageApp, DaemonMessageApp, DelAppReq, ListAppContractsReq, NewAppReq,
NewAppRes, RegisterAppNodeReq,
AppContract, AppNodeFilters, AppNodeListResp, BrainMessageApp, DaemonMessageApp, DelAppReq,
ListAppContractsReq, NewAppReq, NewAppRes, RegisterAppNodeReq,
};
const ADMIN_ACCOUNTS: &[&str] = &[
"x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK",
"FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL",
"H21Shi4iE7vgfjWEQNvzmpmBMJSaiZ17PYUcdNoAoKNc",
];
pub struct BrainDaemonMock {
@ -78,7 +77,7 @@ impl BrainVmDaemon for BrainDaemonMock {
info!("Starting registration process for {:?}", req);
let node = crate::data::VmNode {
public_key: req.node_pubkey.clone(),
owner_key: req.owner_pubkey,
operator_wallet: req.operator_wallet,
country: req.country,
region: req.region,
city: req.city,
@ -86,10 +85,10 @@ impl BrainVmDaemon for BrainDaemonMock {
price: req.price,
..Default::default()
};
self.data.insert_node(node);
self.data.register_node(node);
info!("Sending existing contracts to {}", req.node_pubkey);
let contracts = self.data.find_contracts_by_node_pubkey(&req.node_pubkey);
let contracts = self.data.find_vm_contracts_by_node(&req.node_pubkey);
let (tx, rx) = mpsc::channel(6);
tokio::spawn(async move {
for contract in contracts {
@ -187,6 +186,14 @@ impl BrainCli for BrainCliMock {
async fn new_vm(&self, req: Request<NewVmReq>) -> Result<Response<NewVmResp>, Status> {
let req = check_sig_from_req(req)?;
info!("New VM requested via CLI: {req:?}");
if self
.data
.is_user_banned_by_node(&req.admin_pubkey, &req.node_pubkey)
{
return Err(Status::permission_denied(
"This operator banned you. What did you do?",
));
}
let admin_pubkey = req.admin_pubkey.clone();
let (oneshot_tx, oneshot_rx) = tokio::sync::oneshot::channel();
self.data.submit_newvm_req(req, oneshot_tx).await;
@ -214,9 +221,9 @@ impl BrainCli for BrainCliMock {
info!("Sending UpdateVMResp: {response:?}");
Ok(Response::new(response))
}
Err(_) => Err(Status::unknown(
"Update VM request failed due to error: {e}",
)),
Err(e) => Err(Status::unknown(format!(
"Update VM request failed due to error: {e}"
))),
}
}
@ -231,20 +238,55 @@ impl BrainCli for BrainCliMock {
}
}
async fn delete_vm(&self, req: Request<DeleteVmReq>) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?;
match self.data.delete_vm(req).await {
Ok(()) => Ok(Response::new(Empty {})),
Err(e) => Err(Status::not_found(e.to_string())),
}
}
async fn report_node(&self, req: Request<ReportNodeReq>) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?;
match self.data.find_contract_by_uuid(&req.contract) {
Ok(contract)
if contract.admin_pubkey == req.admin_pubkey
&& contract.node_pubkey == req.node_pubkey =>
{
()
}
_ => return Err(Status::unauthenticated("No contract found by this ID.")),
};
self.data
.report_node(req.admin_pubkey, &req.node_pubkey, req.reason);
Ok(Response::new(Empty {}))
}
type ListVmContractsStream = Pin<Box<dyn Stream<Item = Result<VmContract, Status>> + Send>>;
async fn list_vm_contracts(
&self,
req: Request<ListVmContractsReq>,
) -> Result<Response<Self::ListVmContractsStream>, Status> {
let req = check_sig_from_req(req)?;
info!("CLI {} requested ListVMVmContractsStream", req.admin_pubkey);
let contracts = match req.uuid.is_empty() {
false => match self.data.find_contract_by_uuid(&req.uuid) {
Some(contract) => vec![contract],
None => Vec::new(),
},
true => self.data.find_contracts_by_admin_pubkey(&req.admin_pubkey),
};
info!(
"CLI {} requested ListVMVmContractsStream. As operator: {}",
req.wallet, req.as_operator
);
let mut contracts = Vec::new();
if !req.uuid.is_empty() {
if let Ok(specific_contract) = self.data.find_contract_by_uuid(&req.uuid) {
if specific_contract.admin_pubkey == req.wallet {
contracts.push(specific_contract);
}
// TODO: allow operator to inspect contracts
}
} else {
if req.as_operator {
contracts.append(&mut self.data.find_vm_contracts_by_operator(&req.wallet));
} else {
contracts.append(&mut self.data.find_vm_contracts_by_admin(&req.wallet));
}
}
let (tx, rx) = mpsc::channel(6);
tokio::spawn(async move {
for contract in contracts {
@ -264,7 +306,7 @@ impl BrainCli for BrainCliMock {
) -> Result<Response<Self::ListVmNodesStream>, tonic::Status> {
let req = check_sig_from_req(req)?;
info!("CLI requested ListVmNodesStream: {req:?}");
let nodes = self.data.find_nodes_by_filters(&req);
let nodes = self.data.find_vm_nodes_by_filters(&req);
let (tx, rx) = mpsc::channel(6);
tokio::spawn(async move {
for node in nodes {
@ -291,12 +333,65 @@ impl BrainCli for BrainCliMock {
}
}
async fn delete_vm(&self, req: Request<DeleteVmReq>) -> Result<Response<Empty>, Status> {
async fn register_operator(
&self,
req: Request<RegOperatorReq>,
) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?;
info!("Unknown CLI requested to delete vm {}", req.uuid);
match self.data.delete_vm(req).await {
info!("Regitering new operator: {req:?}");
match self.data.register_operator(req) {
Ok(()) => Ok(Response::new(Empty {})),
Err(e) => Err(Status::not_found(e.to_string())),
Err(e) => Err(Status::failed_precondition(e.to_string())),
}
}
async fn kick_contract(&self, req: Request<KickReq>) -> Result<Response<KickResp>, Status> {
let req = check_sig_from_req(req)?;
match self
.data
.kick_contract(&req.operator_wallet, &req.contract_uuid, &req.reason)
.await
{
Ok(nano_lp) => Ok(Response::new(KickResp { nano_lp })),
Err(e) => Err(Status::permission_denied(e.to_string())),
}
}
async fn ban_user(&self, req: Request<BanUserReq>) -> Result<Response<Empty>, Status> {
let req = check_sig_from_req(req)?;
self.data.ban_user(&req.operator_wallet, &req.user_wallet);
Ok(Response::new(Empty {}))
}
type ListOperatorsStream =
Pin<Box<dyn Stream<Item = Result<ListOperatorsResp, Status>> + Send>>;
async fn list_operators(
&self,
req: Request<Empty>,
) -> Result<Response<Self::ListOperatorsStream>, Status> {
let _ = check_sig_from_req(req)?;
let operators = self.data.list_operators();
let (tx, rx) = mpsc::channel(6);
tokio::spawn(async move {
for op in operators {
let _ = tx.send(Ok(op.into())).await;
}
});
let output_stream = ReceiverStream::new(rx);
Ok(Response::new(
Box::pin(output_stream) as Self::ListOperatorsStream
))
}
async fn inspect_operator(
&self,
req: Request<Pubkey>,
) -> Result<Response<InspectOperatorResp>, Status> {
match self.data.inspect_operator(&req.into_inner().pubkey) {
Some(op) => Ok(Response::new(op.into())),
None => Err(Status::not_found(
"The wallet you specified is not an operator",
)),
}
}
@ -307,6 +402,13 @@ impl BrainCli for BrainCliMock {
Ok(Response::new(Empty {}))
}
async fn slash(&self, req: Request<SlashReq>) -> Result<Response<Empty>, Status> {
check_admin_key(&req)?;
let req = check_sig_from_req(req)?;
self.data.slash_account(&req.pubkey, req.tokens);
Ok(Response::new(Empty {}))
}
type ListAllVmContractsStream = Pin<Box<dyn Stream<Item = Result<VmContract, Status>> + Send>>;
async fn list_all_vm_contracts(
&self,
@ -352,17 +454,14 @@ trait PubkeyGetter {
fn get_pubkey(&self) -> Option<String>;
}
impl PubkeyGetter for Pubkey {
fn get_pubkey(&self) -> Option<String> {
Some(self.pubkey.clone())
}
}
#[tonic::async_trait]
impl BrainAppCli for BrainAppCliMock {
type ListAppContractsStream = Pin<Box<dyn Stream<Item = Result<AppContract, Status>> + Send>>;
type ListAppNodesStream = Pin<Box<dyn Stream<Item = Result<AppNodeListResp, Status>> + Send>>;
type ListAllAppContractsStream =
Pin<Box<dyn Stream<Item = Result<AppContract, Status>> + Send>>;
async fn create_app(
async fn deploy_app(
&self,
req: tonic::Request<NewAppReq>,
) -> Result<tonic::Response<NewAppRes>, Status> {
@ -420,6 +519,54 @@ impl BrainAppCli for BrainAppCliMock {
Box::pin(output_stream) as Self::ListAppContractsStream
))
}
async fn list_app_nodes(
&self,
req: tonic::Request<AppNodeFilters>,
) -> Result<tonic::Response<Self::ListAppNodesStream>, Status> {
let req = check_sig_from_req(req)?;
info!("CLI requested ListAppNodes: {req:?}");
let nodes = self.data.find_app_nodes_by_filters(&req);
let (tx, rx) = mpsc::channel(6);
tokio::spawn(async move {
for node in nodes {
let _ = tx.send(Ok(node.into())).await;
}
});
let output_stream = ReceiverStream::new(rx);
Ok(Response::new(Box::pin(output_stream)))
}
async fn get_one_app_node(
&self,
req: tonic::Request<AppNodeFilters>,
) -> Result<tonic::Response<AppNodeListResp>, Status> {
let req = check_sig_from_req(req)?;
info!("CLI requested GetOneAppNode: {req:?}");
match self.data.get_one_app_node_by_filters(&req) {
Some(node) => Ok(Response::new(node.into())),
None => Err(Status::not_found(
"Could not find any node based on your search criteria",
)),
}
}
async fn list_all_app_contracts(
&self,
req: tonic::Request<detee_shared::sgx::pb::brain::Empty>,
) -> Result<tonic::Response<Self::ListAllAppContractsStream>, Status> {
check_admin_key(&req)?;
let _ = check_sig_from_req(req)?;
let contracts = self.data.list_all_app_contracts();
let (tx, rx) = mpsc::channel(6);
tokio::spawn(async move {
for contract in contracts {
let _ = tx.send(Ok(contract.into())).await;
}
});
let output_stream = ReceiverStream::new(rx);
Ok(Response::new(Box::pin(output_stream)))
}
}
#[tonic::async_trait]
@ -435,26 +582,22 @@ impl BrainAppDaemon for BrainAppDaemonMock {
log::info!(
"registering app node_key : {}, operator_key: {}",
&req_data.node_pubkey,
&req_data.operator_pubkey
&req_data.operator_wallet
);
let app_node = crate::data::AppNode {
node_pubkey: req_data.node_pubkey.clone(),
operator_pubkey: req_data.operator_pubkey,
operator_wallet: req_data.operator_wallet,
ip: req_data.main_ip,
city: req_data.city,
region: req_data.region,
country: req_data.country,
..Default::default()
};
self.data.register_app_node(app_node);
self.data.insert_app_node(app_node);
log::info!("Sending existing contracts to {}", &req_data.node_pubkey);
let app_contracts = self
.data
.find_app_contracts_by_node_pubkey(&req_data.node_pubkey);
let app_contracts = self.data.find_app_contracts_by_node(&req_data.node_pubkey);
let (tx, rx) = mpsc::channel(6);
tokio::spawn(async move {
for contract in app_contracts {
@ -549,83 +692,48 @@ impl BrainAppDaemon for BrainAppDaemonMock {
Ok(Response::new(detee_shared::sgx::pb::brain::Empty {}))
}
}
impl PubkeyGetter for NewVmReq {
fn get_pubkey(&self) -> Option<String> {
Some(self.admin_pubkey.clone())
}
macro_rules! impl_pubkey_getter {
($t:ty, $field:ident) => {
impl PubkeyGetter for $t {
fn get_pubkey(&self) -> Option<String> {
Some(self.$field.clone())
}
}
};
($t:ty) => {
impl PubkeyGetter for $t {
fn get_pubkey(&self) -> Option<String> {
None
}
}
};
}
impl PubkeyGetter for DeleteVmReq {
fn get_pubkey(&self) -> Option<String> {
Some(self.admin_pubkey.clone())
}
}
impl_pubkey_getter!(Pubkey, pubkey);
impl_pubkey_getter!(NewVmReq, admin_pubkey);
impl_pubkey_getter!(DeleteVmReq, admin_pubkey);
impl_pubkey_getter!(UpdateVmReq, admin_pubkey);
impl_pubkey_getter!(ExtendVmReq, admin_pubkey);
impl_pubkey_getter!(ReportNodeReq, admin_pubkey);
impl_pubkey_getter!(ListVmContractsReq, wallet);
impl_pubkey_getter!(RegisterVmNodeReq, node_pubkey);
impl_pubkey_getter!(RegOperatorReq, pubkey);
impl_pubkey_getter!(KickReq, operator_wallet);
impl_pubkey_getter!(BanUserReq, operator_wallet);
impl PubkeyGetter for UpdateVmReq {
fn get_pubkey(&self) -> Option<String> {
Some(self.admin_pubkey.clone())
}
}
impl_pubkey_getter!(VmNodeFilters);
impl_pubkey_getter!(Empty);
impl_pubkey_getter!(AirdropReq);
impl_pubkey_getter!(SlashReq);
impl PubkeyGetter for ExtendVmReq {
fn get_pubkey(&self) -> Option<String> {
Some(self.admin_pubkey.clone())
}
}
impl_pubkey_getter!(NewAppReq, admin_pubkey);
impl_pubkey_getter!(DelAppReq, admin_pubkey);
impl_pubkey_getter!(ListAppContractsReq, admin_pubkey);
impl PubkeyGetter for ListVmContractsReq {
fn get_pubkey(&self) -> Option<String> {
Some(self.admin_pubkey.clone())
}
}
impl PubkeyGetter for VmNodeFilters {
fn get_pubkey(&self) -> Option<String> {
None
}
}
impl PubkeyGetter for RegisterVmNodeReq {
fn get_pubkey(&self) -> Option<String> {
Some(self.node_pubkey.clone())
}
}
impl PubkeyGetter for Empty {
fn get_pubkey(&self) -> Option<String> {
None
}
}
impl PubkeyGetter for AirdropReq {
fn get_pubkey(&self) -> Option<String> {
None
}
}
impl PubkeyGetter for RegisterAppNodeReq {
fn get_pubkey(&self) -> Option<String> {
None
}
}
impl PubkeyGetter for NewAppReq {
fn get_pubkey(&self) -> Option<String> {
Some(self.admin_pubkey.clone())
}
}
impl PubkeyGetter for DelAppReq {
fn get_pubkey(&self) -> Option<String> {
Some(self.admin_pubkey.clone())
}
}
impl PubkeyGetter for ListAppContractsReq {
fn get_pubkey(&self) -> Option<String> {
Some(self.admin_pubkey.clone())
}
}
impl_pubkey_getter!(RegisterAppNodeReq);
impl_pubkey_getter!(detee_shared::sgx::pb::brain::Empty);
impl_pubkey_getter!(AppNodeFilters);
fn check_sig_from_req<T: std::fmt::Debug + PubkeyGetter>(req: Request<T>) -> Result<T, Status> {
let time = match req.metadata().get("timestamp") {
@ -640,9 +748,9 @@ fn check_sig_from_req<T: std::fmt::Debug + PubkeyGetter>(req: Request<T>) -> Res
let parsed_time = chrono::DateTime::parse_from_rfc3339(time)
.map_err(|_| Status::unauthenticated("Coult not parse timestamp"))?;
let seconds_elapsed = now.signed_duration_since(parsed_time).num_seconds();
if seconds_elapsed > 1 || seconds_elapsed < -1 {
if seconds_elapsed > 4 || seconds_elapsed < -4 {
return Err(Status::unauthenticated(format!(
"Date is not within 1 sec of the time of the server: CLI {} vs Server {}",
"Date is not within 4 sec of the time of the server: CLI {} vs Server {}",
parsed_time, now
)));
}
@ -695,9 +803,9 @@ fn check_sig_from_parts(pubkey: &str, time: &str, msg: &str, sig: &str) -> Resul
let parsed_time = chrono::DateTime::parse_from_rfc3339(time)
.map_err(|_| Status::unauthenticated("Coult not parse timestamp"))?;
let seconds_elapsed = now.signed_duration_since(parsed_time).num_seconds();
if seconds_elapsed > 1 || seconds_elapsed < -1 {
if seconds_elapsed > 4 || seconds_elapsed < -4 {
return Err(Status::unauthenticated(format!(
"Date is not within 1 sec of the time of the server: CLI {} vs Server {}",
"Date is not within 4 sec of the time of the server: CLI {} vs Server {}",
parsed_time, now
)));
}

@ -23,7 +23,12 @@ async fn main() {
tokio::spawn(async move {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
data_clone.vm_nodes_cron().await;
data_clone.vm_contracts_cron().await;
data_clone.app_contracts_cron().await;
if let Err(e) = data_clone.save_to_disk() {
log::error!("Could not save data to disk due to error: {e}")
}
}
});
let addr = "0.0.0.0:31337".parse().unwrap();

@ -55,7 +55,7 @@ message MeasurementIP {
// This should also include a block hash or similar, for auth
message RegisterVmNodeReq {
string node_pubkey = 1;
string owner_pubkey = 2;
string operator_wallet = 2;
string main_ip = 3;
string country = 4;
string region = 5;
@ -154,8 +154,8 @@ service BrainVmDaemon {
}
message ListVmContractsReq {
string admin_pubkey = 1;
string node_pubkey = 2;
string wallet = 1;
bool as_operator = 2;
string uuid = 3;
}
@ -174,15 +174,14 @@ message VmNodeFilters {
}
message VmNodeListResp {
string node_pubkey = 1;
string country = 2;
string region = 3;
string city = 4;
string ip = 5; // required for latency test
uint32 server_rating = 6;
uint32 provider_rating = 7;
// nanoLP per unit per minute
uint64 price = 8;
string operator = 1;
string node_pubkey = 2;
string country = 3;
string region = 4;
string city = 5;
string ip = 6; // required for latency test
repeated string reports = 7; // TODO: this will become an enum
uint64 price = 8; // nanoLP per unit per minute
}
message ExtendVmReq {
@ -196,12 +195,59 @@ message AirdropReq {
uint64 tokens = 2;
}
message SlashReq {
string pubkey = 1;
uint64 tokens = 2;
}
message Account {
string pubkey = 1;
uint64 balance = 2;
uint64 tmp_locked = 3;
}
message RegOperatorReq {
string pubkey = 1;
uint64 escrow = 2;
string email = 3;
}
message ListOperatorsResp {
string pubkey = 1;
uint64 escrow = 2;
string email = 3;
uint64 app_nodes = 4;
uint64 vm_nodes = 5;
uint64 reports = 6;
}
message InspectOperatorResp {
ListOperatorsResp operator = 1;
repeated VmNodeListResp nodes = 2;
}
message ReportNodeReq {
string admin_pubkey = 1;
string node_pubkey = 2;
string contract = 3;
string reason = 4;
}
message KickReq {
string operator_wallet = 1;
string contract_uuid = 2;
string reason = 3;
}
message BanUserReq {
string operator_wallet = 1;
string user_wallet = 2;
}
message KickResp {
uint64 nano_lp = 1;
}
service BrainCli {
rpc GetBalance (Pubkey) returns (AccountBalance);
rpc NewVm (NewVmReq) returns (NewVmResp);
@ -211,8 +257,15 @@ service BrainCli {
rpc DeleteVm (DeleteVmReq) returns (Empty);
rpc UpdateVm (UpdateVmReq) returns (UpdateVmResp);
rpc ExtendVm (ExtendVmReq) returns (Empty);
rpc ReportNode (ReportNodeReq) returns (Empty);
rpc ListOperators (Empty) returns (stream ListOperatorsResp);
rpc InspectOperator (Pubkey) returns (InspectOperatorResp);
rpc RegisterOperator (RegOperatorReq) returns (Empty);
rpc KickContract (KickReq) returns (KickResp);
rpc BanUser (BanUserReq) returns (Empty);
// admin commands
rpc Airdrop (AirdropReq) returns (Empty);
rpc Slash (SlashReq) returns (Empty);
rpc ListAllVmContracts (Empty) returns (stream VmContract);
rpc ListAccounts (Empty) returns (stream Account);
}