metrics and refactoring

Signed-off-by: Valentyn Faychuk <valy@detee.ltd>
This commit is contained in:
Valentyn Faychuk 2024-12-02 03:39:27 +02:00
parent 3c93b258f5
commit 2a87efacc1
Signed by: valy
GPG Key ID: F1AB995E20FEADC5
10 changed files with 298 additions and 221 deletions

40
Cargo.lock generated

@ -1490,6 +1490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde",
] ]
[[package]] [[package]]
@ -1525,7 +1526,7 @@ dependencies = [
[[package]] [[package]]
name = "detee-sgx" name = "detee-sgx"
version = "0.1.0" version = "0.1.0"
source = "git+ssh://git@gitea.detee.cloud/SGX/detee-sgx#a47753a8e07ef533cca5df41bea4893c9eeb133e" source = "git+ssh://git@gitea.detee.cloud/SGX/detee-sgx?branch=hacker-challenge#2f032b5c5448be40feda466bb457821ef814f959"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"base64 0.22.1", "base64 0.22.1",
@ -2042,6 +2043,7 @@ dependencies = [
"rustls 0.23.14", "rustls 0.23.14",
"serde", "serde",
"serde_json", "serde_json",
"serde_with 3.11.0",
"solana-client", "solana-client",
"solana-program", "solana-program",
"solana-sdk", "solana-sdk",
@ -2418,6 +2420,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown 0.12.3", "hashbrown 0.12.3",
"serde",
] ]
[[package]] [[package]]
@ -2428,6 +2431,7 @@ checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.0", "hashbrown 0.15.0",
"serde",
] ]
[[package]] [[package]]
@ -3893,7 +3897,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe"
dependencies = [ dependencies = [
"serde", "serde",
"serde_with_macros", "serde_with_macros 2.3.3",
]
[[package]]
name = "serde_with"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817"
dependencies = [
"base64 0.22.1",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.6.0",
"serde",
"serde_derive",
"serde_json",
"serde_with_macros 3.11.0",
"time",
] ]
[[package]] [[package]]
@ -3908,6 +3930,18 @@ dependencies = [
"syn 2.0.79", "syn 2.0.79",
] ]
[[package]]
name = "serde_with_macros"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.79",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@ -4510,7 +4544,7 @@ dependencies = [
"serde_bytes", "serde_bytes",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"serde_with", "serde_with 2.3.3",
"sha2 0.10.8", "sha2 0.10.8",
"sha3 0.10.8", "sha3 0.10.8",
"siphasher", "siphasher",

@ -12,6 +12,7 @@ prost = "0.13"
prost-types = "0.13" prost-types = "0.13"
rand = "0.8" rand = "0.8"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_with = { version = "3.11", features = ["macros", "base64"] }
serde_json = "1.0" serde_json = "1.0"
solana-client = "2.0" solana-client = "2.0"
solana-program = "2.0" solana-program = "2.0"
@ -36,8 +37,7 @@ hyper-util = "0.1.7"
hyper-rustls = { version = "0.27", features = ["http2"] } hyper-rustls = { version = "0.27", features = ["http2"] }
base64 = "0.22" base64 = "0.22"
lazy_static = "1.5" lazy_static = "1.5"
# TODO: create a feature for testing, make occlum feature optional and added only if not compiling for testing detee-sgx = { git = "ssh://git@gitea.detee.cloud/SGX/detee-sgx", branch = "hacker-challenge", features = ["tonic", "occlum", "sealing"] }
detee-sgx = { git = "ssh://git@gitea.detee.cloud/SGX/detee-sgx", features = ["tonic", "occlum", "sealing"] }
env_logger = "0.11" env_logger = "0.11"

