diff --git a/Cargo.toml b/Cargo.toml index 018b3572..491ea7d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ categories = ["algorithms", "database-implementations", "no-std"] edition = "2021" [workspace] -members = ["proto", "harness", "datadriven"] +members = ["proto", "harness", "datadriven", "trusted/pal", "trusted/platform", "trusted/model", "trusted/example"] [features] default = ["protobuf-codec", "default-logger", "std"] diff --git a/trusted/example/Cargo.toml b/trusted/example/Cargo.toml new file mode 100644 index 00000000..ee87d26a --- /dev/null +++ b/trusted/example/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "trusted_example" +version = "0.1.0" +authors = ["people"] + +[dependencies] +trusted_pal = { path = "../pal" } # no_std +trusted_model = { path = "../model" } # no_std +trusted_platform = { path = "../platform" } # std for example implementation +prost = { version = "0.11", default-features = false, features = ["prost-derive"] } +hashbrown = { version = "0.14.0"} + +[build-dependencies] +prost-build = { version = "0.11" } + +[[bin]] +name = "trusted-example" # std for example \ No newline at end of file diff --git a/trusted/example/README.md b/trusted/example/README.md new file mode 100644 index 00000000..579a7b14 --- /dev/null +++ b/trusted/example/README.md @@ -0,0 +1 @@ +# Trusted Application Example \ No newline at end of file diff --git a/trusted/example/build.rs b/trusted/example/build.rs new file mode 100644 index 00000000..f092b967 --- /dev/null +++ b/trusted/example/build.rs @@ -0,0 +1,8 @@ +use std::io::Result; + +fn main() -> Result<()> { + let mut prost_build = prost_build::Config::new(); + prost_build.btree_map(["."]); + prost_build.compile_protos(&["proto/counter.proto"], &["proto/"])?; + Ok(()) +} diff --git a/trusted/example/proto/counter.proto b/trusted/example/proto/counter.proto new file mode 100644 index 00000000..a52506b5 --- /dev/null +++ b/trusted/example/proto/counter.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; +package counter; + +message CounterRequest { + uint64 id = 1; + string name = 2; + oneof op { + CounterCompareAndSwapRequest compare_and_swap = 3; + } +} + +message CounterCompareAndSwapRequest { + int64 expected_value = 1; + int64 new_value = 2; +} + +message CounterResponse { + uint64 id = 1; + CounterStatus status = 2; + oneof op { + CounterCompareAndSwapResponse compare_and_swap = 3; + } +} + +enum CounterStatus { + UNSPECIFIED = 0; + SUCCESS = 1; + NOT_LEADER_ERROR = 2; + INVALID_ARGUMENT_ERROR = 3; +} + +message CounterCompareAndSwapResponse { + int64 old_value = 1; + int64 new_value = 2; +} + +message CounterConfig { + map initial_values = 1; +} + +message CounterSnapshot { + map values = 1; +} diff --git a/trusted/example/src/main.rs b/trusted/example/src/main.rs new file mode 100644 index 00000000..72e9fffa --- /dev/null +++ b/trusted/example/src/main.rs @@ -0,0 +1,156 @@ +// extern crate anyhow; +extern crate alloc; +extern crate core; +extern crate hashbrown; +extern crate prost; +extern crate trusted_model; + +pub mod counter { + include!(concat!(env!("OUT_DIR"), "/counter.rs")); +} + +use crate::counter::{ + counter_request, counter_response, CounterCompareAndSwapRequest, CounterCompareAndSwapResponse, + CounterConfig, CounterRequest, CounterResponse, CounterSnapshot, CounterStatus, +}; +use alloc::collections::BTreeMap; +use alloc::string::String; +use hashbrown::HashMap; +use prost::Message; +use trusted_model::model::{Actor, ActorContext, ActorError}; + +struct CounterActor { + context: Option>, + values: HashMap, +} + +impl CounterActor { + pub fn new() -> Self { + CounterActor { + context: None, + values: HashMap::new(), + } + } + + fn get_context(&mut self) -> &mut dyn ActorContext { + self.context.as_mut().expect("context").as_mut() + } + + fn send_message(&mut self, message: &M) { + self.get_context() + .send_message(message.encode_to_vec().as_ref()) + } + + fn apply_compare_and_swap( + &mut self, + id: u64, + counter_name: &String, + compare_and_swap_request: &CounterCompareAndSwapRequest, + ) -> CounterResponse { + let mut response = CounterResponse { + id, + status: CounterStatus::Unspecified.into(), + op: None, + }; + let mut compare_and_swap_response = CounterCompareAndSwapResponse { + ..Default::default() + }; + + let existing_value_ref = self.values.entry_ref(counter_name); + let existing_value = existing_value_ref.or_insert(0); + compare_and_swap_response.old_value = *existing_value; + if *existing_value == compare_and_swap_request.expected_value { + *existing_value = compare_and_swap_request.new_value; + + response.status = CounterStatus::Success.into(); + compare_and_swap_response.new_value = compare_and_swap_request.new_value; + } else { + response.status = CounterStatus::InvalidArgumentError.into(); + } + response.op = Some(counter_response::Op::CompareAndSwap( + compare_and_swap_response, + )); + + response + } +} + +impl Actor for CounterActor { + fn on_init(&mut self, context: Box) -> Result<(), ActorError> { + self.context = Some(context); + self.values = HashMap::new(); + let config = CounterConfig::decode(self.get_context().get_config().as_ref()) + .map_err(|_e| ActorError::Decoding)?; + for (counter_name, counter_value) in config.initial_values { + self.values.insert(counter_name, counter_value); + } + + Ok(()) + } + + fn on_shutdown(&mut self) {} + + fn on_save_snapshot(&mut self) -> Result, ActorError> { + let mut snapshot = CounterSnapshot { + values: BTreeMap::new(), + }; + for (counter_name, counter_value) in &self.values { + snapshot + .values + .insert(counter_name.to_string(), *counter_value); + } + + Ok(snapshot.encode_to_vec()) + } + + fn on_load_snapshot(&mut self, snapshot: &[u8]) -> Result<(), ActorError> { + let snapshot = CounterSnapshot::decode(snapshot).map_err(|_e| ActorError::Decoding)?; + for (counter_name, counter_value) in snapshot.values { + self.values.insert(counter_name, counter_value); + } + + Ok(()) + } + + fn on_process_command(&mut self, command: &[u8]) -> Result<(), ActorError> { + let request = CounterRequest::decode(command).map_err(|_e| ActorError::Decoding)?; + let mut response = CounterResponse { + id: request.id, + ..Default::default() + }; + let mut status = CounterStatus::Success; + if request.op.is_none() { + status = CounterStatus::InvalidArgumentError; + } + if !self.get_context().is_leader() { + status = CounterStatus::NotLeaderError; + } + + if let CounterStatus::Success = status { + self.get_context().propose_event(command)?; + } else { + response.status = status.into(); + self.send_message(&response); + } + + Ok(()) + } + + fn on_apply_event(&mut self, _index: u64, event: &[u8]) -> Result<(), ActorError> { + let request = CounterRequest::decode(event).map_err(|_e| ActorError::Decoding)?; + let op = request.op.unwrap(); + + let response = match op { + counter_request::Op::CompareAndSwap(ref compare_and_swap_request) => { + self.apply_compare_and_swap(request.id, &request.name, compare_and_swap_request) + } + }; + self.send_message(&response); + + Ok(()) + } +} + +fn main() { + let _acounter_actor = CounterActor::new(); +} diff --git a/trusted/model/Cargo.toml b/trusted/model/Cargo.toml new file mode 100644 index 00000000..4521fa31 --- /dev/null +++ b/trusted/model/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "trusted_model" +version = "0.1.0" +authors = ["people"] + +[features] +default = ["std"] +std = [] + +[dependencies] +trusted_pal = { path = "../pal" } +raft = { path = "../../"} +prost = { version = "0.11", default-features = false, features = ["prost-derive"] } \ No newline at end of file diff --git a/trusted/model/README.md b/trusted/model/README.md new file mode 100644 index 00000000..d5c7195f --- /dev/null +++ b/trusted/model/README.md @@ -0,0 +1 @@ +# Trusted Computations Programming Model \ No newline at end of file diff --git a/trusted/model/src/lib.rs b/trusted/model/src/lib.rs new file mode 100644 index 00000000..5c0d656d --- /dev/null +++ b/trusted/model/src/lib.rs @@ -0,0 +1,12 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(error_in_core))] + +extern crate alloc; +extern crate core; + +pub mod model; + +#[cfg(not(feature = "std"))] +use core::error::Error as StdError; +#[cfg(feature = "std")] +use std::error::Error as StdError; diff --git a/trusted/model/src/model.rs b/trusted/model/src/model.rs new file mode 100644 index 00000000..c8fec081 --- /dev/null +++ b/trusted/model/src/model.rs @@ -0,0 +1,55 @@ +use crate::StdError; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::fmt; +use core::option::Option; +use core::result::Result; + +#[derive(Debug)] +pub enum ActorError { + Decoding, + Internal, +} + +impl StdError for ActorError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + None + } +} + +impl fmt::Display for ActorError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ActorError::Decoding => write!(f, "Failed to decode"), + ActorError::Internal => write!(f, "Intern error"), + } + } +} + +pub trait ActorContext { + fn get_instant(&self) -> u64; + + fn get_config(&self) -> Vec; + + fn is_leader(&self) -> bool; + + fn propose_event(&mut self, event: &[u8]) -> Result<(), ActorError>; + + fn send_message(&mut self, message: &[u8]); + + fn log_entry(&mut self, entry: &[u8]); +} + +pub trait Actor { + fn on_init(&mut self, context: Box) -> Result<(), ActorError>; + + fn on_shutdown(&mut self); + + fn on_save_snapshot(&mut self) -> Result, ActorError>; + + fn on_load_snapshot(&mut self, snapshot: &[u8]) -> Result<(), ActorError>; + + fn on_process_command(&mut self, command: &[u8]) -> Result<(), ActorError>; + + fn on_apply_event(&mut self, index: u64, event: &[u8]) -> Result<(), ActorError>; +} diff --git a/trusted/pal/Cargo.toml b/trusted/pal/Cargo.toml index 88fdbe77..b0faa73a 100644 --- a/trusted/pal/Cargo.toml +++ b/trusted/pal/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "trusted-pal" +name = "trusted_pal" version = "0.1.0" authors = ["people"] diff --git a/trusted/platform/Cargo.toml b/trusted/platform/Cargo.toml new file mode 100644 index 00000000..27b2fca6 --- /dev/null +++ b/trusted/platform/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "trusted_platform" +version = "0.1.0" +authors = ["people"] + +[dependencies] +trusted_pal = { path = "../pal" } \ No newline at end of file diff --git a/trusted/platform/README.md b/trusted/platform/README.md new file mode 100644 index 00000000..1d95818b --- /dev/null +++ b/trusted/platform/README.md @@ -0,0 +1 @@ +# Platform Implementation \ No newline at end of file diff --git a/trusted/platform/src/lib.rs b/trusted/platform/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/trusted/platform/src/lib.rs @@ -0,0 +1 @@ +