use crate::adapters::{
ConsoleHandlerAdapter, CryptoHandlerAdapter, LoggingSystemHandlerAdapter, RandomHandlerAdapter,
StorageHandlerAdapter, TimeHandlerAdapter, TraceHandlerAdapter,
};
use async_trait::async_trait;
use aura_core::{
hash, AuthorityId, ContextId, ContextSnapshot, EffectType, ExecutionMode, OperationSessionId,
SessionId,
};
use aura_effects::{
console::RealConsoleHandler, crypto::RealCryptoHandler, random::RealRandomHandler,
storage::FilesystemStorageHandler, system::logging::LoggingSystemHandler,
time::PhysicalTimeHandler, trace::TraceHandler,
};
use aura_mpst::LocalSessionType;
use cfg_if::cfg_if;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
use uuid::Uuid;
cfg_if! {
if #[cfg(not(target_arch = "wasm32"))] {
use crate::adapters::TransportHandlerAdapter;
use aura_effects::TcpTransportHandler as RealTransportHandler;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EffectId(pub EffectType);
impl From<EffectType> for EffectId {
fn from(value: EffectType) -> Self {
Self(value)
}
}
impl From<EffectId> for EffectType {
fn from(value: EffectId) -> Self {
value.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CapabilityId(String);
impl CapabilityId {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for CapabilityId {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
impl From<String> for CapabilityId {
fn from(value: String) -> Self {
Self(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MetadataKey {
Effect(EffectId),
Capability(CapabilityId),
}
#[derive(Debug, Clone)]
pub struct HandlerContext {
pub authority_id: AuthorityId,
pub context_id: ContextId,
pub execution_mode: ExecutionMode,
pub session_id: OperationSessionId,
pub operation_id: Uuid,
pub metadata: HashMap<MetadataKey, String>,
}
impl HandlerContext {
fn deterministic_operation_id(
authority_id: AuthorityId,
context_id: ContextId,
execution_mode: ExecutionMode,
) -> Uuid {
let mut seed = Vec::with_capacity(33);
seed.extend_from_slice(&authority_id.to_bytes());
seed.extend_from_slice(&context_id.to_bytes());
match execution_mode {
ExecutionMode::Testing => seed.push(0),
ExecutionMode::Production => seed.push(1),
ExecutionMode::Simulation { seed: sim_seed } => {
seed.push(2);
seed.extend_from_slice(&sim_seed.to_le_bytes());
}
}
let digest = hash::hash(&seed);
let mut op_bytes = [0u8; 16];
op_bytes.copy_from_slice(&digest[..16]);
Uuid::from_bytes(op_bytes)
}
fn deterministic_session_id(
authority_id: AuthorityId,
context_id: ContextId,
execution_mode: ExecutionMode,
) -> OperationSessionId {
let mut seed = Vec::with_capacity(41);
seed.extend_from_slice(b"aura-session");
seed.extend_from_slice(&authority_id.to_bytes());
seed.extend_from_slice(&context_id.to_bytes());
match execution_mode {
ExecutionMode::Testing => seed.push(0),
ExecutionMode::Production => seed.push(1),
ExecutionMode::Simulation { seed: sim_seed } => {
seed.push(2);
seed.extend_from_slice(&sim_seed.to_le_bytes());
}
}
OperationSessionId::new(SessionId::new_from_entropy(hash::hash(&seed)))
}
pub fn new(
authority_id: AuthorityId,
context_id: ContextId,
execution_mode: ExecutionMode,
) -> Self {
let operation_id =
Self::deterministic_operation_id(authority_id, context_id, execution_mode);
Self {
authority_id,
context_id,
execution_mode,
session_id: Self::deterministic_session_id(authority_id, context_id, execution_mode),
operation_id,
metadata: HashMap::new(),
}
}
pub fn from_snapshot(snapshot: ContextSnapshot) -> Self {
let authority_id = snapshot.authority_id();
let context_id = snapshot.context_id();
let execution_mode = snapshot.execution_mode();
let operation_id =
Self::deterministic_operation_id(authority_id, context_id, execution_mode);
Self {
authority_id,
context_id,
execution_mode,
session_id: snapshot.session_id(),
operation_id,
metadata: HashMap::new(),
}
}
pub fn with_session_id(mut self, session_id: OperationSessionId) -> Self {
self.session_id = session_id;
self
}
pub fn with_metadata(mut self, key: MetadataKey, value: String) -> Self {
self.metadata.insert(key, value);
self
}
pub fn with_effect_metadata(mut self, effect: EffectType, value: String) -> Self {
self.metadata
.insert(MetadataKey::Effect(effect.into()), value);
self
}
pub fn with_capability_metadata(
mut self,
capability: impl Into<CapabilityId>,
value: String,
) -> Self {
self.metadata
.insert(MetadataKey::Capability(capability.into()), value);
self
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RegisterAllOptions {
pub allow_impure: bool,
}
impl RegisterAllOptions {
pub fn allow_impure() -> Self {
Self { allow_impure: true }
}
}
#[derive(Debug, Error)]
pub enum HandlerError {
#[error("Effect {effect_type:?} not supported")]
UnsupportedEffect { effect_type: EffectType },
#[error("Operation '{operation}' not found in effect {effect_type:?}")]
UnknownOperation {
effect_type: EffectType,
operation: String,
},
#[error("Failed to serialize parameters for {effect_type:?}.{operation}")]
EffectSerialization {
effect_type: EffectType,
operation: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Failed to deserialize parameters for {effect_type:?}.{operation}")]
EffectDeserialization {
effect_type: EffectType,
operation: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Session type execution failed")]
SessionExecution {
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Context operation failed: {message}")]
ContextError { message: String },
#[error("Registry operation failed")]
RegistryError {
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Effect execution failed")]
ExecutionFailed {
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait Handler: Send + Sync {
async fn execute_effect(
&self,
effect_type: EffectType,
operation: &str,
parameters: &[u8],
ctx: &HandlerContext,
) -> Result<Vec<u8>, HandlerError>;
async fn execute_session(
&self,
session: LocalSessionType,
ctx: &HandlerContext,
) -> Result<(), HandlerError>;
fn supports_effect(&self, effect_type: EffectType) -> bool;
fn execution_mode(&self) -> ExecutionMode;
fn supported_effects(&self) -> Vec<EffectType> {
EffectType::all()
.into_iter()
.filter(|&effect_type| self.supports_effect(effect_type))
.collect()
}
}
#[derive(Debug, Error)]
pub enum RegistryError {
#[error("Effect type {effect_type:?} not registered")]
EffectTypeNotRegistered { effect_type: EffectType },
#[error("Operation '{operation}' not supported by handler for {effect_type:?}")]
OperationNotSupported {
effect_type: EffectType,
operation: String,
},
#[error("Failed to register handler for {effect_type:?}")]
RegistrationFailed {
effect_type: EffectType,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Handler execution failed for {effect_type:?}")]
HandlerExecutionFailed {
effect_type: EffectType,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Failed to deserialize result from {effect_type:?} operation '{operation}'")]
ParameterDeserialization {
effect_type: EffectType,
operation: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
}
impl RegistryError {
pub fn registration_failed(
effect_type: EffectType,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::RegistrationFailed {
effect_type,
source: Box::new(source),
}
}
pub fn handler_execution_failed(
effect_type: EffectType,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::HandlerExecutionFailed {
effect_type,
source: Box::new(source),
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait RegistrableHandler: Send + Sync {
async fn execute_operation_bytes(
&self,
effect_type: EffectType,
operation: &str,
parameters: &[u8],
ctx: &HandlerContext,
) -> Result<Vec<u8>, HandlerError>;
fn supported_operations(&self, effect_type: EffectType) -> Vec<String>;
fn supports_operation(&self, effect_type: EffectType, operation: &str) -> bool {
self.supported_operations(effect_type)
.contains(&operation.to_string())
}
fn supports_effect(&self, effect_type: EffectType) -> bool;
fn execution_mode(&self) -> ExecutionMode;
}
pub struct EffectRegistry {
handlers: HashMap<EffectType, Box<dyn RegistrableHandler>>,
default_execution_mode: ExecutionMode,
}
impl std::fmt::Debug for EffectRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EffectRegistry")
.field(
"handlers",
&format!("HashMap with {} entries", self.handlers.len()),
)
.field("default_execution_mode", &self.default_execution_mode)
.finish()
}
}
impl EffectRegistry {
pub fn new(default_execution_mode: ExecutionMode) -> Self {
Self {
handlers: HashMap::new(),
default_execution_mode,
}
}
pub fn register_handler(
&mut self,
effect_type: EffectType,
handler: Box<dyn RegistrableHandler>,
) -> Result<(), RegistryError> {
if !handler.supports_effect(effect_type) {
return Err(RegistryError::registration_failed(
effect_type,
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Handler does not support the specified effect type",
),
));
}
self.handlers.insert(effect_type, handler);
Ok(())
}
pub fn register_all(&mut self, options: RegisterAllOptions) -> Result<(), RegistryError> {
if !options.allow_impure {
return Err(RegistryError::registration_failed(
EffectType::System,
std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"register_all requires allow_impure=true for OS-backed handlers",
),
));
}
self.register_handler(
EffectType::Console,
Box::new(ConsoleHandlerAdapter::new(RealConsoleHandler::new())),
)?;
self.register_handler(
EffectType::Random,
Box::new(RandomHandlerAdapter::new(RealRandomHandler::new())),
)?;
self.register_handler(
EffectType::Crypto,
Box::new(CryptoHandlerAdapter::new(RealCryptoHandler::new())),
)?;
self.register_handler(
EffectType::Storage,
Box::new(StorageHandlerAdapter::new(
FilesystemStorageHandler::with_default_path(),
)),
)?;
self.register_handler(
EffectType::Time,
Box::new(TimeHandlerAdapter::new(PhysicalTimeHandler::new())),
)?;
cfg_if! {
if #[cfg(not(target_arch = "wasm32"))] {
self.register_handler(
EffectType::Network,
Box::new(TransportHandlerAdapter::new(RealTransportHandler::default())),
)?;
}
}
self.register_handler(
EffectType::Trace,
Box::new(TraceHandlerAdapter::new(TraceHandler::new())),
)?;
self.register_handler(
EffectType::System,
Box::new(LoggingSystemHandlerAdapter::new(
LoggingSystemHandler::default(),
)),
)?;
Ok(())
}
pub fn unregister_handler(
&mut self,
effect_type: EffectType,
) -> Option<Box<dyn RegistrableHandler>> {
self.handlers.remove(&effect_type)
}
pub fn is_registered(&self, effect_type: EffectType) -> bool {
self.handlers.contains_key(&effect_type)
}
pub fn registered_effect_types(&self) -> Vec<EffectType> {
self.handlers.keys().copied().collect()
}
pub fn handlers_len(&self) -> usize {
self.handlers.len()
}
pub fn supported_operations(
&self,
effect_type: EffectType,
) -> Result<Vec<String>, RegistryError> {
match self.handlers.get(&effect_type) {
Some(handler) => Ok(handler.supported_operations(effect_type)),
None => Err(RegistryError::EffectTypeNotRegistered { effect_type }),
}
}
pub fn supports_operation(&self, effect_type: EffectType, operation: &str) -> bool {
self.handlers
.get(&effect_type)
.map(|h| h.supports_operation(effect_type, operation))
.unwrap_or(false)
}
pub async fn execute_session(
&mut self,
_session: LocalSessionType,
_ctx: &mut HandlerContext,
) -> Result<(), RegistryError> {
Err(RegistryError::OperationNotSupported {
effect_type: EffectType::Choreographic,
operation: "execute_session".to_string(),
})
}
pub fn execution_mode(&self) -> ExecutionMode {
self.default_execution_mode
}
pub fn capability_summary(&self) -> RegistryCapabilities {
let mut capabilities = RegistryCapabilities {
registered_effects: Vec::new(),
total_operations: 0,
execution_modes: Vec::new(),
};
for (effect_type, handler) in &self.handlers {
let operations = handler.supported_operations(*effect_type);
let operation_count = operations.len();
capabilities.registered_effects.push(EffectCapability {
effect_type: *effect_type,
operation_count,
operations,
});
capabilities.total_operations += operation_count;
let mode = handler.execution_mode();
if !capabilities.execution_modes.contains(&mode) {
capabilities.execution_modes.push(mode);
}
}
capabilities
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl Handler for EffectRegistry {
async fn execute_effect(
&self,
effect_type: EffectType,
operation: &str,
parameters: &[u8],
ctx: &HandlerContext,
) -> Result<Vec<u8>, HandlerError> {
if let Some(handler) = self.handlers.get(&effect_type) {
handler
.execute_operation_bytes(effect_type, operation, parameters, ctx)
.await
} else {
Err(HandlerError::UnsupportedEffect { effect_type })
}
}
async fn execute_session(
&self,
_session: LocalSessionType,
_ctx: &HandlerContext,
) -> Result<(), HandlerError> {
let err = std::io::Error::other("session execution not wired in registry");
Err(HandlerError::SessionExecution {
source: Box::new(err),
})
}
fn supports_effect(&self, effect_type: EffectType) -> bool {
self.is_registered(effect_type)
}
fn execution_mode(&self) -> ExecutionMode {
self.default_execution_mode
}
}
#[derive(Debug, Clone)]
pub struct RegistryCapabilities {
pub registered_effects: Vec<EffectCapability>,
pub total_operations: usize, pub execution_modes: Vec<ExecutionMode>,
}
#[derive(Debug, Clone)]
pub struct EffectCapability {
pub effect_type: EffectType,
pub operation_count: usize, pub operations: Vec<String>,
}
impl RegistryCapabilities {
pub fn has_effect_type(&self, effect_type: EffectType) -> bool {
self.registered_effects
.iter()
.any(|cap| cap.effect_type == effect_type)
}
pub fn get_effect_capability(&self, effect_type: EffectType) -> Option<&EffectCapability> {
self.registered_effects
.iter()
.find(|cap| cap.effect_type == effect_type)
}
pub fn supports_operation(&self, effect_type: EffectType, operation: &str) -> bool {
self.get_effect_capability(effect_type)
.map(|cap| cap.operations.contains(&operation.to_string()))
.unwrap_or(false)
}
pub fn effect_type_count(&self) -> usize {
self.registered_effects.len()
}
pub fn supports_execution_mode(&self, mode: ExecutionMode) -> bool {
self.execution_modes.contains(&mode)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockRegistrableHandler {
effect_type: EffectType,
operations: Vec<String>,
execution_mode: ExecutionMode,
}
impl MockRegistrableHandler {
fn new(
effect_type: EffectType,
operations: Vec<&str>,
execution_mode: ExecutionMode,
) -> Self {
Self {
effect_type,
operations: operations.into_iter().map(|s| s.to_string()).collect(),
execution_mode,
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl Handler for MockRegistrableHandler {
async fn execute_effect(
&self,
effect_type: EffectType,
operation: &str,
_parameters: &[u8],
_ctx: &HandlerContext,
) -> Result<Vec<u8>, HandlerError> {
if self.effect_type == effect_type && self.operations.contains(&operation.to_string()) {
aura_core::util::serialization::to_vec(&serde_json::Value::String(
"mock_result".to_string(),
))
.map_err(|e| HandlerError::EffectSerialization {
effect_type,
operation: operation.to_string(),
source: e.into(),
})
} else {
Err(HandlerError::UnsupportedEffect { effect_type })
}
}
async fn execute_session(
&self,
_session: LocalSessionType,
_ctx: &HandlerContext,
) -> Result<(), HandlerError> {
Ok(())
}
fn supports_effect(&self, effect_type: EffectType) -> bool {
self.effect_type == effect_type
}
fn execution_mode(&self) -> ExecutionMode {
self.execution_mode
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl RegistrableHandler for MockRegistrableHandler {
async fn execute_operation_bytes(
&self,
_effect_type: EffectType,
operation: &str,
_parameters: &[u8],
_ctx: &HandlerContext,
) -> Result<Vec<u8>, HandlerError> {
if self.operations.contains(&operation.to_string()) {
let mock_result = serde_json::Value::String("mock_result".to_string());
aura_core::util::serialization::to_vec(&mock_result).map_err(|e| {
HandlerError::EffectSerialization {
effect_type: self.effect_type,
operation: operation.to_string(),
source: e.into(),
}
})
} else {
Err(HandlerError::UnknownOperation {
effect_type: self.effect_type,
operation: operation.to_string(),
})
}
}
fn supported_operations(&self, effect_type: EffectType) -> Vec<String> {
if self.effect_type == effect_type {
self.operations.clone()
} else {
Vec::new()
}
}
fn supports_effect(&self, effect_type: EffectType) -> bool {
self.effect_type == effect_type
}
fn execution_mode(&self) -> ExecutionMode {
self.execution_mode
}
}
#[test]
fn test_registry_creation() {
let registry = EffectRegistry::new(ExecutionMode::Testing);
assert_eq!(registry.execution_mode(), ExecutionMode::Testing);
assert!(registry.registered_effect_types().is_empty());
}
#[test]
fn test_handler_registration() {
let mut registry = EffectRegistry::new(ExecutionMode::Testing);
let handler = Box::new(MockRegistrableHandler::new(
EffectType::Crypto,
vec!["hash", "sign", "verify"],
ExecutionMode::Testing,
));
registry
.register_handler(EffectType::Crypto, handler)
.unwrap();
assert!(registry.is_registered(EffectType::Crypto));
assert!(!registry.is_registered(EffectType::Network));
let effect_types = registry.registered_effect_types();
assert_eq!(effect_types.len(), 1);
assert!(effect_types.contains(&EffectType::Crypto));
}
#[test]
fn test_operation_support() {
let mut registry = EffectRegistry::new(ExecutionMode::Testing);
let handler = Box::new(MockRegistrableHandler::new(
EffectType::Crypto,
vec!["hash", "sign", "verify"],
ExecutionMode::Testing,
));
registry
.register_handler(EffectType::Crypto, handler)
.unwrap();
let operations = registry.supported_operations(EffectType::Crypto).unwrap();
assert_eq!(operations.len(), 3);
assert!(operations.contains(&"hash".to_string()));
assert!(operations.contains(&"sign".to_string()));
assert!(operations.contains(&"verify".to_string()));
assert!(registry.supports_operation(EffectType::Crypto, "hash"));
assert!(registry.supports_operation(EffectType::Crypto, "sign"));
assert!(!registry.supports_operation(EffectType::Crypto, "encrypt"));
assert!(!registry.supports_operation(EffectType::Network, "send"));
}
#[test]
fn test_capability_summary() {
let mut registry = EffectRegistry::new(ExecutionMode::Testing);
let crypto_handler = Box::new(MockRegistrableHandler::new(
EffectType::Crypto,
vec!["hash", "sign"],
ExecutionMode::Testing,
));
let network_handler = Box::new(MockRegistrableHandler::new(
EffectType::Network,
vec!["send", "receive", "broadcast"],
ExecutionMode::Production,
));
registry
.register_handler(EffectType::Crypto, crypto_handler)
.unwrap();
registry
.register_handler(EffectType::Network, network_handler)
.unwrap();
let capabilities = registry.capability_summary();
assert_eq!(capabilities.effect_type_count(), 2);
assert_eq!(capabilities.total_operations, 5); assert!(capabilities.has_effect_type(EffectType::Crypto));
assert!(capabilities.has_effect_type(EffectType::Network));
assert!(!capabilities.has_effect_type(EffectType::Storage));
assert!(capabilities.supports_operation(EffectType::Crypto, "hash"));
assert!(capabilities.supports_operation(EffectType::Network, "broadcast"));
assert!(!capabilities.supports_operation(EffectType::Crypto, "encrypt"));
assert!(capabilities.supports_execution_mode(ExecutionMode::Testing));
assert!(capabilities.supports_execution_mode(ExecutionMode::Production));
assert!(!capabilities.supports_execution_mode(ExecutionMode::Simulation { seed: 42 }));
}
#[test]
fn test_handler_context_operation_id_deterministic() {
let authority_id = AuthorityId::new_from_entropy([1u8; 32]);
let context_id = ContextId::new_from_entropy([2u8; 32]);
let ctx1 = HandlerContext::new(authority_id, context_id, ExecutionMode::Testing);
let ctx2 = HandlerContext::new(authority_id, context_id, ExecutionMode::Testing);
assert_eq!(ctx1.operation_id, ctx2.operation_id);
let different_authority = AuthorityId::new_from_entropy([3u8; 32]);
let ctx3 = HandlerContext::new(different_authority, context_id, ExecutionMode::Testing);
assert_ne!(ctx1.operation_id, ctx3.operation_id);
let ctx4 = HandlerContext::new(authority_id, context_id, ExecutionMode::Production);
assert_ne!(ctx1.operation_id, ctx4.operation_id);
}
#[test]
fn test_duplicate_registration_replaces_handler() {
let mut registry = EffectRegistry::new(ExecutionMode::Testing);
let handler1 = Box::new(MockRegistrableHandler::new(
EffectType::Crypto,
vec!["hash", "sign"],
ExecutionMode::Testing,
));
let handler2 = Box::new(MockRegistrableHandler::new(
EffectType::Crypto,
vec!["hash", "sign", "verify", "encrypt"],
ExecutionMode::Testing,
));
registry
.register_handler(EffectType::Crypto, handler1)
.unwrap();
registry
.register_handler(EffectType::Crypto, handler2)
.unwrap();
let ops = registry.supported_operations(EffectType::Crypto).unwrap();
assert_eq!(ops.len(), 4);
assert!(ops.contains(&"encrypt".to_string()));
}
}