@ -1,8 +1,6 @@
#![allow(dead_code)] use crate::persistence::Logfile;
use crate::solana::Client as SolClient;
use dashmap::{DashMap, DashSet}; use dashmap::{DashMap, DashSet};
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use crate::persistence::Logfile;
type IP = String; type IP = String;
pub const LOCALHOST: &str = "localhost"; pub const LOCALHOST: &str = "localhost";
@ -14,7 +12,7 @@ pub struct NodeInfo {
pub mint_requests: u64, pub mint_requests: u64,
pub mints: u64, pub mints: u64,
pub mratls_conns: u64, pub mratls_conns: u64,
pub quote_attacks: u64, pub net_attacks: u64,
pub public: bool, pub public: bool,
pub restarts: u64, pub restarts: u64,
pub disk_attacks: u64, pub disk_attacks: u64,
@ -28,7 +26,8 @@ impl NodeInfo {
} }
} }
if self.mint_requests > other_node.mint_requests if self.mint_requests > other_node.mint_requests
|| self.quote_attacks > other_node.quote_attacks || self.net_attacks > other_node.net_attacks
|| self.disk_attacks > other_node.disk_attacks
|| self.mratls_conns > other_node.mratls_conns || self.mratls_conns > other_node.mratls_conns
|| self.mints > other_node.mints || self.mints > other_node.mints
|| (self.public && !other_node.public) || (self.public && !other_node.public)
@ -45,19 +44,17 @@ impl NodeInfo {
} }
} }
/// Keypair must already be known when creating a Store /// Multithreaded state, designed to be
/// This means the first node of the network creates the key /// shared everywhere in the code
/// Second node will grab the key from the first node pub struct State {
pub struct Store {
sol_client: SolClient,
nodes: DashMap<IP, NodeInfo>, nodes: DashMap<IP, NodeInfo>,
conns: DashSet<IP>, conns: DashSet<IP>,
} }
impl Store { impl State {
pub fn init(sol_client: SolClient) -> Self { pub fn new() -> Self {
let store = Self { sol_client, nodes: DashMap::new(), conns: DashSet::new() }; let state = Self { nodes: DashMap::new(), conns: DashSet::new() };
store.nodes.insert( state.nodes.insert(
LOCALHOST.to_string(), LOCALHOST.to_string(),
NodeInfo { NodeInfo {
started_at: SystemTime::now(), started_at: SystemTime::now(),
@ -65,25 +62,13 @@ impl Store {
mint_requests: 0, mint_requests: 0,
mints: 0, mints: 0,
mratls_conns: 0, mratls_conns: 0,
quote_attacks: 0, net_attacks: 0,
public: false, public: false,
restarts: 0, restarts: 0,
disk_attacks: 0, disk_attacks: 0,
}, },
); );
store state
}
pub fn get_token_address(&self) -> String {
self.sol_client.token_address()
}
pub fn get_pubkey_base58(&self) -> String {
self.sol_client.wallet_address()
}
pub fn get_keypair_bytes(&self) -> Vec<u8> {
self.sol_client.get_keypair_bytes()
} }
pub fn add_conn(&self, ip: &str) { pub fn add_conn(&self, ip: &str) {
@ -122,12 +107,11 @@ impl Store {
} }
} }
pub fn mint(&self, recipient: &str) -> Result<String, Box<dyn std::error::Error>> { pub fn increase_net_attacks(&self) {
use std::str::FromStr; if let Some(mut localhost_info) = self.nodes.get_mut(LOCALHOST) {
let recipient = solana_sdk::pubkey::Pubkey::from_str(recipient)?; localhost_info.net_attacks += 1;
let sig = self.sol_client.mint(&recipient)?; localhost_info.log(localhost_info.key());
self.increase_mints(); }
Ok(sig)
} }
pub fn get_localhost(&self) -> NodeInfo { pub fn get_localhost(&self) -> NodeInfo {

@ -1,10 +1,9 @@
#![allow(dead_code)] use super::challenge::{Keys, NodeUpdate};
use super::challenge::NodeUpdate;
use crate::{ use crate::{
datastore::{Store, LOCALHOST}, datastore::{State, LOCALHOST},
grpc::challenge::{update_client::UpdateClient, Empty}, grpc::challenge::{update_client::UpdateClient, Empty},
}; };
use solana_sdk::{pubkey::Pubkey, signature::keypair::Keypair}; use detee_sgx::RaTlsConfig;
use std::{str::FromStr, sync::Arc}; use std::{str::FromStr, sync::Arc};
use tokio::{ use tokio::{
sync::broadcast::Sender, sync::broadcast::Sender,
@ -14,13 +13,14 @@ use tokio_stream::{wrappers::BroadcastStream, StreamExt};
#[derive(Clone)] #[derive(Clone)]
pub struct ConnManager { pub struct ConnManager {
ds: Arc<Store>, state: Arc<State>,
tx: Sender<NodeUpdate>, tx: Sender<NodeUpdate>,
ratls_config: RaTlsConfig,
} }
impl ConnManager { impl ConnManager {
pub fn init(ds: Arc<Store>, tx: Sender<NodeUpdate>) -> Self { pub fn init(state: Arc<State>, ratls_config: RaTlsConfig, tx: Sender<NodeUpdate>) -> Self {
Self { ds, tx } Self { state, ratls_config, tx }
} }
pub async fn start_with_node(self, node_ip: String) { pub async fn start_with_node(self, node_ip: String) {
@ -29,7 +29,7 @@ impl ConnManager {
pub async fn start(self) { pub async fn start(self) {
loop { loop {
if let Some(node) = self.ds.get_random_node() { if let Some(node) = self.state.get_random_node() {
if node != LOCALHOST { if node != LOCALHOST {
self.connect_wrapper(node.clone()).await; self.connect_wrapper(node.clone()).await;
} }
@ -39,7 +39,7 @@ impl ConnManager {
} }
async fn connect_wrapper(&self, node_ip: String) { async fn connect_wrapper(&self, node_ip: String) {
let ds = self.ds.clone(); let ds = self.state.clone();
ds.add_conn(&node_ip); ds.add_conn(&node_ip);
if let Err(e) = self.connect(node_ip.clone()).await { if let Err(e) = self.connect(node_ip.clone()).await {
println!("Client connection for {node_ip} failed: {e:?}"); println!("Client connection for {node_ip} failed: {e:?}");
@ -48,20 +48,14 @@ impl ConnManager {
} }
async fn connect(&self, node_ip: String) -> Result<(), Box<dyn std::error::Error>> { async fn connect(&self, node_ip: String) -> Result<(), Box<dyn std::error::Error>> {
use detee_sgx::{prelude::*, RaTlsConfigBuilder}; use detee_sgx::RaTlsConfigBuilder;
use hyper::Uri; use hyper::Uri;
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
use tokio_rustls::rustls::ClientConfig; use tokio_rustls::rustls::ClientConfig;
println!("Connecting to {node_ip}..."); println!("Connecting to {node_ip}...");
let mrsigner_hex = "83E8A0C3ED045D9747ADE06C3BFC70FCA661A4A65FF79A800223621162A88B76"; let tls = ClientConfig::from_ratls_config(self.ratls_config.clone())
let mrsigner =
crate::sgx::mrsigner_from_hex(mrsigner_hex).expect("mrsigner decoding failed");
let config = RaTlsConfig::new()
.allow_instance_measurement(InstanceMeasurement::new().with_mrsigners(vec![mrsigner]));
let tls = ClientConfig::from_ratls_config(config)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?; .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?;
let mut http = HttpConnector::new(); let mut http = HttpConnector::new();
@ -93,10 +87,14 @@ impl ConnManager {
let rx = self.tx.subscribe(); let rx = self.tx.subscribe();
let rx_stream = BroadcastStream::new(rx).filter_map(|n| n.ok()); let rx_stream = BroadcastStream::new(rx).filter_map(|n| n.ok());
let response = client.get_updates(rx_stream).await?; let response = client.get_updates(rx_stream).await.map_err(|e| {
println!("Error connecting to {node_ip}: {e}");
self.state.increase_net_attacks();
e
})?;
let mut resp_stream = response.into_inner(); let mut resp_stream = response.into_inner();
let _ = self.tx.send((LOCALHOST.to_string(), self.ds.get_localhost()).into()); let _ = self.tx.send((LOCALHOST.to_string(), self.state.get_localhost()).into());
while let Some(mut update) = resp_stream.message().await? { while let Some(mut update) = resp_stream.message().await? {
// "localhost" IPs need to be changed to the real IP of the counterpart // "localhost" IPs need to be changed to the real IP of the counterpart
@ -108,7 +106,7 @@ impl ConnManager {
} }
// update the entire network in case the information is new // update the entire network in case the information is new
if self.ds.process_node_update(update.clone().into()) if self.state.process_node_update(update.clone().into())
&& self.tx.send(update.clone()).is_err() && self.tx.send(update.clone()).is_err()
{ {
println!("tokio broadcast receivers had an issue consuming the channel"); println!("tokio broadcast receivers had an issue consuming the channel");
@ -119,20 +117,19 @@ impl ConnManager {
} }
} }
pub async fn key_grabber(node_ip: String) -> Result<(Keypair, Pubkey), Box<dyn std::error::Error>> { pub async fn key_grabber(
use detee_sgx::{prelude::*, RaTlsConfigBuilder}; state: Arc<State>,
node_ip: String,
ratls_config: RaTlsConfig,
) -> Result<Keys, Box<dyn std::error::Error>> {
use detee_sgx::RaTlsConfigBuilder;
use hyper::Uri; use hyper::Uri;
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
use tokio_rustls::rustls::ClientConfig; use tokio_rustls::rustls::ClientConfig;
println!("Getting key from {node_ip}..."); println!("Getting key from {node_ip}...");
let mrsigner_hex = "83E8A0C3ED045D9747ADE06C3BFC70FCA661A4A65FF79A800223621162A88B76"; let tls = ClientConfig::from_ratls_config(ratls_config)
let mrsigner = crate::sgx::mrsigner_from_hex(mrsigner_hex).expect("mrsigner decoding failed");
let config = RaTlsConfig::new()
.allow_instance_measurement(InstanceMeasurement::new().with_mrsigners(vec![mrsigner]));
let tls = ClientConfig::from_ratls_config(config)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?; .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?;
let mut http = HttpConnector::new(); let mut http = HttpConnector::new();
@ -160,14 +157,10 @@ pub async fn key_grabber(node_ip: String) -> Result<(Keypair, Pubkey), Box<dyn s
let uri = Uri::from_static("https://example.com"); let uri = Uri::from_static("https://example.com");
let mut client = UpdateClient::with_origin(client, uri); let mut client = UpdateClient::with_origin(client, uri);
let response = client.get_keys(tonic::Request::new(Empty {})).await?; let response = client.get_keys(tonic::Request::new(Empty {})).await.map_err(|e| {
let response = &response.into_inner(); println!("Error getting keys from {node_ip}: {e}");
let keypair = response.keypair.clone(); state.increase_net_attacks();
let keypair = match Keypair::from_bytes(&keypair) { e
Ok(k) => k, })?;
Err(_) => return Err("Could not parse keypair.".into()), Ok(response.into_inner())
};
let token_address = Pubkey::from_str(&response.token_address)
.map_err(|_| "Could not parse wallet address.".to_string())?;
Ok((keypair, token_address))
} }

@ -16,10 +16,10 @@ impl From<(String, NodeInfo)> for NodeUpdate {
mint_requests: info.mint_requests, mint_requests: info.mint_requests,
mints: info.mints, mints: info.mints,
mratls_conns: info.mratls_conns, mratls_conns: info.mratls_conns,
quote_attacks: info.quote_attacks, quote_attacks: info.net_attacks,
public: info.public, public: info.public,
restarts: info.restarts, restarts: info.restarts,
disk_attacks: info.disk_attacks disk_attacks: info.disk_attacks,
} }
} }
} }
@ -47,10 +47,10 @@ impl From<NodeUpdate> for (String, NodeInfo) {
mint_requests: val.mint_requests, mint_requests: val.mint_requests,
mints: val.mints, mints: val.mints,
mratls_conns: val.mratls_conns, mratls_conns: val.mratls_conns,
quote_attacks: val.quote_attacks, net_attacks: val.quote_attacks,
public: val.public, public: val.public,
restarts: val.restarts, restarts: val.restarts,
disk_attacks: val.disk_attacks disk_attacks: val.disk_attacks,
}; };
(ip, self_info) (ip, self_info)
} }

@ -1,23 +1,30 @@
#![allow(dead_code)]
use super::challenge::{update_server::UpdateServer, Empty, Keys, NodeUpdate}; use super::challenge::{update_server::UpdateServer, Empty, Keys, NodeUpdate};
use crate::{datastore::Store, grpc::challenge::update_server::Update}; use crate::{datastore::State, grpc::challenge::update_server::Update};
use detee_sgx::RaTlsConfig;
use std::{pin::Pin, sync::Arc}; use std::{pin::Pin, sync::Arc};
use tokio::sync::broadcast::Sender; use tokio::sync::broadcast::Sender;
use tokio_stream::{Stream, StreamExt}; use tokio_stream::{Stream, StreamExt};
use tonic::{Request, Response, Status, Streaming}; use tonic::{Request, Response, Status, Streaming};
pub struct MyServer { pub struct MyServer {
ds: Arc<Store>, state: Arc<State>,
tx: Sender<NodeUpdate>, tx: Sender<NodeUpdate>,
ratls_config: RaTlsConfig,
keys: Keys, // For sending secret keys to new nodes ;)
} }
impl MyServer { impl MyServer {
pub fn init(ds: Arc<Store>, tx: Sender<NodeUpdate>) -> Self { pub fn init(
Self { ds, tx } state: Arc<State>,
keys: Keys,
ratls_config: RaTlsConfig,
tx: Sender<NodeUpdate>,
) -> Self {
Self { state, tx, keys, ratls_config }
} }
pub async fn start(self) { pub async fn start(self) {
use detee_sgx::RaTlsConfigBuilder;
use hyper::server::conn::http2::Builder; use hyper::server::conn::http2::Builder;
use hyper_util::{ use hyper_util::{
rt::{TokioExecutor, TokioIo}, rt::{TokioExecutor, TokioIo},
@ -29,22 +36,12 @@ impl MyServer {
use tonic::{body::boxed, service::Routes}; use tonic::{body::boxed, service::Routes};
use tower::{ServiceBuilder, ServiceExt}; use tower::{ServiceBuilder, ServiceExt};
use detee_sgx::{prelude::*, RaTlsConfigBuilder};
// TODO: ratls config should be global
// TODO: error handling, shouldn't have expects // TODO: error handling, shouldn't have expects
let mrsigner_hex = "83E8A0C3ED045D9747ADE06C3BFC70FCA661A4A65FF79A800223621162A88B76"; let mut tls = ServerConfig::from_ratls_config(self.ratls_config.clone()).unwrap();
let mrsigner =
crate::sgx::mrsigner_from_hex(mrsigner_hex).expect("mrsigner decoding failed");
let config = RaTlsConfig::new()
.allow_instance_measurement(InstanceMeasurement::new().with_mrsigners(vec![mrsigner]));
let mut tls = ServerConfig::from_ratls_config(config)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))
.expect("failed to create server config");
tls.alpn_protocols = vec![b"h2".to_vec()]; tls.alpn_protocols = vec![b"h2".to_vec()];
let state = self.state.clone();
let svc = Routes::new(UpdateServer::new(self)).prepare(); let svc = Routes::new(UpdateServer::new(self)).prepare();
let http = Builder::new(TokioExecutor::new()); let http = Builder::new(TokioExecutor::new());
@ -53,10 +50,11 @@ impl MyServer {
let tls_acceptor = TlsAcceptor::from(Arc::new(tls)); let tls_acceptor = TlsAcceptor::from(Arc::new(tls));
loop { loop {
let (conn, addr) = match listener.accept().await { let (conn, _addr) = match listener.accept().await {
Ok(incoming) => incoming, Ok(incoming) => incoming,
Err(e) => { Err(e) => {
eprintln!("Error accepting connection: {}", e); println!("Error accepting connection: {}", e);
state.increase_net_attacks();
continue; continue;
} }
}; };
@ -65,6 +63,7 @@ impl MyServer {
let tls_acceptor = tls_acceptor.clone(); let tls_acceptor = tls_acceptor.clone();
let svc = svc.clone(); let svc = svc.clone();
let state = state.clone();
tokio::spawn(async move { tokio::spawn(async move {
let mut certificates = Vec::new(); let mut certificates = Vec::new();
@ -76,30 +75,29 @@ impl MyServer {
} }
} }
}) })
.await .await;
.unwrap();
#[derive(Debug)] let conn = if let Err(e) = conn {
pub struct ConnInfo { println!("Error accepting TLS connection: {}", e);
pub addr: std::net::SocketAddr, state.increase_net_attacks();
pub certificates: Vec<rustls::pki_types::CertificateDer<'static>>, return;
} } else {
conn.unwrap()
};
let extension_layer = let svc = ServiceBuilder::new().service(svc);
tower_http::add_extension::AddExtensionLayer::new(Arc::new(ConnInfo {
addr,
certificates,
}));
let svc = ServiceBuilder::new().layer(extension_layer).service(svc);
http.serve_connection( if let Err(e) = http
.serve_connection(
TokioIo::new(conn), TokioIo::new(conn),
TowerToHyperService::new( TowerToHyperService::new(
svc.map_request(|req: hyper::Request<_>| req.map(boxed)), svc.map_request(|req: hyper::Request<_>| req.map(boxed)),
), ),
) )
.await .await
.unwrap(); {
println!("Error serving connection: {}", e);
}
}); });
} }
} }
@ -109,16 +107,20 @@ impl MyServer {
impl Update for MyServer { impl Update for MyServer {
type GetUpdatesStream = Pin<Box<dyn Stream<Item = Result<NodeUpdate, Status>> + Send>>; type GetUpdatesStream = Pin<Box<dyn Stream<Item = Result<NodeUpdate, Status>> + Send>>;
async fn get_keys(&self, _request: Request<Empty>) -> Result<Response<Keys>, Status> {
Ok(Response::new(self.keys.clone()))
}
async fn get_updates( async fn get_updates(
&self, &self,
req: Request<Streaming<NodeUpdate>>, req: Request<Streaming<NodeUpdate>>,
) -> Result<Response<Self::GetUpdatesStream>, Status> { ) -> Result<Response<Self::GetUpdatesStream>, Status> {
self.ds.increase_mratls_conns(); self.state.increase_mratls_conns();
let remote_ip = req.remote_addr().unwrap().ip().to_string(); let remote_ip = req.remote_addr().unwrap().ip().to_string();
let tx = self.tx.clone(); let tx = self.tx.clone();
let mut rx = self.tx.subscribe(); let mut rx = self.tx.subscribe();
let mut inbound = req.into_inner(); let mut inbound = req.into_inner();
let ds = self.ds.clone(); let ds = self.state.clone();
let stream = async_stream::stream! { let stream = async_stream::stream! {
let full_update_list: Vec<NodeUpdate> = ds.get_node_list().into_iter().map(Into::<NodeUpdate>::into).collect(); let full_update_list: Vec<NodeUpdate> = ds.get_node_list().into_iter().map(Into::<NodeUpdate>::into).collect();
@ -161,12 +163,4 @@ impl Update for MyServer {
Ok(Response::new(Box::pin(stream) as Self::GetUpdatesStream)) Ok(Response::new(Box::pin(stream) as Self::GetUpdatesStream))
} }
async fn get_keys(&self, _request: Request<Empty>) -> Result<Response<Keys>, Status> {
let reply = Keys {
keypair: self.ds.get_keypair_bytes(),
token_address: self.ds.get_token_address(),
};
Ok(Response::new(reply))
}
} }

@ -1,5 +1,4 @@
#![allow(dead_code)] use crate::{datastore, datastore::State, solana::SolClient};
use crate::{datastore, datastore::Store};
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,11 +7,11 @@ use std::sync::Arc;
const HOMEPAGE: &str = include_str!("HOMEPAGE.md"); const HOMEPAGE: &str = include_str!("HOMEPAGE.md");
#[get("/")] #[get("/")]
async fn homepage(ds: web::Data<Arc<Store>>) -> impl Responder { async fn homepage(sol_client: web::Data<Arc<SolClient>>) -> impl Responder {
let text = HOMEPAGE let text = HOMEPAGE
.to_string() .to_string()
.replace("TOKEN_ADDRESS", &ds.get_token_address()) .replace("TOKEN_ADDRESS", &sol_client.get_token_address())
.replace("MINT_AUTHORITY", &ds.get_pubkey_base58()); .replace("MINT_AUTHORITY", &sol_client.get_wallet_pubkey());
HttpResponse::Ok().body(text) HttpResponse::Ok().body(text)
} }
@ -40,7 +39,7 @@ impl From<(String, datastore::NodeInfo)> for NodesResp {
last_keepalive, last_keepalive,
mints: node_info.mints, mints: node_info.mints,
total_ratls_conns: node_info.mratls_conns, total_ratls_conns: node_info.mratls_conns,
ratls_attacks: node_info.quote_attacks, ratls_attacks: node_info.net_attacks,
public: node_info.public, public: node_info.public,
mint_requests: node_info.mint_requests, mint_requests: node_info.mint_requests,
} }
@ -48,7 +47,7 @@ impl From<(String, datastore::NodeInfo)> for NodesResp {
} }
#[get("/nodes")] #[get("/nodes")]
async fn get_nodes(ds: web::Data<Arc<Store>>) -> HttpResponse { async fn get_nodes(ds: web::Data<Arc<State>>) -> HttpResponse {
HttpResponse::Ok().json( HttpResponse::Ok().json(
ds.get_node_list().into_iter().map(Into::<NodesResp>::into).collect::<Vec<NodesResp>>(), ds.get_node_list().into_iter().map(Into::<NodesResp>::into).collect::<Vec<NodesResp>>(),
) )
@ -60,22 +59,30 @@ struct MintReq {
} }
#[post("/mint")] #[post("/mint")]
async fn mint(ds: web::Data<Arc<Store>>, req: web::Json<MintReq>) -> impl Responder { async fn mint(
ds.increase_mint_requests(); state: web::Data<Arc<State>>,
sol_client: web::Data<Arc<SolClient>>,
req: web::Json<MintReq>,
) -> impl Responder {
let recipient = req.into_inner().wallet;
state.increase_mint_requests();
let result = let result =
web::block(move || ds.mint(&req.into_inner().wallet).map_err(|e| e.to_string())) web::block(move || sol_client.mint(&recipient).map_err(|e| e.to_string())).await.unwrap(); // TODO: check if this can get a BlockingError
.await
.unwrap(); // TODO: check if this can get a BlockingError
match result { match result {
Ok(s) => HttpResponse::Ok().body(format!(r#"{{" signature": "{s} "}}"#)), Ok(s) => {
state.increase_mints();
HttpResponse::Ok().body(format!(r#"{{" signature": "{s} "}}"#))
}
Err(e) => HttpResponse::InternalServerError().body(format!(r#"{{ "error": "{e}" }}"#)), Err(e) => HttpResponse::InternalServerError().body(format!(r#"{{ "error": "{e}" }}"#)),
} }
} }
pub async fn init(ds: Arc<Store>) { pub async fn init(state: Arc<State>, sol_client: Arc<SolClient>) {
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.app_data(web::Data::new(ds.clone())) .app_data(web::Data::new(state.clone()))
.app_data(web::Data::new(sol_client.clone()))
.service(homepage) .service(homepage)
.service(get_nodes) .service(get_nodes)
.service(mint) .service(mint)

@ -5,9 +5,11 @@ mod persistence;
mod sgx; mod sgx;
mod solana; mod solana;
use crate::{datastore::LOCALHOST, grpc::challenge::NodeUpdate, solana::Client as SolClient}; use crate::{
use datastore::Store; datastore::LOCALHOST, grpc::challenge::NodeUpdate, persistence::KeysFile, solana::SolClient,
use solana_sdk::signer::Signer; };
use datastore::State;
use detee_sgx::{InstanceMeasurement, RaTlsConfig};
use std::{ use std::{
fs::File, fs::File,
io::{BufRead, BufReader}, io::{BufRead, BufReader},
@ -19,52 +21,61 @@ use tokio::{
time::{sleep, Duration}, time::{sleep, Duration},
}; };
const INIT_NODES: &str = "/host/detee_challenge_nodes"; const INIT_NODES_FILE: &str = "/host/detee_challenge_nodes";
const DISK_PERSISTENCE: &str = "/host/main/TRY_TO_HACK_THIS"; const KEYS_FILE: &str = "/host/TRY_TO_HACK_THIS";
const MAINTAINED_CONNECTIONS: usize = 3; const MAX_CONNECTIONS: usize = 3;
pub async fn localhost_cron(ds: Arc<Store>, tx: Sender<NodeUpdate>) { pub async fn localhost_cron(state: Arc<State>, tx: Sender<NodeUpdate>) {
loop { loop {
sleep(Duration::from_secs(60)).await; sleep(Duration::from_secs(60)).await;
let _ = tx.send((LOCALHOST.to_string(), ds.get_localhost()).into()); let _ = tx.send((LOCALHOST.to_string(), state.get_localhost()).into());
ds.remove_inactive_nodes(); state.remove_inactive_nodes();
} }
} }
async fn get_sol_client() -> SolClient { async fn get_sol_client(state: Arc<State>, ratls_config: RaTlsConfig) -> SolClient {
match crate::persistence::Data::read(DISK_PERSISTENCE).await { match KeysFile::read(KEYS_FILE, &state).await {
Ok(data) => { Ok(keys_file) => {
let (keypair, token) = data.parse(); let sol_client = SolClient::try_from(keys_file).unwrap();
println!("Found the following wallet saved to disk: {}", keypair.pubkey()); println!(
println!("Loading token mint address {}", token); "Found the following wallet saved to disk: {}",
return SolClient::from(keypair, token); sol_client.get_wallet_pubkey()
);
println!("Loading token mint address {}", sol_client.get_token_address());
return sol_client;
} }
Err(e) => println!("Did not find old pubkeys saved to disk: {e}"), Err(e) => println!("Can't initialize using sealed keys: {e}"),
}; };
let input = match File::open(INIT_NODES) { let init_nodes = match File::open(INIT_NODES_FILE) {
Ok(i) => i, Ok(init_nodes) => init_nodes,
Err(_) => { Err(_) => {
println!("Could not find remote nodes in the file {INIT_NODES}"); println!("Can't initialize using init nodes from {INIT_NODES_FILE}");
println!("Starting a new network with a new key..."); println!("Starting a new network with a new key...");
return SolClient::new().await; return SolClient::new().await;
} }
}; };
let buffered = BufReader::new(input);
for line in buffered.lines() { let init_nodes_reader = BufReader::new(init_nodes);
match grpc::client::key_grabber(line.unwrap()).await { for init_node_ip in init_nodes_reader.lines().map(|l| l.unwrap()) {
Ok(bundle) => { match grpc::client::key_grabber(state.clone(), init_node_ip, ratls_config.clone()).await {
Ok(keys) => {
let sol_client = SolClient::try_from(keys.clone())
.map_err(|e| {
println!("Received malformed keys from the network: {e}");
state.increase_net_attacks();
})
.unwrap();
println!( println!(
"Got keypair from the network. Joining the network using wallet {}", "Got keypair from the network. Joining the network using wallet {}",
bundle.0.pubkey() sol_client.get_wallet_pubkey()
); );
println!("The address of the Token is {}", bundle.1); println!("The address of the Token is {}", sol_client.get_token_address());
println!("Saving this data to disk in the file {DISK_PERSISTENCE}"); println!("Saving this data to disk in the file {KEYS_FILE}");
let disk_data = crate::persistence::Data::init_from(&bundle.0, &bundle.1).await; if let Err(e) = sol_client.get_keys_file().write(KEYS_FILE).await {
if let Err(e) = disk_data.write(DISK_PERSISTENCE).await { println!("Could not save data to disk: {e}");
println!("Could not save data to disk due to: {e}");
} }
return SolClient::from(bundle.0, bundle.1); return sol_client;
} }
Err(e) => { Err(e) => {
println!("Could not get keypair: {e:?}"); println!("Could not get keypair: {e:?}");
@ -77,29 +88,42 @@ async fn get_sol_client() -> SolClient {
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("trace")); env_logger::init_from_env(env_logger::Env::default().default_filter_or("trace"));
let ratls_config = RaTlsConfig::new()
.allow_instance_measurement(InstanceMeasurement::new().with_current_mrenclave().unwrap());
let sol_client = get_sol_client().await; let state = Arc::new(State::new());
let ds = Arc::new(Store::init(sol_client)); let sol_client = Arc::new(get_sol_client(state.clone(), ratls_config.clone()).await);
let (tx, _) = broadcast::channel(500); let (tx, _) = broadcast::channel(500);
let mut tasks = JoinSet::new(); let mut tasks = JoinSet::new();
tasks.spawn(localhost_cron(ds.clone(), tx.clone())); tasks.spawn(localhost_cron(state.clone(), tx.clone()));
tasks.spawn(http_server::init(ds.clone())); tasks.spawn(http_server::init(state.clone(), sol_client.clone()));
tasks.spawn(grpc::server::MyServer::init(ds.clone(), tx.clone()).start()); tasks.spawn(
grpc::server::MyServer::init(
state.clone(),
sol_client.get_keys(),
ratls_config.clone(),
tx.clone(),
)
.start(),
);
if let Ok(input) = std::fs::read_to_string(INIT_NODES) { if let Ok(input) = std::fs::read_to_string(INIT_NODES_FILE) {
for line in input.lines() { for line in input.lines() {
tasks.spawn( tasks.spawn(
grpc::client::ConnManager::init(ds.clone(), tx.clone()) grpc::client::ConnManager::init(state.clone(), ratls_config.clone(), tx.clone())
.start_with_node(line.to_string()), .start_with_node(line.to_string()),
); );
} }
} }
for _ in 0..MAINTAINED_CONNECTIONS { for _ in 0..MAX_CONNECTIONS {
tasks.spawn(grpc::client::ConnManager::init(ds.clone(), tx.clone()).start()); tasks.spawn(
grpc::client::ConnManager::init(state.clone(), ratls_config.clone(), tx.clone())
.start(),
);
} }
while let Some(Ok(_)) = tasks.join_next().await {} while let Some(Ok(_)) = tasks.join_next().await {}

@ -1,43 +1,52 @@
use crate::{datastore::State, grpc::challenge::Keys};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use solana_sdk::{pubkey::Pubkey, signature::keypair::Keypair}; use serde_with::base64::Base64;
use std::str::FromStr;
#[serde_with::serde_as]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Data { pub struct KeysFile {
random: String, random: String,
keypair: String, #[serde_as(as = "Base64")]
keypair: Vec<u8>,
token: String, token: String,
} }
impl Data { impl From<Keys> for KeysFile {
pub async fn init_from(keypair: &Keypair, token: &Pubkey) -> Self { fn from(keys: Keys) -> Self {
use rand::{distributions::Alphanumeric, Rng}; use rand::{distributions::Alphanumeric, Rng};
let random_string: String = let random: String =
rand::thread_rng().sample_iter(&Alphanumeric).take(128).map(char::from).collect(); rand::thread_rng().sample_iter(&Alphanumeric).take(128).map(char::from).collect();
Self { Self { keypair: keys.keypair, token: keys.token_address, random }
random: random_string,
keypair: keypair.to_base58_string(),
token: token.to_string(),
} }
} }
impl Into<Keys> for KeysFile {
fn into(self) -> Keys {
Keys { keypair: self.keypair, token_address: self.token }
}
}
impl KeysFile {
pub async fn write(self, path: &str) -> Result<(), Box<dyn std::error::Error>> { pub async fn write(self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
let serialized = serde_json::to_string(&self)?; let serialized = serde_json::to_string(&self)?;
let sealed = detee_sgx::SealingConfig::new()?.seal_data(serialized.into_bytes())?; let sealed = detee_sgx::SealingConfig::new()?.seal_data(serialized.into_bytes())?;
tokio::fs::write(path, sealed).await.map_err(Into::into) tokio::fs::write(path, sealed).await.map_err(Into::into)
} }
pub async fn read(path: &str) -> Result<Self, Box<dyn std::error::Error>> { pub async fn read(path: &str, state: &State) -> Result<Self, Box<dyn std::error::Error>> {
let sealed = tokio::fs::read(path).await?; let sealed = tokio::fs::read(path).await?;
let serialized = detee_sgx::SealingConfig::new()?.un_seal_data(sealed)?; let serialized = detee_sgx::SealingConfig::new()?.un_seal_data(sealed).map_err(|e| {
Ok(serde_json::from_str(&String::from_utf8(serialized)?)?) match e {
detee_sgx::SgxError::UnSealingError(ref ue) => {
state.increase_disk_attacks();
println!("The disk data is corrupted: {ue}");
} }
_ => println!("Failed to unseal data: {e}"),
pub fn parse(self) -> (Keypair, Pubkey) { };
let keypair = Keypair::from_base58_string(&self.keypair); e
let pubkey = Pubkey::from_str(&self.token).unwrap(); })?;
(keypair, pubkey) Ok(serde_json::from_str(&String::from_utf8(serialized)?)?)
} }
} }
@ -48,9 +57,7 @@ pub struct Logfile {}
impl Logfile { impl Logfile {
pub fn append(msg: &str) -> Result<(), Box<dyn std::error::Error>> { pub fn append(msg: &str) -> Result<(), Box<dyn std::error::Error>> {
use std::io::Write; use std::io::Write;
let mut file = std::fs::OpenOptions::new() let mut file = std::fs::OpenOptions::new().append(true).open(LOG_PATH)?;
.append(true)
.open(LOG_PATH)?;
file.write_all(msg.as_bytes())?; file.write_all(msg.as_bytes())?;
Ok(()) Ok(())
} }

@ -1,4 +1,5 @@
#![allow(dead_code)] #![allow(dead_code)]
use crate::{grpc::challenge::Keys, persistence::KeysFile};
use solana_client::rpc_client::RpcClient; use solana_client::rpc_client::RpcClient;
use solana_program::program_pack::Pack; use solana_program::program_pack::Pack;
use solana_sdk::{ use solana_sdk::{
@ -12,18 +13,17 @@ use spl_token::{
instruction::{initialize_mint, mint_to}, instruction::{initialize_mint, mint_to},
state::Mint, state::Mint,
}; };
use std::error::Error;
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
const RPC_URL: &str = "https://api.devnet.solana.com"; const RPC_URL: &str = "https://api.devnet.solana.com";
pub struct Client { pub struct SolClient {
client: RpcClient, client: RpcClient,
keypair: Keypair, keypair: Keypair,
token: Pubkey, token: Pubkey,
} }
impl Client { impl SolClient {
pub async fn new() -> Self { pub async fn new() -> Self {
let client = RpcClient::new(RPC_URL); let client = RpcClient::new(RPC_URL);
let keypair = Keypair::new(); let keypair = Keypair::new();
@ -31,12 +31,18 @@ impl Client {
Self { client, keypair, token } Self { client, keypair, token }
} }
pub fn from(keypair: Keypair, token: Pubkey) -> Self { pub fn get_keys(&self) -> Keys {
Self { client: RpcClient::new(RPC_URL), keypair, token } Keys { keypair: self.get_keypair_bytes(), token_address: self.get_token_address() }
} }
pub fn mint(&self, recipient: &Pubkey) -> Result<String, Box<dyn std::error::Error>> { pub fn get_keys_file(&self) -> KeysFile {
let associated_token_address = self.create_token_account(recipient)?; self.get_keys().into()
}
pub fn mint(&self, recipient: &str) -> Result<String, Box<dyn std::error::Error>> {
use std::str::FromStr;
let recipient = solana_sdk::pubkey::Pubkey::from_str(recipient)?;
let associated_token_address = self.create_token_account(&recipient)?;
let mint_to_instruction = mint_to( let mint_to_instruction = mint_to(
&spl_token::id(), &spl_token::id(),
&self.token, &self.token,
@ -56,7 +62,10 @@ impl Client {
Ok(signature.to_string()) Ok(signature.to_string())
} }
fn create_token_account(&self, recipient: &Pubkey) -> Result<Pubkey, Box<dyn Error>> { fn create_token_account(
&self,
recipient: &Pubkey,
) -> Result<Pubkey, Box<dyn std::error::Error>> {
let address = get_associated_token_address(recipient, &self.token); let address = get_associated_token_address(recipient, &self.token);
if self.client.get_account(&address).is_err() { if self.client.get_account(&address).is_err() {
let create_token_account_instruction = create_associated_token_account( let create_token_account_instruction = create_associated_token_account(
@ -77,11 +86,12 @@ impl Client {
Ok(address) Ok(address)
} }
pub fn wallet_address(&self) -> String { pub fn get_wallet_pubkey(&self) -> String {
// Return the base58 string representation of the public key
self.keypair.pubkey().to_string() self.keypair.pubkey().to_string()
} }
pub fn token_address(&self) -> String { pub fn get_token_address(&self) -> String {
self.token.to_string() self.token.to_string()
} }
@ -90,6 +100,30 @@ impl Client {
} }
} }
impl TryFrom<Keys> for SolClient {
type Error = String;
fn try_from(keys: Keys) -> Result<Self, Self::Error> {
use std::str::FromStr;
let keypair = match Keypair::from_bytes(&keys.keypair) {
Ok(k) => k,
Err(_) => return Err("Could not parse keypair.".into()),
};
let token = Pubkey::from_str(&keys.token_address)
.map_err(|_| "Could not parse wallet address.".to_string())?;
Ok(Self { client: RpcClient::new(RPC_URL), keypair, token })
}
}
impl TryFrom<KeysFile> for SolClient {
type Error = String;
fn try_from(keys_file: KeysFile) -> Result<Self, Self::Error> {
let keys: Keys = keys_file.into();
Self::try_from(keys)
}
}
async fn wait_for_sol(client: &RpcClient, pubkey: &Pubkey) { async fn wait_for_sol(client: &RpcClient, pubkey: &Pubkey) {
println!("Waiting to receive 0.01 SOL in address {pubkey}"); println!("Waiting to receive 0.01 SOL in address {pubkey}");
loop { loop {