code migration
This commit is contained in:
commit
6580cc6a75
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
2497
Cargo.lock
generated
Normal file
2497
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
Cargo.toml
Normal file
28
Cargo.toml
Normal file
@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "brain-mock"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.5.1"
|
||||
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.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"
|
||||
tonic = "0.12"
|
||||
uuid = { version = "1.11.0", features = ["v4"] }
|
||||
|
||||
detee-shared = { git = "ssh://git@gitea.detee.cloud/noormohammedb/detee-shared", branch = "stable_01" }
|
||||
# detee-shared = { path = "../detee-shared" }
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.12"
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Brain mock
|
||||
|
||||
eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519
|
6
build.rs
Normal file
6
build.rs
Normal file
@ -0,0 +1,6 @@
|
||||
fn main() {
|
||||
tonic_build::configure()
|
||||
.build_server(true)
|
||||
.compile_protos(&["vm.proto"], &["proto"])
|
||||
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
|
||||
}
|
1524
src/data.rs
Normal file
1524
src/data.rs
Normal file
File diff suppressed because it is too large
Load Diff
857
src/grpc.rs
Normal file
857
src/grpc.rs
Normal file
@ -0,0 +1,857 @@
|
||||
pub mod snp_proto {
|
||||
tonic::include_proto!("vm_proto");
|
||||
}
|
||||
|
||||
use crate::data::BrainData;
|
||||
use crate::grpc::vm_daemon_message;
|
||||
use log::info;
|
||||
use snp_proto::brain_cli_server::BrainCli;
|
||||
use snp_proto::brain_vm_daemon_server::BrainVmDaemon;
|
||||
use snp_proto::*;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{wrappers::ReceiverStream, Stream, StreamExt};
|
||||
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, AppNodeFilters, AppNodeListResp, BrainMessageApp, DaemonMessageApp, DelAppReq,
|
||||
ListAppContractsReq, NewAppReq, NewAppRes, RegisterAppNodeReq,
|
||||
};
|
||||
const ADMIN_ACCOUNTS: &[&str] = &[
|
||||
"x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK",
|
||||
"FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL",
|
||||
"H21Shi4iE7vgfjWEQNvzmpmBMJSaiZ17PYUcdNoAoKNc",
|
||||
];
|
||||
|
||||
pub struct BrainDaemonMock {
|
||||
data: Arc<BrainData>,
|
||||
}
|
||||
|
||||
impl BrainDaemonMock {
|
||||
pub fn new(data: Arc<BrainData>) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BrainCliMock {
|
||||
data: Arc<BrainData>,
|
||||
}
|
||||
|
||||
impl BrainCliMock {
|
||||
pub fn new(data: Arc<BrainData>) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BrainAppCliMock {
|
||||
data: Arc<BrainData>,
|
||||
}
|
||||
|
||||
impl BrainAppCliMock {
|
||||
pub fn new(data: Arc<BrainData>) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BrainAppDaemonMock {
|
||||
data: Arc<BrainData>,
|
||||
}
|
||||
|
||||
impl BrainAppDaemonMock {
|
||||
pub fn new(data: Arc<BrainData>) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl BrainVmDaemon for BrainDaemonMock {
|
||||
type RegisterVmNodeStream = Pin<Box<dyn Stream<Item = Result<VmContract, Status>> + Send>>;
|
||||
async fn register_vm_node(
|
||||
&self,
|
||||
req: Request<RegisterVmNodeReq>,
|
||||
) -> Result<Response<Self::RegisterVmNodeStream>, Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
info!("Starting registration process for {:?}", req);
|
||||
let node = crate::data::VmNode {
|
||||
public_key: req.node_pubkey.clone(),
|
||||
operator_wallet: req.operator_wallet,
|
||||
country: req.country,
|
||||
region: req.region,
|
||||
city: req.city,
|
||||
ip: req.main_ip,
|
||||
price: req.price,
|
||||
..Default::default()
|
||||
};
|
||||
self.data.register_node(node);
|
||||
|
||||
info!("Sending existing contracts to {}", 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 {
|
||||
let _ = tx.send(Ok(contract.into())).await;
|
||||
}
|
||||
});
|
||||
let output_stream = ReceiverStream::new(rx);
|
||||
Ok(Response::new(
|
||||
Box::pin(output_stream) as Self::RegisterVmNodeStream
|
||||
))
|
||||
}
|
||||
|
||||
type BrainMessagesStream = Pin<Box<dyn Stream<Item = Result<BrainVmMessage, Status>> + Send>>;
|
||||
async fn brain_messages(
|
||||
&self,
|
||||
req: Request<DaemonStreamAuth>,
|
||||
) -> Result<Response<Self::BrainMessagesStream>, Status> {
|
||||
let auth = req.into_inner();
|
||||
let pubkey = auth.pubkey.clone();
|
||||
check_sig_from_parts(
|
||||
&pubkey,
|
||||
&auth.timestamp,
|
||||
&format!("{:?}", auth.contracts),
|
||||
&auth.signature,
|
||||
)?;
|
||||
info!("Daemon {} connected to receive brain messages", pubkey);
|
||||
let (tx, rx) = mpsc::channel(6);
|
||||
self.data.add_daemon_tx(&pubkey, tx);
|
||||
let output_stream = ReceiverStream::new(rx).map(|msg| Ok(msg));
|
||||
Ok(Response::new(
|
||||
Box::pin(output_stream) as Self::BrainMessagesStream
|
||||
))
|
||||
}
|
||||
|
||||
async fn daemon_messages(
|
||||
&self,
|
||||
req: Request<Streaming<VmDaemonMessage>>,
|
||||
) -> Result<Response<Empty>, Status> {
|
||||
let mut req_stream = req.into_inner();
|
||||
let pubkey: String;
|
||||
if let Some(Ok(msg)) = req_stream.next().await {
|
||||
log::debug!(
|
||||
"demon_messages received the following auth message: {:?}",
|
||||
msg.msg
|
||||
);
|
||||
if let Some(vm_daemon_message::Msg::Auth(auth)) = msg.msg {
|
||||
pubkey = auth.pubkey.clone();
|
||||
check_sig_from_parts(
|
||||
&pubkey,
|
||||
&auth.timestamp,
|
||||
&format!("{:?}", auth.contracts),
|
||||
&auth.signature,
|
||||
)?;
|
||||
} else {
|
||||
return Err(Status::unauthenticated(
|
||||
"Could not authenticate the daemon: could not extract auth signature",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(Status::unauthenticated("Could not authenticate the daemon"));
|
||||
}
|
||||
|
||||
// info!("Received a message from daemon {pubkey}: {daemon_message:?}");
|
||||
while let Some(daemon_message) = req_stream.next().await {
|
||||
match daemon_message {
|
||||
Ok(msg) => match msg.msg {
|
||||
Some(vm_daemon_message::Msg::NewVmResp(new_vm_resp)) => {
|
||||
self.data.submit_newvm_resp(new_vm_resp).await;
|
||||
}
|
||||
Some(vm_daemon_message::Msg::UpdateVmResp(update_vm_resp)) => {
|
||||
self.data.submit_updatevm_resp(update_vm_resp).await;
|
||||
}
|
||||
Some(vm_daemon_message::Msg::VmNodeResources(node_resources)) => {
|
||||
self.data.submit_node_resources(node_resources);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Daemon disconnected: {e:?}");
|
||||
self.data.del_daemon_tx(&pubkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Response::new(Empty {}))
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl BrainCli for BrainCliMock {
|
||||
async fn get_balance(&self, req: Request<Pubkey>) -> Result<Response<AccountBalance>, Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
Ok(Response::new(self.data.get_balance(&req.pubkey).into()))
|
||||
}
|
||||
|
||||
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;
|
||||
match oneshot_rx.await {
|
||||
Ok(response) => {
|
||||
info!("Sending VM confirmation to {admin_pubkey}: {response:?}");
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Something weird happened. Reached error {e:?}");
|
||||
Err(Status::unknown(
|
||||
"Request failed due to unknown error. Please try again or contact the DeTEE devs team.",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_vm(&self, req: Request<UpdateVmReq>) -> Result<Response<UpdateVmResp>, Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
info!("Update VM requested via CLI: {req:?}");
|
||||
let (oneshot_tx, oneshot_rx) = tokio::sync::oneshot::channel();
|
||||
self.data.submit_updatevm_req(req, oneshot_tx).await;
|
||||
match oneshot_rx.await {
|
||||
Ok(response) => {
|
||||
info!("Sending UpdateVMResp: {response:?}");
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
Err(e) => Err(Status::unknown(format!(
|
||||
"Update VM request failed due to error: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn extend_vm(&self, req: Request<ExtendVmReq>) -> Result<Response<Empty>, Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
match self
|
||||
.data
|
||||
.extend_vm_contract_time(&req.uuid, &req.admin_pubkey, req.locked_nano)
|
||||
{
|
||||
Ok(()) => Ok(Response::new(Empty {})),
|
||||
Err(e) => Err(Status::unknown(format!("Could not extend contract: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
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. 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 {
|
||||
let _ = tx.send(Ok(contract.into())).await;
|
||||
}
|
||||
});
|
||||
let output_stream = ReceiverStream::new(rx);
|
||||
Ok(Response::new(
|
||||
Box::pin(output_stream) as Self::ListVmContractsStream
|
||||
))
|
||||
}
|
||||
|
||||
type ListVmNodesStream = Pin<Box<dyn Stream<Item = Result<VmNodeListResp, Status>> + Send>>;
|
||||
async fn list_vm_nodes(
|
||||
&self,
|
||||
req: Request<VmNodeFilters>,
|
||||
) -> Result<Response<Self::ListVmNodesStream>, tonic::Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
info!("CLI requested ListVmNodesStream: {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 {
|
||||
let _ = tx.send(Ok(node.into())).await;
|
||||
}
|
||||
});
|
||||
let output_stream = ReceiverStream::new(rx);
|
||||
Ok(Response::new(
|
||||
Box::pin(output_stream) as Self::ListVmNodesStream
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_one_vm_node(
|
||||
&self,
|
||||
req: Request<VmNodeFilters>,
|
||||
) -> Result<Response<VmNodeListResp>, Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
info!("Unknown CLI requested ListVmNodesStream: {req:?}");
|
||||
match self.data.get_one_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 register_operator(
|
||||
&self,
|
||||
req: Request<RegOperatorReq>,
|
||||
) -> Result<Response<Empty>, Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
info!("Regitering new operator: {req:?}");
|
||||
match self.data.register_operator(req) {
|
||||
Ok(()) => Ok(Response::new(Empty {})),
|
||||
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",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn airdrop(&self, req: Request<AirdropReq>) -> Result<Response<Empty>, Status> {
|
||||
check_admin_key(&req)?;
|
||||
let req = check_sig_from_req(req)?;
|
||||
self.data.give_airdrop(&req.pubkey, req.tokens);
|
||||
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,
|
||||
req: Request<Empty>,
|
||||
) -> Result<Response<Self::ListVmContractsStream>, Status> {
|
||||
check_admin_key(&req)?;
|
||||
let _ = check_sig_from_req(req)?;
|
||||
let contracts = self.data.list_all_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) as Self::ListVmContractsStream
|
||||
))
|
||||
}
|
||||
|
||||
type ListAccountsStream = Pin<Box<dyn Stream<Item = Result<Account, Status>> + Send>>;
|
||||
async fn list_accounts(
|
||||
&self,
|
||||
req: Request<Empty>,
|
||||
) -> Result<Response<Self::ListAccountsStream>, Status> {
|
||||
check_admin_key(&req)?;
|
||||
let _ = check_sig_from_req(req)?;
|
||||
let accounts = self.data.list_accounts();
|
||||
let (tx, rx) = mpsc::channel(6);
|
||||
tokio::spawn(async move {
|
||||
for account in accounts {
|
||||
let _ = tx.send(Ok(account.into())).await;
|
||||
}
|
||||
});
|
||||
let output_stream = ReceiverStream::new(rx);
|
||||
Ok(Response::new(
|
||||
Box::pin(output_stream) as Self::ListAccountsStream
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
trait PubkeyGetter {
|
||||
fn get_pubkey(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
#[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 deploy_app(
|
||||
&self,
|
||||
req: tonic::Request<NewAppReq>,
|
||||
) -> Result<tonic::Response<NewAppRes>, Status> {
|
||||
let req_data = check_sig_from_req(req)?;
|
||||
log::info!("Creating new container: {req_data:?}");
|
||||
let admin_pubkey = req_data.admin_pubkey.clone();
|
||||
let (oneshot_tx, oneshot_rx) = tokio::sync::oneshot::channel();
|
||||
self.data.send_new_container_req(req_data, oneshot_tx).await;
|
||||
|
||||
match oneshot_rx.await {
|
||||
Ok(response) => {
|
||||
info!("responding container confirmation to {admin_pubkey}: {response:?}");
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Something went wrong. Reached error {e:?}");
|
||||
Err(Status::unknown(
|
||||
"Request failed due to unknown error. Please try again or contact the DeTEE devs team.",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_app(
|
||||
&self,
|
||||
req: tonic::Request<DelAppReq>,
|
||||
) -> Result<tonic::Response<detee_shared::sgx::pb::brain::Empty>, Status> {
|
||||
let req_data = check_sig_from_req(req)?;
|
||||
log::info!("deleting container: {}", req_data.uuid.clone());
|
||||
if let Err(er) = self.data.send_del_container_req(req_data).await {
|
||||
info!("Could not delete container: {er}");
|
||||
return Err(Status::not_found("Could not find container"));
|
||||
};
|
||||
|
||||
Ok(Response::new(detee_shared::sgx::pb::brain::Empty {}))
|
||||
}
|
||||
|
||||
async fn list_app_contracts(
|
||||
&self,
|
||||
req: tonic::Request<ListAppContractsReq>,
|
||||
) -> Result<tonic::Response<Self::ListAppContractsStream>, Status> {
|
||||
let req_data = check_sig_from_req(req)?;
|
||||
let app_contracts = self
|
||||
.data
|
||||
.find_app_contracts_by_admin_pubkey(&req_data.admin_pubkey);
|
||||
|
||||
let (tx, rx) = mpsc::channel(6);
|
||||
tokio::spawn(async move {
|
||||
for contract in app_contracts {
|
||||
let _ = tx.send(contract.into()).await;
|
||||
}
|
||||
});
|
||||
let output_stream = ReceiverStream::new(rx).map(Ok);
|
||||
Ok(Response::new(
|
||||
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]
|
||||
impl BrainAppDaemon for BrainAppDaemonMock {
|
||||
type RegisterAppNodeStream = Pin<Box<dyn Stream<Item = Result<AppContract, Status>> + Send>>;
|
||||
type BrainMessagesStream = Pin<Box<dyn Stream<Item = Result<BrainMessageApp, Status>> + Send>>;
|
||||
|
||||
async fn register_app_node(
|
||||
&self,
|
||||
req: tonic::Request<RegisterAppNodeReq>,
|
||||
) -> Result<tonic::Response<Self::RegisterAppNodeStream>, Status> {
|
||||
let req_data = check_sig_from_req(req)?;
|
||||
log::info!(
|
||||
"registering app node_key : {}, operator_key: {}",
|
||||
&req_data.node_pubkey,
|
||||
&req_data.operator_wallet
|
||||
);
|
||||
|
||||
let app_node = crate::data::AppNode {
|
||||
node_pubkey: req_data.node_pubkey.clone(),
|
||||
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);
|
||||
|
||||
log::info!("Sending existing contracts to {}", &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 {
|
||||
let _ = tx.send(contract.into()).await;
|
||||
}
|
||||
});
|
||||
let output_stream = ReceiverStream::new(rx).map(Ok);
|
||||
Ok(Response::new(Box::pin(output_stream)))
|
||||
}
|
||||
|
||||
async fn brain_messages(
|
||||
&self,
|
||||
req: tonic::Request<detee_shared::sgx::pb::brain::DaemonAuth>,
|
||||
) -> Result<tonic::Response<Self::BrainMessagesStream>, Status> {
|
||||
let req_data = req.into_inner();
|
||||
let pubkey = req_data.pubkey.clone();
|
||||
check_sig_from_parts(
|
||||
&pubkey,
|
||||
&req_data.timestamp,
|
||||
&format!("{:?}", req_data.contracts),
|
||||
&req_data.signature,
|
||||
)?;
|
||||
|
||||
info!(
|
||||
"Daemon {} connected to receive brain messages",
|
||||
req_data.pubkey
|
||||
);
|
||||
let (tx, rx) = mpsc::channel(6);
|
||||
self.data.add_app_daemon_tx(&req_data.pubkey, tx);
|
||||
let output_stream = ReceiverStream::new(rx).map(Ok);
|
||||
Ok(Response::new(
|
||||
Box::pin(output_stream) as Self::BrainMessagesStream
|
||||
))
|
||||
}
|
||||
|
||||
async fn daemon_messages(
|
||||
&self,
|
||||
req: tonic::Request<Streaming<DaemonMessageApp>>,
|
||||
) -> Result<tonic::Response<detee_shared::sgx::pb::brain::Empty>, Status> {
|
||||
let mut req_stream = req.into_inner();
|
||||
let mut pubkey;
|
||||
|
||||
if let Some(Ok(msg)) = req_stream.next().await {
|
||||
log::debug!(
|
||||
"demon_messages received the following auth message: {:?}",
|
||||
msg.msg
|
||||
);
|
||||
if let Some(detee_shared::sgx::pb::brain::daemon_message_app::Msg::Auth(auth)) = msg.msg
|
||||
{
|
||||
pubkey = auth.pubkey.clone();
|
||||
check_sig_from_parts(
|
||||
&pubkey,
|
||||
&auth.timestamp,
|
||||
&format!("{:?}", auth.contracts),
|
||||
&auth.signature,
|
||||
)?;
|
||||
} else {
|
||||
return Err(Status::unauthenticated(
|
||||
"Could not authenticate the daemon: could not extract auth signature",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(Status::unauthenticated("Could not authenticate the daemon"));
|
||||
}
|
||||
|
||||
while let Some(daemon_message) = req_stream.next().await {
|
||||
match daemon_message {
|
||||
Ok(msg) => match msg.msg {
|
||||
Some(detee_shared::sgx::pb::brain::daemon_message_app::Msg::Auth(
|
||||
daemon_auth,
|
||||
)) => pubkey = daemon_auth.pubkey,
|
||||
Some(detee_shared::sgx::pb::brain::daemon_message_app::Msg::NewAppRes(
|
||||
new_app_res,
|
||||
)) => self.data.send_new_container_resp(new_app_res).await,
|
||||
Some(
|
||||
detee_shared::sgx::pb::brain::daemon_message_app::Msg::AppNodeResources(
|
||||
node_resource,
|
||||
),
|
||||
) => self.data.submit_app_node_resources(node_resource),
|
||||
_ => {
|
||||
dbg!("None");
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Daemon disconnected: {e:?}");
|
||||
self.data.del_app_daemon_tx(&pubkey);
|
||||
}
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
Ok(Response::new(detee_shared::sgx::pb::brain::Empty {}))
|
||||
}
|
||||
}
|
||||
|
||||
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_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_pubkey_getter!(VmNodeFilters);
|
||||
impl_pubkey_getter!(Empty);
|
||||
impl_pubkey_getter!(AirdropReq);
|
||||
impl_pubkey_getter!(SlashReq);
|
||||
|
||||
impl_pubkey_getter!(NewAppReq, admin_pubkey);
|
||||
impl_pubkey_getter!(DelAppReq, admin_pubkey);
|
||||
impl_pubkey_getter!(ListAppContractsReq, admin_pubkey);
|
||||
|
||||
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") {
|
||||
Some(t) => t.clone(),
|
||||
None => return Err(Status::unauthenticated("Timestamp not found in metadata.")),
|
||||
};
|
||||
let time = time
|
||||
.to_str()
|
||||
.map_err(|_| Status::unauthenticated("Timestamp in metadata is not a string"))?;
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
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 > 4 || seconds_elapsed < -4 {
|
||||
return Err(Status::unauthenticated(format!(
|
||||
"Date is not within 4 sec of the time of the server: CLI {} vs Server {}",
|
||||
parsed_time, now
|
||||
)));
|
||||
}
|
||||
|
||||
let signature = match req.metadata().get("request-signature") {
|
||||
Some(t) => t,
|
||||
None => return Err(Status::unauthenticated("signature not found in metadata.")),
|
||||
};
|
||||
let signature = bs58::decode(signature)
|
||||
.into_vec()
|
||||
.map_err(|_| Status::unauthenticated("signature is not a bs58 string"))?;
|
||||
let signature = ed25519_dalek::Signature::from_bytes(
|
||||
signature
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| Status::unauthenticated("could not parse ed25519 signature"))?,
|
||||
);
|
||||
|
||||
let pubkey_value = match req.metadata().get("pubkey") {
|
||||
Some(p) => p.clone(),
|
||||
None => return Err(Status::unauthenticated("pubkey not found in metadata.")),
|
||||
};
|
||||
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
||||
&bs58::decode(&pubkey_value)
|
||||
.into_vec()
|
||||
.map_err(|_| Status::unauthenticated("pubkey is not a bs58 string"))?
|
||||
.try_into()
|
||||
.map_err(|_| Status::unauthenticated("pubkey does not have the correct size."))?,
|
||||
)
|
||||
.map_err(|_| Status::unauthenticated("could not parse ed25519 pubkey"))?;
|
||||
|
||||
let req = req.into_inner();
|
||||
let message = format!("{time}{req:?}");
|
||||
use ed25519_dalek::Verifier;
|
||||
pubkey
|
||||
.verify(message.as_bytes(), &signature)
|
||||
.map_err(|_| Status::unauthenticated("the signature is not valid"))?;
|
||||
if let Some(req_pubkey) = req.get_pubkey() {
|
||||
if pubkey_value.to_str().unwrap().to_string() != req_pubkey {
|
||||
return Err(Status::unauthenticated(
|
||||
"pubkey of signature does not match pubkey of request",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
fn check_sig_from_parts(pubkey: &str, time: &str, msg: &str, sig: &str) -> Result<(), Status> {
|
||||
let now = chrono::Utc::now();
|
||||
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 > 4 || seconds_elapsed < -4 {
|
||||
return Err(Status::unauthenticated(format!(
|
||||
"Date is not within 4 sec of the time of the server: CLI {} vs Server {}",
|
||||
parsed_time, now
|
||||
)));
|
||||
}
|
||||
|
||||
let signature = bs58::decode(sig)
|
||||
.into_vec()
|
||||
.map_err(|_| Status::unauthenticated("signature is not a bs58 string"))?;
|
||||
let signature = ed25519_dalek::Signature::from_bytes(
|
||||
signature
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| Status::unauthenticated("could not parse ed25519 signature"))?,
|
||||
);
|
||||
|
||||
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
||||
&bs58::decode(&pubkey)
|
||||
.into_vec()
|
||||
.map_err(|_| Status::unauthenticated("pubkey is not a bs58 string"))?
|
||||
.try_into()
|
||||
.map_err(|_| Status::unauthenticated("pubkey does not have the correct size."))?,
|
||||
)
|
||||
.map_err(|_| Status::unauthenticated("could not parse ed25519 pubkey"))?;
|
||||
|
||||
let msg = time.to_string() + msg;
|
||||
use ed25519_dalek::Verifier;
|
||||
pubkey
|
||||
.verify(msg.as_bytes(), &signature)
|
||||
.map_err(|_| Status::unauthenticated("the signature is not valid"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_admin_key<T>(req: &Request<T>) -> Result<(), Status> {
|
||||
let pubkey = match req.metadata().get("pubkey") {
|
||||
Some(p) => p.clone(),
|
||||
None => return Err(Status::unauthenticated("pubkey not found in metadata.")),
|
||||
};
|
||||
let pubkey = pubkey
|
||||
.to_str()
|
||||
.map_err(|_| Status::unauthenticated("could not parse pubkey metadata to str"))?;
|
||||
|
||||
if !ADMIN_ACCOUNTS.contains(&pubkey) {
|
||||
return Err(Status::unauthenticated(
|
||||
"This operation is reserved to admin accounts",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
50
src/main.rs
Normal file
50
src/main.rs
Normal file
@ -0,0 +1,50 @@
|
||||
mod data;
|
||||
mod grpc;
|
||||
|
||||
use data::BrainData;
|
||||
use detee_shared::sgx::pb::brain::brain_app_cli_server::BrainAppCliServer;
|
||||
use detee_shared::sgx::pb::brain::brain_app_daemon_server::BrainAppDaemonServer;
|
||||
use grpc::snp_proto::brain_cli_server::BrainCliServer;
|
||||
use grpc::snp_proto::brain_vm_daemon_server::BrainVmDaemonServer;
|
||||
use grpc::BrainAppCliMock;
|
||||
use grpc::BrainAppDaemonMock;
|
||||
use grpc::BrainCliMock;
|
||||
use grpc::BrainDaemonMock;
|
||||
use std::sync::Arc;
|
||||
use tonic::transport::Server;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.init();
|
||||
let data = Arc::new(BrainData::new());
|
||||
let data_clone = data.clone();
|
||||
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();
|
||||
|
||||
let daemon_server = BrainVmDaemonServer::new(BrainDaemonMock::new(data.clone()));
|
||||
let cli_server = BrainCliServer::new(BrainCliMock::new(data.clone()));
|
||||
|
||||
let sgx_cli_server = BrainAppCliServer::new(BrainAppCliMock::new(data.clone()));
|
||||
let sgx_daemon_server = BrainAppDaemonServer::new(BrainAppDaemonMock::new(data.clone()));
|
||||
|
||||
Server::builder()
|
||||
.add_service(daemon_server)
|
||||
.add_service(cli_server)
|
||||
.add_service(sgx_cli_server)
|
||||
.add_service(sgx_daemon_server)
|
||||
.serve(addr)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
271
vm.proto
Normal file
271
vm.proto
Normal file
@ -0,0 +1,271 @@
|
||||
syntax = "proto3";
|
||||
package vm_proto;
|
||||
|
||||
message Empty {
|
||||
}
|
||||
|
||||
message Pubkey {
|
||||
string pubkey = 1;
|
||||
}
|
||||
|
||||
message AccountBalance {
|
||||
uint64 balance = 1;
|
||||
uint64 tmp_locked = 2;
|
||||
}
|
||||
|
||||
message VmContract {
|
||||
string uuid = 1;
|
||||
string hostname = 2;
|
||||
string admin_pubkey = 3;
|
||||
string node_pubkey = 4;
|
||||
repeated uint32 exposed_ports = 5;
|
||||
string public_ipv4 = 6;
|
||||
string public_ipv6 = 7;
|
||||
uint32 disk_size_gb = 8;
|
||||
uint32 vcpus = 9;
|
||||
uint32 memory_mb = 10;
|
||||
string kernel_sha = 11;
|
||||
string dtrfs_sha = 12;
|
||||
string created_at = 13;
|
||||
string updated_at = 14;
|
||||
// total nanoLP cost per minute (for all units)
|
||||
uint64 nano_per_minute = 15;
|
||||
uint64 locked_nano = 16;
|
||||
string collected_at = 17;
|
||||
}
|
||||
|
||||
message MeasurementArgs {
|
||||
// this will be IP:Port of the dtrfs API
|
||||
// actually not a measurement arg, but needed for the injector
|
||||
string dtrfs_api_endpoint = 1;
|
||||
repeated uint32 exposed_ports = 2;
|
||||
string ovmf_hash = 5;
|
||||
// This is needed to allow the CLI to build the kernel params from known data.
|
||||
// The CLI will use the kernel params to get the measurement.
|
||||
repeated MeasurementIP ips = 6;
|
||||
}
|
||||
|
||||
message MeasurementIP {
|
||||
uint32 nic_index = 1;
|
||||
string address = 2;
|
||||
string mask = 3;
|
||||
string gateway = 4;
|
||||
}
|
||||
|
||||
// This should also include a block hash or similar, for auth
|
||||
message RegisterVmNodeReq {
|
||||
string node_pubkey = 1;
|
||||
string operator_wallet = 2;
|
||||
string main_ip = 3;
|
||||
string country = 4;
|
||||
string region = 5;
|
||||
string city = 6;
|
||||
// nanoLP per unit per minute
|
||||
uint64 price = 7;
|
||||
}
|
||||
|
||||
message VmNodeResources {
|
||||
string node_pubkey = 1;
|
||||
uint32 avail_ports = 2;
|
||||
uint32 avail_ipv4 = 3;
|
||||
uint32 avail_ipv6 = 4;
|
||||
uint32 avail_vcpus = 5;
|
||||
uint32 avail_memory_mb = 6;
|
||||
uint32 avail_storage_gb = 7;
|
||||
uint32 max_ports_per_vm = 8;
|
||||
}
|
||||
|
||||
message NewVmReq {
|
||||
string uuid = 1;
|
||||
string hostname = 2;
|
||||
string admin_pubkey = 3;
|
||||
string node_pubkey = 4;
|
||||
repeated uint32 extra_ports = 5;
|
||||
bool public_ipv4 = 6;
|
||||
bool public_ipv6 = 7;
|
||||
uint32 disk_size_gb = 8;
|
||||
uint32 vcpus = 9;
|
||||
uint32 memory_mb = 10;
|
||||
string kernel_url = 11;
|
||||
string kernel_sha = 12;
|
||||
string dtrfs_url = 13;
|
||||
string dtrfs_sha = 14;
|
||||
uint64 price_per_unit = 15;
|
||||
uint64 locked_nano = 16;
|
||||
}
|
||||
|
||||
message NewVmResp {
|
||||
string uuid = 1;
|
||||
string error = 2;
|
||||
MeasurementArgs args = 3;
|
||||
}
|
||||
|
||||
message UpdateVmReq {
|
||||
string uuid = 1;
|
||||
string admin_pubkey = 2;
|
||||
uint32 disk_size_gb = 3;
|
||||
uint32 vcpus = 4;
|
||||
uint32 memory_mb = 5;
|
||||
string kernel_url = 6;
|
||||
string kernel_sha = 7;
|
||||
string dtrfs_url = 8;
|
||||
string dtrfs_sha = 9;
|
||||
}
|
||||
|
||||
message UpdateVmResp {
|
||||
string uuid = 1;
|
||||
string error = 2;
|
||||
MeasurementArgs args = 3;
|
||||
}
|
||||
|
||||
message DeleteVmReq {
|
||||
string uuid = 1;
|
||||
string admin_pubkey = 2;
|
||||
}
|
||||
|
||||
message BrainVmMessage {
|
||||
oneof Msg {
|
||||
NewVmReq new_vm_req = 1;
|
||||
UpdateVmReq update_vm_req = 2;
|
||||
DeleteVmReq delete_vm = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message DaemonStreamAuth {
|
||||
string timestamp = 1;
|
||||
string pubkey = 2;
|
||||
repeated string contracts = 3;
|
||||
string signature = 4;
|
||||
}
|
||||
|
||||
message VmDaemonMessage {
|
||||
oneof Msg {
|
||||
DaemonStreamAuth auth = 1;
|
||||
NewVmResp new_vm_resp = 2;
|
||||
UpdateVmResp update_vm_resp = 3;
|
||||
VmNodeResources vm_node_resources = 4;
|
||||
}
|
||||
}
|
||||
|
||||
service BrainVmDaemon {
|
||||
rpc RegisterVmNode (RegisterVmNodeReq) returns (stream VmContract);
|
||||
rpc BrainMessages (DaemonStreamAuth) returns (stream BrainVmMessage);
|
||||
rpc DaemonMessages (stream VmDaemonMessage) returns (Empty);
|
||||
}
|
||||
|
||||
message ListVmContractsReq {
|
||||
string wallet = 1;
|
||||
bool as_operator = 2;
|
||||
string uuid = 3;
|
||||
}
|
||||
|
||||
message VmNodeFilters {
|
||||
uint32 free_ports = 1;
|
||||
bool offers_ipv4 = 2;
|
||||
bool offers_ipv6 = 3;
|
||||
uint32 vcpus = 4;
|
||||
uint32 memory_mb = 5;
|
||||
uint32 storage_gb = 6;
|
||||
string country = 7;
|
||||
string region = 8;
|
||||
string city = 9;
|
||||
string ip = 10;
|
||||
string node_pubkey = 11;
|
||||
}
|
||||
|
||||
message VmNodeListResp {
|
||||
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 {
|
||||
string uuid = 1;
|
||||
string admin_pubkey = 2;
|
||||
uint64 locked_nano = 3;
|
||||
}
|
||||
|
||||
message AirdropReq {
|
||||
string pubkey = 1;
|
||||
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);
|
||||
rpc ListVmContracts (ListVmContractsReq) returns (stream VmContract);
|
||||
rpc ListVmNodes (VmNodeFilters) returns (stream VmNodeListResp);
|
||||
rpc GetOneVmNode (VmNodeFilters) returns (VmNodeListResp);
|
||||
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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user