From 4b6b3107f091581cf28a84df3fa58e23fea4b626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=B3=CE=BB?= Date: Fri, 27 Dec 2024 23:46:43 +1100 Subject: [PATCH] Refactor router (#218) * Extract all repositories to separate crates * Fix tests * Extract keyshare and aggregator features to crates * Fix test imports * Remove comments * Add helpful comments * Use HetrogenousMap * Extract features out of router * Tidy up * Add docs --- packages/ciphernode/Cargo.lock | 17 +- packages/ciphernode/Cargo.toml | 3 + packages/ciphernode/aggregator/Cargo.toml | 2 + packages/ciphernode/aggregator/src/feature.rs | 257 +++++++++++ packages/ciphernode/aggregator/src/lib.rs | 6 + packages/ciphernode/aggregator/src/repo.rs | 25 ++ packages/ciphernode/data/src/lib.rs | 3 +- ...{repository_factory.rs => repositories.rs} | 1 - packages/ciphernode/enclave/Cargo.toml | 14 +- .../enclave/src/commands/net/generate.rs | 1 + .../enclave/src/commands/net/purge.rs | 1 + .../enclave/src/commands/net/set.rs | 1 + .../enclave/src/commands/wallet/set.rs | 1 + .../ciphernode/enclave_node/src/aggregator.rs | 17 +- .../ciphernode/enclave_node/src/ciphernode.rs | 19 +- .../ciphernode/enclave_node/src/datastore.rs | 2 +- packages/ciphernode/evm/src/lib.rs | 2 + packages/ciphernode/evm/src/repo.rs | 37 ++ packages/ciphernode/fhe/Cargo.toml | 3 + packages/ciphernode/fhe/src/feature.rs | 86 ++++ packages/ciphernode/fhe/src/lib.rs | 4 + packages/ciphernode/fhe/src/repo.rs | 15 + packages/ciphernode/keyshare/Cargo.toml | 2 + packages/ciphernode/keyshare/src/feature.rs | 111 +++++ packages/ciphernode/keyshare/src/lib.rs | 4 + packages/ciphernode/keyshare/src/repo.rs | 13 + packages/ciphernode/net/Cargo.toml | 1 + packages/ciphernode/net/src/lib.rs | 2 + packages/ciphernode/net/src/repo.rs | 12 + packages/ciphernode/router/Cargo.toml | 6 - .../ciphernode/router/src/committee_meta.rs | 13 +- packages/ciphernode/router/src/context.rs | 154 +++---- .../router/src/e3_request_router.rs | 32 +- .../ciphernode/router/src/hetrogenous_map.rs | 154 +++++++ packages/ciphernode/router/src/hooks.rs | 419 ------------------ .../ciphernode/router/src/keyshare_feature.rs | 0 packages/ciphernode/router/src/lib.rs | 10 +- packages/ciphernode/router/src/repo.rs | 35 ++ .../ciphernode/router/src/repositories.rs | 107 ----- packages/ciphernode/sortition/Cargo.toml | 1 + .../src/ciphernode_selector.rs | 2 +- packages/ciphernode/sortition/src/lib.rs | 4 + packages/ciphernode/sortition/src/repo.rs | 14 + .../tests/test_aggregation_and_decryption.rs | 20 +- 44 files changed, 956 insertions(+), 677 deletions(-) create mode 100644 packages/ciphernode/aggregator/src/feature.rs create mode 100644 packages/ciphernode/aggregator/src/repo.rs rename packages/ciphernode/data/src/{repository_factory.rs => repositories.rs} (96%) create mode 100644 packages/ciphernode/evm/src/repo.rs create mode 100644 packages/ciphernode/fhe/src/feature.rs create mode 100644 packages/ciphernode/fhe/src/repo.rs create mode 100644 packages/ciphernode/keyshare/src/feature.rs create mode 100644 packages/ciphernode/keyshare/src/repo.rs create mode 100644 packages/ciphernode/net/src/repo.rs create mode 100644 packages/ciphernode/router/src/hetrogenous_map.rs delete mode 100644 packages/ciphernode/router/src/hooks.rs delete mode 100644 packages/ciphernode/router/src/keyshare_feature.rs create mode 100644 packages/ciphernode/router/src/repo.rs delete mode 100644 packages/ciphernode/router/src/repositories.rs rename packages/ciphernode/{router => sortition}/src/ciphernode_selector.rs (98%) create mode 100644 packages/ciphernode/sortition/src/repo.rs diff --git a/packages/ciphernode/Cargo.lock b/packages/ciphernode/Cargo.lock index 734202d5..6d30df34 100644 --- a/packages/ciphernode/Cargo.lock +++ b/packages/ciphernode/Cargo.lock @@ -116,9 +116,11 @@ dependencies = [ "anyhow", "async-trait", "bincode", + "config", "data", "enclave-core", "fhe 0.1.0", + "router", "serde", "sortition", "tracing", @@ -2186,8 +2188,10 @@ dependencies = [ "dirs", "enclave-core", "enclave_node", + "evm", "hex", "libp2p", + "net", "once_cell", "petname", "phf", @@ -2414,9 +2418,11 @@ dependencies = [ name = "fhe" version = "0.1.0" dependencies = [ + "actix", "anyhow", "async-trait", "bincode", + "config", "data", "enclave-core", "fhe 0.1.0-beta.7", @@ -2424,6 +2430,7 @@ dependencies = [ "fhe-util", "rand", "rand_chacha", + "router", "serde", ] @@ -3489,9 +3496,11 @@ dependencies = [ "anyhow", "async-trait", "cipher 0.1.0", + "config", "data", "enclave-core", "fhe 0.1.0", + "router", "serde", "tracing", ] @@ -4147,6 +4156,7 @@ dependencies = [ "async-std", "async-trait", "cipher 0.1.0", + "config", "data", "enclave-core", "futures", @@ -5301,19 +5311,13 @@ name = "router" version = "0.1.0" dependencies = [ "actix", - "aggregator", "anyhow", "async-trait", "bincode", - "cipher 0.1.0", "config", "data", "enclave-core", - "evm", - "fhe 0.1.0", - "keyshare", "serde", - "sortition", "tracing", ] @@ -5838,6 +5842,7 @@ dependencies = [ "alloy", "anyhow", "async-trait", + "config", "data", "enclave-core", "num", diff --git a/packages/ciphernode/Cargo.toml b/packages/ciphernode/Cargo.toml index 1a4a4747..34664799 100644 --- a/packages/ciphernode/Cargo.toml +++ b/packages/ciphernode/Cargo.toml @@ -42,6 +42,7 @@ config = { path = "./config" } dirs = "5.0.1" data = { path = "./data" } enclave-core = { path = "./core" } +evm = { path = "./evm" } shellexpand = "3.1.0" figment = { version = "0.10.19", features = ["yaml", "test"] } fhe_rs = { package = "fhe", git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } @@ -52,8 +53,10 @@ futures-util = "0.3" hex = "0.4.3" lazy_static = "1.5.0" num = "0.4.3" +net = { path = "./net" } rand_chacha = "0.3.1" rand = "0.8.5" +router = { path = "./router" } serde = { version = "1.0.208", features = ["derive"] } serde_json = { version = "1.0.133" } sled = "0.34.7" diff --git a/packages/ciphernode/aggregator/Cargo.toml b/packages/ciphernode/aggregator/Cargo.toml index 495a26a1..83de43a5 100644 --- a/packages/ciphernode/aggregator/Cargo.toml +++ b/packages/ciphernode/aggregator/Cargo.toml @@ -8,9 +8,11 @@ actix = { workspace = true } anyhow = { workspace = true } serde = { workspace = true } bincode = { workspace = true } +config = { workspace = true } async-trait = { workspace = true } enclave-core = { path = "../core" } fhe = { path = "../fhe" } sortition = { path = "../sortition" } +router = { workspace = true } data = { path = "../data" } tracing = { workspace = true } diff --git a/packages/ciphernode/aggregator/src/feature.rs b/packages/ciphernode/aggregator/src/feature.rs new file mode 100644 index 00000000..d0422b5c --- /dev/null +++ b/packages/ciphernode/aggregator/src/feature.rs @@ -0,0 +1,257 @@ +use crate::{ + PlaintextAggregator, PlaintextAggregatorParams, PlaintextAggregatorState, + PlaintextRepositoryFactory, PublicKeyAggregator, PublicKeyAggregatorParams, + PublicKeyAggregatorState, PublicKeyRepositoryFactory, +}; +use actix::{Actor, Addr}; +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use data::{AutoPersist, RepositoriesFactory}; +use enclave_core::{BusError, EnclaveErrorType, EnclaveEvent, EventBus}; +use fhe::FHE_KEY; +use router::{E3Feature, E3RequestContext, E3RequestContextSnapshot, META_KEY}; +use sortition::Sortition; + +pub struct PlaintextAggregatorFeature { + bus: Addr, + sortition: Addr, +} +impl PlaintextAggregatorFeature { + pub fn create(bus: &Addr, sortition: &Addr) -> Box { + Box::new(Self { + bus: bus.clone(), + sortition: sortition.clone(), + }) + } +} + +const ERROR_PLAINTEXT_FHE_MISSING:&str = "Could not create PlaintextAggregator because the fhe instance it depends on was not set on the context."; +const ERROR_PLAINTEXT_META_MISSING:&str = "Could not create PlaintextAggregator because the meta instance it depends on was not set on the context."; + +#[async_trait] +impl E3Feature for PlaintextAggregatorFeature { + fn on_event(&self, ctx: &mut E3RequestContext, evt: &EnclaveEvent) { + // Save plaintext aggregator + let EnclaveEvent::CiphertextOutputPublished { data, .. } = evt else { + return; + }; + + let Some(fhe) = ctx.get_dependency(FHE_KEY) else { + self.bus.err( + EnclaveErrorType::PlaintextAggregation, + anyhow!(ERROR_PLAINTEXT_FHE_MISSING), + ); + return; + }; + + let Some(ref meta) = ctx.get_dependency(META_KEY) else { + self.bus.err( + EnclaveErrorType::PlaintextAggregation, + anyhow!(ERROR_PLAINTEXT_META_MISSING), + ); + return; + }; + + let e3_id = data.e3_id.clone(); + let repo = ctx.repositories().plaintext(&e3_id); + let sync_state = repo.send(Some(PlaintextAggregatorState::init( + meta.threshold_m, + meta.seed, + data.ciphertext_output.clone(), + ))); + + ctx.set_event_recipient( + "plaintext", + Some( + PlaintextAggregator::new( + PlaintextAggregatorParams { + fhe: fhe.clone(), + bus: self.bus.clone(), + sortition: self.sortition.clone(), + e3_id: e3_id.clone(), + src_chain_id: meta.src_chain_id, + }, + sync_state, + ) + .start() + .into(), + ), + ); + } + + async fn hydrate( + &self, + ctx: &mut E3RequestContext, + snapshot: &E3RequestContextSnapshot, + ) -> Result<()> { + // No ID on the snapshot -> bail + if !snapshot.contains("plaintext") { + return Ok(()); + } + + let repo = ctx.repositories().plaintext(&snapshot.e3_id); + let sync_state = repo.load().await?; + + // No Snapshot returned from the store -> bail + if !sync_state.has() { + return Ok(()); + }; + + // Get deps + let Some(fhe) = ctx.get_dependency(FHE_KEY) else { + self.bus.err( + EnclaveErrorType::PlaintextAggregation, + anyhow!(ERROR_PLAINTEXT_FHE_MISSING), + ); + return Ok(()); + }; + + let Some(ref meta) = ctx.get_dependency(META_KEY) else { + self.bus.err( + EnclaveErrorType::PlaintextAggregation, + anyhow!(ERROR_PLAINTEXT_META_MISSING), + ); + return Ok(()); + }; + + let value = PlaintextAggregator::new( + PlaintextAggregatorParams { + fhe: fhe.clone(), + bus: self.bus.clone(), + sortition: self.sortition.clone(), + e3_id: ctx.e3_id.clone(), + src_chain_id: meta.src_chain_id, + }, + sync_state, + ) + .start() + .into(); + + // send to context + ctx.set_event_recipient("plaintext", Some(value)); + + Ok(()) + } +} + +pub struct PublicKeyAggregatorFeature { + bus: Addr, + sortition: Addr, +} + +impl PublicKeyAggregatorFeature { + pub fn create(bus: &Addr, sortition: &Addr) -> Box { + Box::new(Self { + bus: bus.clone(), + sortition: sortition.clone(), + }) + } +} + +const ERROR_PUBKEY_FHE_MISSING:&str = "Could not create PublicKeyAggregator because the fhe instance it depends on was not set on the context."; +const ERROR_PUBKEY_META_MISSING:&str = "Could not create PublicKeyAggregator because the meta instance it depends on was not set on the context."; + +#[async_trait] +impl E3Feature for PublicKeyAggregatorFeature { + fn on_event(&self, ctx: &mut E3RequestContext, evt: &EnclaveEvent) { + // Saving the publickey aggregator with deps on E3Requested + let EnclaveEvent::E3Requested { data, .. } = evt else { + return; + }; + + let Some(fhe) = ctx.get_dependency(FHE_KEY) else { + self.bus.err( + EnclaveErrorType::PublickeyAggregation, + anyhow!(ERROR_PUBKEY_FHE_MISSING), + ); + return; + }; + let Some(ref meta) = ctx.get_dependency(META_KEY) else { + self.bus.err( + EnclaveErrorType::PublickeyAggregation, + anyhow!(ERROR_PUBKEY_META_MISSING), + ); + return; + }; + + let e3_id = data.e3_id.clone(); + let repo = ctx.repositories().publickey(&e3_id); + let sync_state = repo.send(Some(PublicKeyAggregatorState::init( + meta.threshold_m, + meta.seed, + ))); + ctx.set_event_recipient( + "publickey", + Some( + PublicKeyAggregator::new( + PublicKeyAggregatorParams { + fhe: fhe.clone(), + bus: self.bus.clone(), + sortition: self.sortition.clone(), + e3_id, + src_chain_id: meta.src_chain_id, + }, + sync_state, + ) + .start() + .into(), + ), + ); + } + + async fn hydrate( + &self, + ctx: &mut E3RequestContext, + snapshot: &E3RequestContextSnapshot, + ) -> Result<()> { + // No ID on the snapshot -> bail + if !snapshot.contains("publickey") { + return Ok(()); + }; + + let repo = ctx.repositories().publickey(&ctx.e3_id); + let sync_state = repo.load().await?; + + // No Snapshot returned from the store -> bail + if !sync_state.has() { + return Ok(()); + }; + + // Get deps + let Some(fhe) = ctx.get_dependency(FHE_KEY) else { + self.bus.err( + EnclaveErrorType::PublickeyAggregation, + anyhow!(ERROR_PUBKEY_FHE_MISSING), + ); + + return Ok(()); + }; + + let Some(meta) = ctx.get_dependency(META_KEY) else { + self.bus.err( + EnclaveErrorType::PublickeyAggregation, + anyhow!(ERROR_PUBKEY_META_MISSING), + ); + + return Ok(()); + }; + + let value = PublicKeyAggregator::new( + PublicKeyAggregatorParams { + fhe: fhe.clone(), + bus: self.bus.clone(), + sortition: self.sortition.clone(), + e3_id: ctx.e3_id.clone(), + src_chain_id: meta.src_chain_id, + }, + sync_state, + ) + .start() + .into(); + + // send to context + ctx.set_event_recipient("publickey", Some(value)); + + Ok(()) + } +} diff --git a/packages/ciphernode/aggregator/src/lib.rs b/packages/ciphernode/aggregator/src/lib.rs index fef3eb91..8a7415a5 100644 --- a/packages/ciphernode/aggregator/src/lib.rs +++ b/packages/ciphernode/aggregator/src/lib.rs @@ -1,8 +1,14 @@ +mod feature; mod plaintext_aggregator; mod publickey_aggregator; +mod repo; + pub use plaintext_aggregator::{ PlaintextAggregator, PlaintextAggregatorParams, PlaintextAggregatorState, }; pub use publickey_aggregator::{ PublicKeyAggregator, PublicKeyAggregatorParams, PublicKeyAggregatorState, }; + +pub use feature::*; +pub use repo::*; diff --git a/packages/ciphernode/aggregator/src/repo.rs b/packages/ciphernode/aggregator/src/repo.rs new file mode 100644 index 00000000..a010b142 --- /dev/null +++ b/packages/ciphernode/aggregator/src/repo.rs @@ -0,0 +1,25 @@ +use config::StoreKeys; +use data::{Repositories, Repository}; +use enclave_core::E3id; + +use crate::{PlaintextAggregatorState, PublicKeyAggregatorState}; + +pub trait PlaintextRepositoryFactory { + fn plaintext(&self, e3_id: &E3id) -> Repository; +} + +impl PlaintextRepositoryFactory for Repositories { + fn plaintext(&self, e3_id: &E3id) -> Repository { + Repository::new(self.store.scope(StoreKeys::plaintext(e3_id))) + } +} + +pub trait PublicKeyRepositoryFactory { + fn publickey(&self, e3_id: &E3id) -> Repository; +} + +impl PublicKeyRepositoryFactory for Repositories { + fn publickey(&self, e3_id: &E3id) -> Repository { + Repository::new(self.store.scope(StoreKeys::publickey(e3_id))) + } +} diff --git a/packages/ciphernode/data/src/lib.rs b/packages/ciphernode/data/src/lib.rs index 08b5f844..48ed8475 100644 --- a/packages/ciphernode/data/src/lib.rs +++ b/packages/ciphernode/data/src/lib.rs @@ -2,8 +2,8 @@ mod data_store; mod in_mem; mod into_key; mod persistable; +mod repositories; mod repository; -mod repository_factory; mod sled_store; mod snapshot; @@ -11,6 +11,7 @@ pub use data_store::*; pub use in_mem::*; pub use into_key::IntoKey; pub use persistable::*; +pub use repositories::*; pub use repository::*; pub use sled_store::*; pub use snapshot::*; diff --git a/packages/ciphernode/data/src/repository_factory.rs b/packages/ciphernode/data/src/repositories.rs similarity index 96% rename from packages/ciphernode/data/src/repository_factory.rs rename to packages/ciphernode/data/src/repositories.rs index 3024b84a..be9aad25 100644 --- a/packages/ciphernode/data/src/repository_factory.rs +++ b/packages/ciphernode/data/src/repositories.rs @@ -1,6 +1,5 @@ use crate::{DataStore, Repository}; -// TODO: Naming here is confusing pub struct Repositories { pub store: DataStore, } diff --git a/packages/ciphernode/enclave/Cargo.toml b/packages/ciphernode/enclave/Cargo.toml index c2595c4b..fcf601d3 100644 --- a/packages/ciphernode/enclave/Cargo.toml +++ b/packages/ciphernode/enclave/Cargo.toml @@ -12,26 +12,28 @@ alloy = { workspace = true } anyhow = { workspace = true } cipher = { path = "../cipher" } clap = { workspace = true } +compile-time = { workspace = true } config = { path = "../config" } data = { path = "../data" } -dirs = { workspace = true } dialoguer = "0.11.0" +dirs = { workspace = true } enclave-core = { path = "../core" } enclave_node = { path = "../enclave_node" } +evm = { workspace = true } hex = { workspace = true } libp2p = { workspace = true } +net = { workspace = true } once_cell = "1.20.2" +petname = "2.0.2" +phf = { version = "0.11", features = ["macros"] } +rand = { workspace = true } router = { path = "../router" } -tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } zeroize = { workspace = true } -phf = { version = "0.11", features = ["macros"] } -compile-time = { workspace = true } -rand = { workspace = true } -petname = "2.0.2" [build-dependencies] serde_json = { workspace = true } diff --git a/packages/ciphernode/enclave/src/commands/net/generate.rs b/packages/ciphernode/enclave/src/commands/net/generate.rs index 1e747379..d582daab 100644 --- a/packages/ciphernode/enclave/src/commands/net/generate.rs +++ b/packages/ciphernode/enclave/src/commands/net/generate.rs @@ -5,6 +5,7 @@ use config::AppConfig; use enclave_core::{EventBus, GetErrors}; use enclave_node::get_repositories; use libp2p::identity::Keypair; +use net::NetRepositoryFactory; use zeroize::Zeroize; pub async fn execute(config: &AppConfig) -> Result<()> { diff --git a/packages/ciphernode/enclave/src/commands/net/purge.rs b/packages/ciphernode/enclave/src/commands/net/purge.rs index 520a75d5..a83cc46e 100644 --- a/packages/ciphernode/enclave/src/commands/net/purge.rs +++ b/packages/ciphernode/enclave/src/commands/net/purge.rs @@ -3,6 +3,7 @@ use anyhow::*; use config::AppConfig; use enclave_core::EventBus; use enclave_node::get_repositories; +use net::NetRepositoryFactory; pub async fn execute(config: &AppConfig) -> Result<()> { let bus = EventBus::new(true).start(); diff --git a/packages/ciphernode/enclave/src/commands/net/set.rs b/packages/ciphernode/enclave/src/commands/net/set.rs index 853ecde0..3a6739ca 100644 --- a/packages/ciphernode/enclave/src/commands/net/set.rs +++ b/packages/ciphernode/enclave/src/commands/net/set.rs @@ -7,6 +7,7 @@ use dialoguer::{theme::ColorfulTheme, Password}; use enclave_core::{EventBus, GetErrors}; use enclave_node::get_repositories; use libp2p::identity::Keypair; +use net::NetRepositoryFactory; pub fn create_keypair(input: &String) -> Result { match hex::check(input) { diff --git a/packages/ciphernode/enclave/src/commands/wallet/set.rs b/packages/ciphernode/enclave/src/commands/wallet/set.rs index fcafd38e..4c322628 100644 --- a/packages/ciphernode/enclave/src/commands/wallet/set.rs +++ b/packages/ciphernode/enclave/src/commands/wallet/set.rs @@ -6,6 +6,7 @@ use config::AppConfig; use dialoguer::{theme::ColorfulTheme, Password}; use enclave_core::{EventBus, GetErrors}; use enclave_node::get_repositories; +use evm::EthPrivateKeyRepositoryFactory; pub fn validate_private_key(input: &String) -> Result<()> { let bytes = diff --git a/packages/ciphernode/enclave_node/src/aggregator.rs b/packages/ciphernode/enclave_node/src/aggregator.rs index e9fbbf6a..178432c0 100644 --- a/packages/ciphernode/enclave_node/src/aggregator.rs +++ b/packages/ciphernode/enclave_node/src/aggregator.rs @@ -1,27 +1,28 @@ +use crate::setup_datastore; use actix::{Actor, Addr}; +use aggregator::{PlaintextAggregatorFeature, PublicKeyAggregatorFeature}; use anyhow::Result; use cipher::Cipher; use config::AppConfig; +use data::RepositoriesFactory; use enclave_core::EventBus; use evm::{ helpers::{get_signer_from_repository, ProviderConfig}, - CiphernodeRegistrySol, EnclaveSol, RegistryFilterSol, + CiphernodeRegistryReaderRepositoryFactory, CiphernodeRegistrySol, EnclaveSol, + EnclaveSolReaderRepositoryFactory, EthPrivateKeyRepositoryFactory, RegistryFilterSol, }; +use fhe::FheFeature; use logger::SimpleLogger; -use net::NetworkManager; +use net::{NetRepositoryFactory, NetworkManager}; use rand::SeedableRng; use rand_chacha::{rand_core::OsRng, ChaCha20Rng}; -use router::{ - E3RequestRouter, FheFeature, PlaintextAggregatorFeature, PublicKeyAggregatorFeature, - RepositoriesFactory, -}; +use router::E3RequestRouter; use sortition::Sortition; +use sortition::SortitionRepositoryFactory; use std::sync::{Arc, Mutex}; use test_helpers::{PlaintextWriter, PublicKeyWriter}; use tokio::task::JoinHandle; -use crate::setup_datastore; - pub async fn setup_aggregator( config: AppConfig, pubkey_write_path: Option<&str>, diff --git a/packages/ciphernode/enclave_node/src/ciphernode.rs b/packages/ciphernode/enclave_node/src/ciphernode.rs index bf71c73c..77bd8cc4 100644 --- a/packages/ciphernode/enclave_node/src/ciphernode.rs +++ b/packages/ciphernode/enclave_node/src/ciphernode.rs @@ -1,24 +1,29 @@ +use crate::setup_datastore; use actix::{Actor, Addr}; use alloy::primitives::Address; use anyhow::Result; use cipher::Cipher; use config::AppConfig; +use data::RepositoriesFactory; use enclave_core::{get_tag, EventBus}; -use evm::{helpers::ProviderConfig, CiphernodeRegistrySol, EnclaveSolReader}; +use evm::{ + helpers::ProviderConfig, CiphernodeRegistryReaderRepositoryFactory, CiphernodeRegistrySol, + EnclaveSolReader, EnclaveSolReaderRepositoryFactory, +}; +use fhe::FheFeature; +use keyshare::KeyshareFeature; use logger::SimpleLogger; -use net::NetworkManager; +use net::{NetRepositoryFactory, NetworkManager}; use rand::SeedableRng; use rand_chacha::rand_core::OsRng; -use router::{ - CiphernodeSelector, E3RequestRouter, FheFeature, KeyshareFeature, RepositoriesFactory, -}; +use router::E3RequestRouter; +use sortition::CiphernodeSelector; use sortition::Sortition; +use sortition::SortitionRepositoryFactory; use std::sync::{Arc, Mutex}; use tokio::task::JoinHandle; use tracing::instrument; -use crate::setup_datastore; - #[instrument(name="app", skip_all,fields(id = get_tag()))] pub async fn setup_ciphernode( config: AppConfig, diff --git a/packages/ciphernode/enclave_node/src/datastore.rs b/packages/ciphernode/enclave_node/src/datastore.rs index 17a2be04..43ff182f 100644 --- a/packages/ciphernode/enclave_node/src/datastore.rs +++ b/packages/ciphernode/enclave_node/src/datastore.rs @@ -4,8 +4,8 @@ use actix::{Actor, Addr}; use anyhow::Result; use config::AppConfig; use data::{DataStore, InMemStore, SledStore}; +use data::{Repositories, RepositoriesFactory}; use enclave_core::EventBus; -use router::{Repositories, RepositoriesFactory}; pub fn get_sled_store(bus: &Addr, db_file: &PathBuf) -> Result { Ok((&SledStore::new(bus, db_file)?).into()) diff --git a/packages/ciphernode/evm/src/lib.rs b/packages/ciphernode/evm/src/lib.rs index e7fb10c9..6cecae10 100644 --- a/packages/ciphernode/evm/src/lib.rs +++ b/packages/ciphernode/evm/src/lib.rs @@ -5,6 +5,7 @@ mod enclave_sol_writer; mod event_reader; pub mod helpers; mod registry_filter_sol; +mod repo; pub use ciphernode_registry_sol::{CiphernodeRegistrySol, CiphernodeRegistrySolReader}; pub use enclave_sol::EnclaveSol; @@ -12,3 +13,4 @@ pub use enclave_sol_reader::EnclaveSolReader; pub use enclave_sol_writer::EnclaveSolWriter; pub use event_reader::{EnclaveEvmEvent, EvmEventReader, EvmEventReaderState, ExtractorFn}; pub use registry_filter_sol::{RegistryFilterSol, RegistryFilterSolWriter}; +pub use repo::*; diff --git a/packages/ciphernode/evm/src/repo.rs b/packages/ciphernode/evm/src/repo.rs new file mode 100644 index 00000000..f20b5d1e --- /dev/null +++ b/packages/ciphernode/evm/src/repo.rs @@ -0,0 +1,37 @@ +use config::StoreKeys; +use data::{Repositories, Repository}; + +use crate::EvmEventReaderState; + +pub trait EthPrivateKeyRepositoryFactory { + fn eth_private_key(&self) -> Repository>; +} + +impl EthPrivateKeyRepositoryFactory for Repositories { + fn eth_private_key(&self) -> Repository> { + Repository::new(self.store.scope(StoreKeys::eth_private_key())) + } +} + +pub trait EnclaveSolReaderRepositoryFactory { + fn enclave_sol_reader(&self, chain_id: u64) -> Repository; +} + +impl EnclaveSolReaderRepositoryFactory for Repositories { + fn enclave_sol_reader(&self, chain_id: u64) -> Repository { + Repository::new(self.store.scope(StoreKeys::enclave_sol_reader(chain_id))) + } +} + +pub trait CiphernodeRegistryReaderRepositoryFactory { + fn ciphernode_registry_reader(&self, chain_id: u64) -> Repository; +} + +impl CiphernodeRegistryReaderRepositoryFactory for Repositories { + fn ciphernode_registry_reader(&self, chain_id: u64) -> Repository { + Repository::new( + self.store + .scope(StoreKeys::ciphernode_registry_reader(chain_id)), + ) + } +} diff --git a/packages/ciphernode/fhe/Cargo.toml b/packages/ciphernode/fhe/Cargo.toml index 3272964a..aeca4615 100644 --- a/packages/ciphernode/fhe/Cargo.toml +++ b/packages/ciphernode/fhe/Cargo.toml @@ -4,9 +4,11 @@ version = "0.1.0" edition = "2021" [dependencies] +actix = { workspace = true } anyhow = { workspace = true } async-trait = { workspace = true } bincode = { workspace = true } +config = { workspace = true } data = { path = "../data" } enclave-core = { path = "../core" } fhe-traits = { workspace = true } @@ -14,4 +16,5 @@ fhe-util = { workspace = true } fhe_rs = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } +router = { workspace = true } serde = { workspace = true } diff --git a/packages/ciphernode/fhe/src/feature.rs b/packages/ciphernode/fhe/src/feature.rs new file mode 100644 index 00000000..adba1897 --- /dev/null +++ b/packages/ciphernode/fhe/src/feature.rs @@ -0,0 +1,86 @@ +use crate::{Fhe, FheRepositoryFactory, SharedRng}; +use actix::Addr; +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use data::{FromSnapshotWithParams, RepositoriesFactory, Snapshot}; +use enclave_core::{BusError, E3Requested, EnclaveErrorType, EnclaveEvent, EventBus}; +use router::{E3Feature, E3RequestContext, E3RequestContextSnapshot, TypedKey}; +use std::sync::Arc; + +pub const FHE_KEY: TypedKey> = TypedKey::new("fhe"); + +/// TODO: move these to each package with access on MyStruct::launcher() +pub struct FheFeature { + rng: SharedRng, + bus: Addr, +} + +impl FheFeature { + pub fn create(bus: &Addr, rng: &SharedRng) -> Box { + Box::new(Self { + rng: rng.clone(), + bus: bus.clone(), + }) + } +} + +const ERROR_FHE_FAILED_TO_DECODE: &str = "Failed to decode encoded FHE params"; + +#[async_trait] +impl E3Feature for FheFeature { + fn on_event(&self, ctx: &mut E3RequestContext, evt: &EnclaveEvent) { + // Saving the fhe on Committee Requested + let EnclaveEvent::E3Requested { data, .. } = evt else { + return; + }; + + let E3Requested { + params, + seed, + e3_id, + .. + } = data.clone(); + + let Ok(fhe_inner) = Fhe::from_encoded(¶ms, seed, self.rng.clone()) else { + self.bus.err( + EnclaveErrorType::KeyGeneration, + anyhow!(ERROR_FHE_FAILED_TO_DECODE), + ); + return; + }; + + let fhe = Arc::new(fhe_inner); + + // FHE doesn't implement Checkpoint so we are going to store it manually + let Ok(snapshot) = fhe.snapshot() else { + self.bus.err( + EnclaveErrorType::KeyGeneration, + anyhow!("Failed to get snapshot"), + ); + return; + }; + ctx.repositories().fhe(&e3_id).write(&snapshot); + let _ = ctx.set_dependency(FHE_KEY, fhe); + } + + async fn hydrate( + &self, + ctx: &mut E3RequestContext, + snapshot: &E3RequestContextSnapshot, + ) -> Result<()> { + // No ID on the snapshot -> bail without reporting + if !snapshot.contains("fhe") { + return Ok(()); + }; + + // No Snapshot returned from the store -> bail without reporting + let Some(snap) = ctx.repositories().fhe(&ctx.e3_id).read().await? else { + return Ok(()); + }; + + let value = Arc::new(Fhe::from_snapshot(self.rng.clone(), snap).await?); + ctx.set_dependency(FHE_KEY, value); + + Ok(()) + } +} diff --git a/packages/ciphernode/fhe/src/lib.rs b/packages/ciphernode/fhe/src/lib.rs index 75b9bd75..f6a4203d 100644 --- a/packages/ciphernode/fhe/src/lib.rs +++ b/packages/ciphernode/fhe/src/lib.rs @@ -1,5 +1,9 @@ +mod feature; mod fhe; +mod repo; mod utils; +pub use feature::*; pub use fhe::*; +pub use repo::*; pub use utils::*; diff --git a/packages/ciphernode/fhe/src/repo.rs b/packages/ciphernode/fhe/src/repo.rs new file mode 100644 index 00000000..7e035b73 --- /dev/null +++ b/packages/ciphernode/fhe/src/repo.rs @@ -0,0 +1,15 @@ +use config::StoreKeys; +use data::{Repositories, Repository}; +use enclave_core::E3id; + +use crate::FheSnapshot; + +pub trait FheRepositoryFactory { + fn fhe(&self, e3_id: &E3id) -> Repository; +} + +impl FheRepositoryFactory for Repositories { + fn fhe(&self, e3_id: &E3id) -> Repository { + Repository::new(self.store.scope(StoreKeys::fhe(e3_id))) + } +} diff --git a/packages/ciphernode/keyshare/Cargo.toml b/packages/ciphernode/keyshare/Cargo.toml index bc05ba0b..b5a9c599 100644 --- a/packages/ciphernode/keyshare/Cargo.toml +++ b/packages/ciphernode/keyshare/Cargo.toml @@ -7,9 +7,11 @@ edition = "2021" actix = { workspace = true } anyhow = { workspace = true } async-trait = { workspace = true } +config = { workspace = true } data = { path = "../data" } cipher = { path = "../cipher" } enclave-core = { path = "../core" } fhe = { path = "../fhe" } +router = { workspace = true } serde = { workspace = true } tracing = { workspace = true } diff --git a/packages/ciphernode/keyshare/src/feature.rs b/packages/ciphernode/keyshare/src/feature.rs new file mode 100644 index 00000000..e5db14ea --- /dev/null +++ b/packages/ciphernode/keyshare/src/feature.rs @@ -0,0 +1,111 @@ +use crate::{Keyshare, KeyshareParams, KeyshareRepositoryFactory}; +use actix::{Actor, Addr}; +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use cipher::Cipher; +use data::{AutoPersist, RepositoriesFactory}; +use enclave_core::{BusError, EnclaveErrorType, EnclaveEvent, EventBus}; +use fhe::FHE_KEY; +use router::{E3Feature, E3RequestContext, E3RequestContextSnapshot}; +use std::sync::Arc; + +pub struct KeyshareFeature { + bus: Addr, + address: String, + cipher: Arc, +} + +impl KeyshareFeature { + pub fn create(bus: &Addr, address: &str, cipher: &Arc) -> Box { + Box::new(Self { + bus: bus.clone(), + address: address.to_owned(), + cipher: cipher.to_owned(), + }) + } +} + +const ERROR_KEYSHARE_FHE_MISSING: &str = + "Could not create Keyshare because the fhe instance it depends on was not set on the context."; + +#[async_trait] +impl E3Feature for KeyshareFeature { + fn on_event(&self, ctx: &mut E3RequestContext, evt: &EnclaveEvent) { + // if this is NOT a CiphernodeSelected event then ignore + let EnclaveEvent::CiphernodeSelected { data, .. } = evt else { + return; + }; + + // Has the FHE dependency been already setup? (hint: it should have) + let Some(fhe) = ctx.get_dependency(FHE_KEY) else { + self.bus.err( + EnclaveErrorType::KeyGeneration, + anyhow!(ERROR_KEYSHARE_FHE_MISSING), + ); + return; + }; + + let e3_id = data.clone().e3_id; + let repo = ctx.repositories().keyshare(&e3_id); + let container = repo.send(None); // New container with None + + ctx.set_event_recipient( + "keyshare", + Some( + Keyshare::new(KeyshareParams { + bus: self.bus.clone(), + secret: container, + fhe: fhe.clone(), + address: self.address.clone(), + cipher: self.cipher.clone(), + }) + .start() + .into(), + ), + ); + } + + async fn hydrate( + &self, + ctx: &mut E3RequestContext, + snapshot: &E3RequestContextSnapshot, + ) -> Result<()> { + // No keyshare on the snapshot -> bail + if !snapshot.contains("keyshare") { + return Ok(()); + }; + + // Get the saved state as a persistable + let sync_secret = ctx.repositories().keyshare(&snapshot.e3_id).load().await?; + + // No Snapshot returned from the sync_secret -> bail + if !sync_secret.has() { + return Ok(()); + }; + + // Has the FHE dependency been already setup? (hint: it should have) + let Some(fhe) = ctx.get_dependency(FHE_KEY) else { + self.bus.err( + EnclaveErrorType::KeyGeneration, + anyhow!(ERROR_KEYSHARE_FHE_MISSING), + ); + return Ok(()); + }; + + // Construct from snapshot + let value = Keyshare::new(KeyshareParams { + fhe: fhe.clone(), + bus: self.bus.clone(), + secret: sync_secret, + address: self.address.clone(), + cipher: self.cipher.clone(), + }) + .start() + .into(); + + // send to context + ctx.set_event_recipient("keyshare", Some(value)); + + Ok(()) + } +} diff --git a/packages/ciphernode/keyshare/src/lib.rs b/packages/ciphernode/keyshare/src/lib.rs index 46e4b5c9..a41ebf72 100644 --- a/packages/ciphernode/keyshare/src/lib.rs +++ b/packages/ciphernode/keyshare/src/lib.rs @@ -1,2 +1,6 @@ +mod feature; mod keyshare; +mod repo; +pub use feature::*; pub use keyshare::*; +pub use repo::*; diff --git a/packages/ciphernode/keyshare/src/repo.rs b/packages/ciphernode/keyshare/src/repo.rs new file mode 100644 index 00000000..772c5335 --- /dev/null +++ b/packages/ciphernode/keyshare/src/repo.rs @@ -0,0 +1,13 @@ +use config::StoreKeys; +use data::{Repositories, Repository}; +use enclave_core::E3id; + +pub trait KeyshareRepositoryFactory { + fn keyshare(&self, e3_id: &E3id) -> Repository>; +} + +impl KeyshareRepositoryFactory for Repositories { + fn keyshare(&self, e3_id: &E3id) -> Repository> { + Repository::new(self.store.scope(StoreKeys::keyshare(e3_id))) + } +} diff --git a/packages/ciphernode/net/Cargo.toml b/packages/ciphernode/net/Cargo.toml index 91ecb570..dba2c1d5 100644 --- a/packages/ciphernode/net/Cargo.toml +++ b/packages/ciphernode/net/Cargo.toml @@ -12,6 +12,7 @@ async-std = { workspace = true, features = ["attributes"] } async-trait = { workspace = true } futures = { workspace = true } cipher = { workspace = true } +config = { workspace = true } data = { workspace = true } libp2p = { workspace = true, features = [ "async-std", diff --git a/packages/ciphernode/net/src/lib.rs b/packages/ciphernode/net/src/lib.rs index 35d4f37e..b695df32 100644 --- a/packages/ciphernode/net/src/lib.rs +++ b/packages/ciphernode/net/src/lib.rs @@ -6,7 +6,9 @@ mod dialer; pub mod events; mod network_manager; mod network_peer; +mod repo; mod retry; pub use network_manager::*; pub use network_peer::*; +pub use repo::*; diff --git a/packages/ciphernode/net/src/repo.rs b/packages/ciphernode/net/src/repo.rs new file mode 100644 index 00000000..93b0abc2 --- /dev/null +++ b/packages/ciphernode/net/src/repo.rs @@ -0,0 +1,12 @@ +use config::StoreKeys; +use data::{Repositories, Repository}; + +pub trait NetRepositoryFactory { + fn libp2p_keypair(&self) -> Repository>; +} + +impl NetRepositoryFactory for Repositories { + fn libp2p_keypair(&self) -> Repository> { + Repository::new(self.store.scope(StoreKeys::libp2p_keypair())) + } +} diff --git a/packages/ciphernode/router/Cargo.toml b/packages/ciphernode/router/Cargo.toml index d2dbacec..f5e485c9 100644 --- a/packages/ciphernode/router/Cargo.toml +++ b/packages/ciphernode/router/Cargo.toml @@ -6,16 +6,10 @@ edition = "2021" [dependencies] actix = { workspace = true } enclave-core = { path = "../core" } -sortition = { path = "../sortition" } -fhe = { path = "../fhe" } data = { path = "../data" } -keyshare = { path = "../keyshare" } -aggregator = { path = "../aggregator" } -evm = { path = "../evm" } anyhow = { workspace = true } serde = { workspace = true } config = { workspace = true } -cipher = { path = "../cipher" } bincode = { workspace = true } async-trait = { workspace = true } tracing = { workspace = true } diff --git a/packages/ciphernode/router/src/committee_meta.rs b/packages/ciphernode/router/src/committee_meta.rs index eb9a7d2c..74c6cc2b 100644 --- a/packages/ciphernode/router/src/committee_meta.rs +++ b/packages/ciphernode/router/src/committee_meta.rs @@ -1,8 +1,13 @@ -use crate::{E3Feature, E3RequestContext, E3RequestContextSnapshot, RepositoriesFactory}; +use crate::{ + E3Feature, E3RequestContext, E3RequestContextSnapshot, MetaRepositoryFactory, TypedKey, +}; use anyhow::*; use async_trait::async_trait; +use data::RepositoriesFactory; use enclave_core::{E3Requested, EnclaveEvent, Seed}; +pub const META_KEY: TypedKey = TypedKey::new("meta"); + #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct CommitteeMeta { pub threshold_m: usize, @@ -39,7 +44,7 @@ impl E3Feature for CommitteeMetaFeature { src_chain_id, }; ctx.repositories().meta(&e3_id).write(&meta); - let _ = ctx.set_meta(meta); + let _ = ctx.set_dependency(META_KEY, meta); } async fn hydrate( @@ -48,7 +53,7 @@ impl E3Feature for CommitteeMetaFeature { snapshot: &E3RequestContextSnapshot, ) -> Result<()> { // No ID on the snapshot -> bail - if !snapshot.meta { + if !snapshot.contains("meta") { return Ok(()); }; @@ -59,7 +64,7 @@ impl E3Feature for CommitteeMetaFeature { return Ok(()); }; - ctx.set_meta(value); + ctx.set_dependency(META_KEY, value); Ok(()) } diff --git a/packages/ciphernode/router/src/context.rs b/packages/ciphernode/router/src/context.rs index f869844c..e1912c30 100644 --- a/packages/ciphernode/router/src/context.rs +++ b/packages/ciphernode/router/src/context.rs @@ -1,36 +1,53 @@ -use std::sync::Arc; - -use crate::{CommitteeMeta, E3Feature, EventBuffer, Repositories, RepositoriesFactory}; -use actix::{Addr, Recipient}; -use aggregator::{PlaintextAggregator, PublicKeyAggregator}; +use crate::{E3Feature, EventBuffer, HetrogenousMap, TypedKey}; +use actix::Recipient; use anyhow::Result; use async_trait::async_trait; -use data::{Checkpoint, FromSnapshotWithParams, Repository, Snapshot}; +use data::{ + Checkpoint, FromSnapshotWithParams, Repositories, RepositoriesFactory, Repository, Snapshot, +}; use enclave_core::{E3id, EnclaveEvent}; -use fhe::Fhe; -use keyshare::Keyshare; use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, sync::Arc}; + +/// Initialize the HashMap with a list of expected Recipients. In order to know whether or not we +/// should buffer we need to iterate over this list and determine which recipients are missing based +/// on the recipient value is why we set it here to have keys with empty values. +fn init_recipients() -> HashMap>> { + HashMap::from([ + ("keyshare".to_owned(), None), + ("plaintext".to_owned(), None), + ("publickey".to_owned(), None), + ]) +} /// Context that is set to each event hook. Hooks can use this context to gather dependencies if /// they need to instantiate struct instances or actors. +// TODO: remove Addr imports as we need to be able to move the features out of the hooks file +// without circular deps +// TODO: remove Arc import as we need to be able to move the Fhe feature out of the hooks +// file without circular deps pub struct E3RequestContext { + /// The E3Request's ID pub e3_id: E3id, - pub keyshare: Option>, - pub fhe: Option>, - pub plaintext: Option>, - pub publickey: Option>, - pub meta: Option, + /// A way to store EnclaveEvent recipients on the context + pub recipients: HashMap>>, // NOTE: can be a None value + /// A way to store a feature's dependencies on the context + pub dependencies: HetrogenousMap, + /// A Repository for storing this context's data snapshot pub store: Repository, } #[derive(Serialize, Deserialize)] pub struct E3RequestContextSnapshot { - pub keyshare: bool, pub e3_id: E3id, - pub fhe: bool, - pub plaintext: bool, - pub publickey: bool, - pub meta: bool, + pub recipients: Vec, + pub dependencies: Vec, +} + +impl E3RequestContextSnapshot { + pub fn contains(&self, key: &str) -> bool { + self.recipients.contains(&key.to_string()) || self.dependencies.contains(&key.to_string()) + } } pub struct E3RequestContextParams { @@ -44,29 +61,18 @@ impl E3RequestContext { Self { e3_id: params.e3_id, store: params.store, - fhe: None, - keyshare: None, - meta: None, - plaintext: None, - publickey: None, + recipients: init_recipients(), + dependencies: HetrogenousMap::new(), } } + /// Return a list of expected recipient keys alongside any values that have or have not been + /// set. fn recipients(&self) -> Vec<(String, Option>)> { - vec![ - ( - "keyshare".to_owned(), - self.keyshare.clone().map(|addr| addr.into()), - ), - ( - "plaintext".to_owned(), - self.plaintext.clone().map(|addr| addr.into()), - ), - ( - "publickey".to_owned(), - self.publickey.clone().map(|addr| addr.into()), - ), - ] + self.recipients + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect() } pub fn forward_message(&self, msg: &EnclaveEvent, buffer: &mut EventBuffer) { @@ -90,54 +96,34 @@ impl E3RequestContext { }); } - /// Accept a DataStore ID and a Keystore actor address - pub fn set_keyshare(&mut self, value: Addr) { - self.keyshare = Some(value); - self.checkpoint(); - } - - /// Accept a DataStore ID and a Keystore actor address - pub fn set_plaintext(&mut self, value: Addr) { - self.plaintext = Some(value); - self.checkpoint(); - } - - /// Accept a DataStore ID and a Keystore actor address - pub fn set_publickey(&mut self, value: Addr) { - self.publickey = Some(value); + pub fn set_event_recipient( + &mut self, + key: impl Into, + value: Option>, + ) { + self.recipients.insert(key.into(), value); self.checkpoint(); } - /// Accept a DataStore ID and an Arc instance of the Fhe wrapper - pub fn set_fhe(&mut self, value: Arc) { - self.fhe = Some(value.clone()); - self.checkpoint(); + pub fn get_event_recipient(&self, key: impl Into) -> Option<&Recipient> { + self.recipients + .get(&key.into()) + .and_then(|opt| opt.as_ref()) } - /// Accept a Datastore ID and a metadata object - pub fn set_meta(&mut self, value: CommitteeMeta) { - self.meta = Some(value.clone()); + pub fn set_dependency(&mut self, key: TypedKey, value: T) + where + T: Send + Sync + 'static, + { + self.dependencies.insert(key, value); self.checkpoint(); } - pub fn get_keyshare(&self) -> Option<&Addr> { - self.keyshare.as_ref() - } - - pub fn get_plaintext(&self) -> Option<&Addr> { - self.plaintext.as_ref() - } - - pub fn get_publickey(&self) -> Option<&Addr> { - self.publickey.as_ref() - } - - pub fn get_fhe(&self) -> Option<&Arc> { - self.fhe.as_ref() - } - - pub fn get_meta(&self) -> Option<&CommitteeMeta> { - self.meta.as_ref() + pub fn get_dependency(&self, key: TypedKey) -> Option<&T> + where + T: Send + Sync + 'static, + { + self.dependencies.get(key) } } @@ -154,11 +140,8 @@ impl Snapshot for E3RequestContext { fn snapshot(&self) -> Result { Ok(Self::Snapshot { e3_id: self.e3_id.clone(), - meta: self.meta.is_some(), - fhe: self.fhe.is_some(), - publickey: self.publickey.is_some(), - plaintext: self.plaintext.is_some(), - keyshare: self.keyshare.is_some(), + dependencies: self.dependencies.keys(), + recipients: self.recipients.keys().cloned().collect(), }) } } @@ -170,11 +153,8 @@ impl FromSnapshotWithParams for E3RequestContext { let mut ctx = Self { e3_id: params.e3_id, store: params.store, - fhe: None, - keyshare: None, - meta: None, - plaintext: None, - publickey: None, + recipients: init_recipients(), + dependencies: HetrogenousMap::new(), }; for feature in params.features.iter() { diff --git a/packages/ciphernode/router/src/e3_request_router.rs b/packages/ciphernode/router/src/e3_request_router.rs index ba2eb39c..9c29c81a 100644 --- a/packages/ciphernode/router/src/e3_request_router.rs +++ b/packages/ciphernode/router/src/e3_request_router.rs @@ -1,8 +1,9 @@ use crate::CommitteeMetaFeature; +use crate::ContextRepositoryFactory; use crate::E3RequestContext; use crate::E3RequestContextParams; use crate::E3RequestContextSnapshot; -use crate::RepositoriesFactory; +use crate::RouterRepositoryFactory; use actix::AsyncContext; use actix::{Actor, Addr, Context, Handler}; use anyhow::*; @@ -10,6 +11,7 @@ use async_trait::async_trait; use data::Checkpoint; use data::DataStore; use data::FromSnapshotWithParams; +use data::RepositoriesFactory; use data::Repository; use data::Snapshot; use enclave_core::E3RequestComplete; @@ -39,10 +41,24 @@ impl EventBuffer { } } -/// Format of the hook that needs to be passed to E3RequestRouter +/// Format of a Feature that can be passed to E3RequestRouter. E3Features listen for EnclaveEvents +/// that are braoadcast to know when to instantiate themselves. They define the events they respond +/// to using the `on_event` handler. Within this handler they will typically use the request's +/// context to construct a version of their requisite actors and save their addresses to the +/// context using the `set_event_recipient` method on the context. Event recipients once set will +/// then have all their events streamed to them from their buffer. Features can also reconstruct +/// Actors based on their persisted state using the context snapshot and relevant repositories. +/// Generally Features can ask the context to see if a dependency has already been set to know if +/// it has everything it needs to construct the Feature #[async_trait] pub trait E3Feature: Send + Sync + 'static { + /// This function is triggered when an EnclaveEvent is sent to the router. Use this to + /// initialize the receiver using `ctx.set_event_receiver(my_address.into())`. Typically this + /// means filtering for specific e3_id enabled events that give rise to actors that have to + /// handle certain behaviour. fn on_event(&self, ctx: &mut E3RequestContext, evt: &EnclaveEvent); + + /// This function it triggered when the request context is being hydrated from snapshot. async fn hydrate( &self, ctx: &mut E3RequestContext, @@ -51,14 +67,16 @@ pub trait E3Feature: Send + Sync + 'static { } /// E3RequestRouter will register features that receive an E3_id specific context. After features -/// have run e3_id specific messages are forwarded to all instances on the context. This enables -/// features to lazily register instances that have the correct dependencies available per e3_id -/// request -// TODO: setup typestate pattern so that we have to place features within correct order of -// dependencies +/// have run e3_id specific messages are forwarded to all instances on the context as they come in. +/// This enables features to lazily register instances that have the correct dependencies available +/// per e3_id request. +// TODO: setup so that we have to place features within correct order of dependencies pub struct E3RequestRouter { + /// The context for every E3 request contexts: HashMap, + /// A list of completed requests completed: HashSet, + /// The features this instance of the router is configured to listen for features: Arc>>, buffer: EventBuffer, bus: Addr, diff --git a/packages/ciphernode/router/src/hetrogenous_map.rs b/packages/ciphernode/router/src/hetrogenous_map.rs new file mode 100644 index 00000000..c8c34383 --- /dev/null +++ b/packages/ciphernode/router/src/hetrogenous_map.rs @@ -0,0 +1,154 @@ +use std::any::Any; +use std::{collections::HashMap, marker::PhantomData}; + +/// A key that is associated to a type within the HetrogenousMap given by the generic parameter T +pub struct TypedKey { + name: &'static str, + _phantom: PhantomData, +} + +impl TypedKey { + pub const fn new(name: &'static str) -> Self { + Self { + name, + _phantom: PhantomData, + } + } +} + +/// A map that accepts hetrogenous data and stores it in a typesafe way using a typed key +pub struct HetrogenousMap { + storage: HashMap<&'static str, Box>, +} + +impl HetrogenousMap { + pub fn new() -> Self { + Self { + storage: HashMap::new(), + } + } + + /// Insert data of type T + pub fn insert(&mut self, key: TypedKey, dep: T) { + self.storage.insert(key.name, Box::new(dep)); + } + + /// Get data of type T + pub fn get(&self, key: TypedKey) -> Option<&T> { + self.storage.get(key.name)?.downcast_ref() + } + + /// Search for data that holds data under the given key name + pub fn contains(&self, name: &'static str) -> bool { + self.storage.contains_key(name) + } + + /// Get a list of all key names + pub fn keys(&self) -> Vec { + self.storage.keys().map(|&k| k.to_string()).collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + + // Define test keys + const STRING_KEY: TypedKey = TypedKey::new("string_value"); + const INT_KEY: TypedKey = TypedKey::new("int_value"); + const FLOAT_KEY: TypedKey = TypedKey::new("float_value"); + const VEC_KEY: TypedKey> = TypedKey::new("vec_value"); + const ARC_KEY: TypedKey> = TypedKey::new("arc_value"); + + #[test] + fn test_basic_insert_and_get() { + let mut map = HetrogenousMap::new(); + map.insert(STRING_KEY, "hello".to_string()); + map.insert(INT_KEY, 42); + + assert_eq!(map.get(STRING_KEY), Some(&"hello".to_string())); + assert_eq!(map.get(INT_KEY), Some(&42)); + } + + #[test] + fn test_overwrite_value() { + let mut map = HetrogenousMap::new(); + map.insert(INT_KEY, 42); + map.insert(INT_KEY, 24); + + assert_eq!(map.get(INT_KEY), Some(&24)); + } + + #[test] + fn test_get_nonexistent_key() { + let map = HetrogenousMap::new(); + assert_eq!(map.get(STRING_KEY), None); + } + + #[test] + fn test_contains() { + let mut map = HetrogenousMap::new(); + map.insert(STRING_KEY, "test".to_string()); + + assert!(map.contains("string_value")); + assert!(!map.contains("nonexistent")); + } + + #[test] + fn test_keys() { + let mut map = HetrogenousMap::new(); + map.insert(STRING_KEY, "test".to_string()); + map.insert(INT_KEY, 42); + + let mut keys = map.keys(); + keys.sort(); // Sort for deterministic comparison + assert_eq!(keys, vec!["int_value", "string_value"]); + } + + #[test] + fn test_complex_types() { + let mut map = HetrogenousMap::new(); + + // Test with Vec + let vec_data = vec![1, 2, 3]; + map.insert(VEC_KEY, vec_data.clone()); + assert_eq!(map.get(VEC_KEY), Some(&vec_data)); + + // Test with Arc + let arc_data = Arc::new("shared data".to_string()); + map.insert(ARC_KEY, arc_data.clone()); + assert_eq!(map.get(ARC_KEY).map(|a| a.as_str()), Some("shared data")); + } + + #[test] + fn test_multiple_types() { + let mut map = HetrogenousMap::new(); + + map.insert(STRING_KEY, "string".to_string()); + map.insert(INT_KEY, 42); + map.insert(FLOAT_KEY, 3.14); + + assert_eq!(map.get(STRING_KEY), Some(&"string".to_string())); + assert_eq!(map.get(INT_KEY), Some(&42)); + assert_eq!(map.get(FLOAT_KEY), Some(&3.14)); + } + + // This test verifies that Send + Sync bounds work correctly + #[test] + fn test_thread_safety() { + use std::thread; + + let mut map = HetrogenousMap::new(); + map.insert(STRING_KEY, "test".to_string()); + + let map_arc = Arc::new(map); + let map_clone = map_arc.clone(); + + let handle = thread::spawn(move || { + assert_eq!(map_clone.get(STRING_KEY), Some(&"test".to_string())); + }); + + handle.join().unwrap(); + } +} diff --git a/packages/ciphernode/router/src/hooks.rs b/packages/ciphernode/router/src/hooks.rs deleted file mode 100644 index 5752542f..00000000 --- a/packages/ciphernode/router/src/hooks.rs +++ /dev/null @@ -1,419 +0,0 @@ -use crate::{E3Feature, E3RequestContext, E3RequestContextSnapshot, RepositoriesFactory}; -use actix::{Actor, Addr}; -use aggregator::{ - PlaintextAggregator, PlaintextAggregatorParams, PlaintextAggregatorState, PublicKeyAggregator, - PublicKeyAggregatorParams, PublicKeyAggregatorState, -}; -use anyhow::{anyhow, Result}; -use async_trait::async_trait; -use cipher::Cipher; -use data::{AutoPersist, FromSnapshotWithParams, Snapshot}; -use enclave_core::{BusError, E3Requested, EnclaveErrorType, EnclaveEvent, EventBus}; -use fhe::{Fhe, SharedRng}; -use keyshare::{Keyshare, KeyshareParams}; -use sortition::Sortition; -use std::sync::Arc; - -/// TODO: move these to each package with access on MyStruct::launcher() -pub struct FheFeature { - rng: SharedRng, - bus: Addr, -} - -impl FheFeature { - pub fn create(bus: &Addr, rng: &SharedRng) -> Box { - Box::new(Self { - rng: rng.clone(), - bus: bus.clone(), - }) - } -} - -const ERROR_FHE_FAILED_TO_DECODE: &str = "Failed to decode encoded FHE params"; - -#[async_trait] -impl E3Feature for FheFeature { - fn on_event(&self, ctx: &mut crate::E3RequestContext, evt: &EnclaveEvent) { - // Saving the fhe on Committee Requested - let EnclaveEvent::E3Requested { data, .. } = evt else { - return; - }; - - let E3Requested { - params, - seed, - e3_id, - .. - } = data.clone(); - - let Ok(fhe_inner) = Fhe::from_encoded(¶ms, seed, self.rng.clone()) else { - self.bus.err( - EnclaveErrorType::KeyGeneration, - anyhow!(ERROR_FHE_FAILED_TO_DECODE), - ); - return; - }; - - let fhe = Arc::new(fhe_inner); - - // FHE doesn't implement Checkpoint so we are going to store it manually - let Ok(snapshot) = fhe.snapshot() else { - self.bus.err( - EnclaveErrorType::KeyGeneration, - anyhow!("Failed to get snapshot"), - ); - return; - }; - ctx.repositories().fhe(&e3_id).write(&snapshot); - - let _ = ctx.set_fhe(fhe); - } - - async fn hydrate( - &self, - ctx: &mut E3RequestContext, - snapshot: &E3RequestContextSnapshot, - ) -> Result<()> { - // No ID on the snapshot -> bail without reporting - if !snapshot.fhe { - return Ok(()); - }; - - // No Snapshot returned from the store -> bail without reporting - let Some(snap) = ctx.repositories().fhe(&ctx.e3_id).read().await? else { - return Ok(()); - }; - - let value = Arc::new(Fhe::from_snapshot(self.rng.clone(), snap).await?); - ctx.set_fhe(value); - - Ok(()) - } -} - -pub struct KeyshareFeature { - bus: Addr, - address: String, - cipher: Arc, -} - -impl KeyshareFeature { - pub fn create(bus: &Addr, address: &str, cipher: &Arc) -> Box { - Box::new(Self { - bus: bus.clone(), - address: address.to_owned(), - cipher: cipher.to_owned(), - }) - } -} - -const ERROR_KEYSHARE_FHE_MISSING: &str = - "Could not create Keyshare because the fhe instance it depends on was not set on the context."; - -#[async_trait] -impl E3Feature for KeyshareFeature { - fn on_event(&self, ctx: &mut E3RequestContext, evt: &EnclaveEvent) { - // Save Ciphernode on CiphernodeSelected - let EnclaveEvent::CiphernodeSelected { data, .. } = evt else { - return; - }; - - let Some(fhe) = ctx.get_fhe() else { - self.bus.err( - EnclaveErrorType::KeyGeneration, - anyhow!(ERROR_KEYSHARE_FHE_MISSING), - ); - return; - }; - - let e3_id = data.clone().e3_id; - let repo = ctx.repositories().keyshare(&e3_id); - let container = repo.send(None); - - ctx.set_keyshare( - Keyshare::new(KeyshareParams { - bus: self.bus.clone(), - secret: container, - fhe: fhe.clone(), - address: self.address.clone(), - cipher: self.cipher.clone(), - }) - .start(), - ); - } - - async fn hydrate( - &self, - ctx: &mut E3RequestContext, - snapshot: &E3RequestContextSnapshot, - ) -> Result<()> { - // No ID on the snapshot -> bail - if !snapshot.keyshare { - return Ok(()); - }; - - let sync_secret = ctx.repositories().keyshare(&snapshot.e3_id).load().await?; - - // No Snapshot returned from the sync_secret -> bail - if !sync_secret.has() { - return Ok(()); - }; - - // Get deps - let Some(fhe) = ctx.fhe.clone() else { - self.bus.err( - EnclaveErrorType::KeyGeneration, - anyhow!(ERROR_KEYSHARE_FHE_MISSING), - ); - return Ok(()); - }; - - // Construct from snapshot - let value = Keyshare::new(KeyshareParams { - fhe, - bus: self.bus.clone(), - secret: sync_secret, - address: self.address.clone(), - cipher: self.cipher.clone(), - }) - .start(); - - // send to context - ctx.set_keyshare(value); - - Ok(()) - } -} -pub struct PlaintextAggregatorFeature { - bus: Addr, - sortition: Addr, -} -impl PlaintextAggregatorFeature { - pub fn create(bus: &Addr, sortition: &Addr) -> Box { - Box::new(Self { - bus: bus.clone(), - sortition: sortition.clone(), - }) - } -} - -const ERROR_PLAINTEXT_FHE_MISSING:&str = "Could not create PlaintextAggregator because the fhe instance it depends on was not set on the context."; -const ERROR_PLAINTEXT_META_MISSING:&str = "Could not create PlaintextAggregator because the meta instance it depends on was not set on the context."; - -#[async_trait] -impl E3Feature for PlaintextAggregatorFeature { - fn on_event(&self, ctx: &mut E3RequestContext, evt: &EnclaveEvent) { - // Save plaintext aggregator - let EnclaveEvent::CiphertextOutputPublished { data, .. } = evt else { - return; - }; - - let Some(fhe) = ctx.get_fhe() else { - self.bus.err( - EnclaveErrorType::PlaintextAggregation, - anyhow!(ERROR_PLAINTEXT_FHE_MISSING), - ); - return; - }; - - let Some(ref meta) = ctx.get_meta() else { - self.bus.err( - EnclaveErrorType::PlaintextAggregation, - anyhow!(ERROR_PLAINTEXT_META_MISSING), - ); - return; - }; - - let e3_id = data.e3_id.clone(); - let repo = ctx.repositories().plaintext(&e3_id); - let sync_state = repo.send(Some(PlaintextAggregatorState::init( - meta.threshold_m, - meta.seed, - data.ciphertext_output.clone(), - ))); - - ctx.set_plaintext( - PlaintextAggregator::new( - PlaintextAggregatorParams { - fhe: fhe.clone(), - bus: self.bus.clone(), - sortition: self.sortition.clone(), - e3_id: e3_id.clone(), - src_chain_id: meta.src_chain_id, - }, - sync_state, - ) - .start(), - ); - } - - async fn hydrate( - &self, - ctx: &mut E3RequestContext, - snapshot: &E3RequestContextSnapshot, - ) -> Result<()> { - // No ID on the snapshot -> bail - if !snapshot.plaintext { - return Ok(()); - } - - let repo = ctx.repositories().plaintext(&snapshot.e3_id); - let sync_state = repo.load().await?; - - // No Snapshot returned from the store -> bail - if !sync_state.has() { - return Ok(()); - }; - - // Get deps - let Some(fhe) = ctx.get_fhe() else { - self.bus.err( - EnclaveErrorType::PlaintextAggregation, - anyhow!(ERROR_PLAINTEXT_FHE_MISSING), - ); - return Ok(()); - }; - - let Some(ref meta) = ctx.get_meta() else { - self.bus.err( - EnclaveErrorType::PlaintextAggregation, - anyhow!(ERROR_PLAINTEXT_META_MISSING), - ); - return Ok(()); - }; - - let value = PlaintextAggregator::new( - PlaintextAggregatorParams { - fhe: fhe.clone(), - bus: self.bus.clone(), - sortition: self.sortition.clone(), - e3_id: ctx.e3_id.clone(), - src_chain_id: meta.src_chain_id, - }, - sync_state, - ) - .start(); - - // send to context - ctx.set_plaintext(value); - - Ok(()) - } -} - -pub struct PublicKeyAggregatorFeature { - bus: Addr, - sortition: Addr, -} - -impl PublicKeyAggregatorFeature { - pub fn create(bus: &Addr, sortition: &Addr) -> Box { - Box::new(Self { - bus: bus.clone(), - sortition: sortition.clone(), - }) - } -} - -const ERROR_PUBKEY_FHE_MISSING:&str = "Could not create PublicKeyAggregator because the fhe instance it depends on was not set on the context."; -const ERROR_PUBKEY_META_MISSING:&str = "Could not create PublicKeyAggregator because the meta instance it depends on was not set on the context."; - -#[async_trait] -impl E3Feature for PublicKeyAggregatorFeature { - fn on_event(&self, ctx: &mut E3RequestContext, evt: &EnclaveEvent) { - // Saving the publickey aggregator with deps on E3Requested - let EnclaveEvent::E3Requested { data, .. } = evt else { - return; - }; - - let Some(fhe) = ctx.get_fhe() else { - self.bus.err( - EnclaveErrorType::PublickeyAggregation, - anyhow!(ERROR_PUBKEY_FHE_MISSING), - ); - return; - }; - let Some(ref meta) = ctx.get_meta() else { - self.bus.err( - EnclaveErrorType::PublickeyAggregation, - anyhow!(ERROR_PUBKEY_META_MISSING), - ); - return; - }; - - let e3_id = data.e3_id.clone(); - let repo = ctx.repositories().publickey(&e3_id); - let sync_state = repo.send(Some(PublicKeyAggregatorState::init( - meta.threshold_m, - meta.seed, - ))); - ctx.set_publickey( - PublicKeyAggregator::new( - PublicKeyAggregatorParams { - fhe: fhe.clone(), - bus: self.bus.clone(), - sortition: self.sortition.clone(), - e3_id, - src_chain_id: meta.src_chain_id, - }, - sync_state, - ) - .start(), - ); - } - - async fn hydrate( - &self, - ctx: &mut E3RequestContext, - snapshot: &E3RequestContextSnapshot, - ) -> Result<()> { - // No ID on the snapshot -> bail - if !snapshot.publickey { - return Ok(()); - }; - - let repo = ctx.repositories().publickey(&ctx.e3_id); - let sync_state = repo.load().await?; - - // No Snapshot returned from the store -> bail - if !sync_state.has() { - return Ok(()); - }; - - // Get deps - let Some(fhe) = ctx.fhe.clone() else { - self.bus.err( - EnclaveErrorType::PublickeyAggregation, - anyhow!(ERROR_PUBKEY_FHE_MISSING), - ); - - return Ok(()); - }; - - let Some(meta) = ctx.meta.clone() else { - self.bus.err( - EnclaveErrorType::PublickeyAggregation, - anyhow!(ERROR_PUBKEY_META_MISSING), - ); - - return Ok(()); - }; - - let value = PublicKeyAggregator::new( - PublicKeyAggregatorParams { - fhe: fhe.clone(), - bus: self.bus.clone(), - sortition: self.sortition.clone(), - e3_id: ctx.e3_id.clone(), - src_chain_id: meta.src_chain_id, - }, - sync_state, - ) - .start(); - - // send to context - ctx.set_publickey(value); - - Ok(()) - } -} diff --git a/packages/ciphernode/router/src/keyshare_feature.rs b/packages/ciphernode/router/src/keyshare_feature.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/ciphernode/router/src/lib.rs b/packages/ciphernode/router/src/lib.rs index 77f7aa5c..a51bcee3 100644 --- a/packages/ciphernode/router/src/lib.rs +++ b/packages/ciphernode/router/src/lib.rs @@ -1,13 +1,11 @@ -mod ciphernode_selector; mod committee_meta; mod context; mod e3_request_router; -mod hooks; -mod repositories; +mod hetrogenous_map; +mod repo; -pub use ciphernode_selector::*; pub use committee_meta::*; pub use context::*; pub use e3_request_router::*; -pub use hooks::*; -pub use repositories::*; +pub use hetrogenous_map::*; +pub use repo::*; diff --git a/packages/ciphernode/router/src/repo.rs b/packages/ciphernode/router/src/repo.rs new file mode 100644 index 00000000..de6c11b2 --- /dev/null +++ b/packages/ciphernode/router/src/repo.rs @@ -0,0 +1,35 @@ +use config::StoreKeys; +use data::{Repositories, Repository}; +use enclave_core::E3id; + +use crate::{CommitteeMeta, E3RequestContextSnapshot, E3RequestRouterSnapshot}; + +pub trait MetaRepositoryFactory { + fn meta(&self, e3_id: &E3id) -> Repository; +} + +impl MetaRepositoryFactory for Repositories { + fn meta(&self, e3_id: &E3id) -> Repository { + Repository::new(self.store.scope(StoreKeys::meta(e3_id))) + } +} + +pub trait ContextRepositoryFactory { + fn context(&self, e3_id: &E3id) -> Repository; +} + +impl ContextRepositoryFactory for Repositories { + fn context(&self, e3_id: &E3id) -> Repository { + Repository::new(self.store.scope(StoreKeys::context(e3_id))) + } +} + +pub trait RouterRepositoryFactory { + fn router(&self) -> Repository; +} + +impl RouterRepositoryFactory for Repositories { + fn router(&self) -> Repository { + Repository::new(self.store.scope(StoreKeys::router())) + } +} diff --git a/packages/ciphernode/router/src/repositories.rs b/packages/ciphernode/router/src/repositories.rs deleted file mode 100644 index dc72c2b9..00000000 --- a/packages/ciphernode/router/src/repositories.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::{CommitteeMeta, E3RequestContextSnapshot, E3RequestRouterSnapshot}; -use aggregator::{PlaintextAggregatorState, PublicKeyAggregatorState}; -use config::StoreKeys; -use data::{DataStore, Repository}; -use enclave_core::E3id; -use evm::EvmEventReaderState; -use fhe::FheSnapshot; -use sortition::SortitionModule; - -pub struct Repositories { - store: DataStore, -} - -impl From for Repositories { - fn from(value: DataStore) -> Self { - Repositories { store: value } - } -} -impl From<&DataStore> for Repositories { - fn from(value: &DataStore) -> Self { - Repositories { - store: value.clone(), - } - } -} - -impl Repositories { - pub fn new(store: DataStore) -> Self { - Repositories { store } - } -} - -impl From> for Repositories { - fn from(value: Repository) -> Self { - let store: DataStore = value.into(); - store.into() - } -} - -impl Repositories { - pub fn keyshare(&self, e3_id: &E3id) -> Repository> { - Repository::new(self.store.scope(StoreKeys::keyshare(e3_id))) - } - - pub fn plaintext(&self, e3_id: &E3id) -> Repository { - Repository::new(self.store.scope(StoreKeys::plaintext(e3_id))) - } - - pub fn publickey(&self, e3_id: &E3id) -> Repository { - Repository::new(self.store.scope(StoreKeys::publickey(e3_id))) - } - - pub fn fhe(&self, e3_id: &E3id) -> Repository { - Repository::new(self.store.scope(StoreKeys::fhe(e3_id))) - } - - pub fn meta(&self, e3_id: &E3id) -> Repository { - Repository::new(self.store.scope(StoreKeys::meta(e3_id))) - } - - pub fn context(&self, e3_id: &E3id) -> Repository { - Repository::new(self.store.scope(StoreKeys::context(e3_id))) - } - - pub fn router(&self) -> Repository { - Repository::new(self.store.scope(StoreKeys::router())) - } - - pub fn sortition(&self) -> Repository { - Repository::new(self.store.scope(StoreKeys::sortition())) - } - - pub fn eth_private_key(&self) -> Repository> { - Repository::new(self.store.scope(StoreKeys::eth_private_key())) - } - - pub fn libp2p_keypair(&self) -> Repository> { - Repository::new(self.store.scope(StoreKeys::libp2p_keypair())) - } - - pub fn enclave_sol_reader(&self, chain_id: u64) -> Repository { - Repository::new(self.store.scope(StoreKeys::enclave_sol_reader(chain_id))) - } - pub fn ciphernode_registry_reader(&self, chain_id: u64) -> Repository { - Repository::new( - self.store - .scope(StoreKeys::ciphernode_registry_reader(chain_id)), - ) - } -} - -pub trait RepositoriesFactory { - fn repositories(&self) -> Repositories; -} - -impl RepositoriesFactory for DataStore { - fn repositories(&self) -> Repositories { - self.into() - } -} - -impl RepositoriesFactory for Repository { - fn repositories(&self) -> Repositories { - let store: DataStore = self.into(); - store.repositories() - } -} diff --git a/packages/ciphernode/sortition/Cargo.toml b/packages/ciphernode/sortition/Cargo.toml index 383e1955..bed3b0a7 100644 --- a/packages/ciphernode/sortition/Cargo.toml +++ b/packages/ciphernode/sortition/Cargo.toml @@ -13,6 +13,7 @@ actix = { workspace = true } alloy = { workspace = true, features = ["full"] } anyhow = { workspace = true } async-trait = { workspace = true } +config = { workspace = true } data = { path = "../data" } enclave-core = { path = "../core" } num = { workspace = true } diff --git a/packages/ciphernode/router/src/ciphernode_selector.rs b/packages/ciphernode/sortition/src/ciphernode_selector.rs similarity index 98% rename from packages/ciphernode/router/src/ciphernode_selector.rs rename to packages/ciphernode/sortition/src/ciphernode_selector.rs index 96048a67..72ca1d50 100644 --- a/packages/ciphernode/router/src/ciphernode_selector.rs +++ b/packages/ciphernode/sortition/src/ciphernode_selector.rs @@ -1,8 +1,8 @@ +use crate::{GetHasNode, Sortition}; /// CiphernodeSelector is an actor that determines if a ciphernode is part of a committee and if so /// forwards a CiphernodeSelected event to the event bus use actix::prelude::*; use enclave_core::{CiphernodeSelected, E3Requested, EnclaveEvent, EventBus, Shutdown, Subscribe}; -use sortition::{GetHasNode, Sortition}; use tracing::info; pub struct CiphernodeSelector { diff --git a/packages/ciphernode/sortition/src/lib.rs b/packages/ciphernode/sortition/src/lib.rs index 792f64fc..7021bb9f 100644 --- a/packages/ciphernode/sortition/src/lib.rs +++ b/packages/ciphernode/sortition/src/lib.rs @@ -2,10 +2,14 @@ #![crate_type = "lib"] // #![warn(missing_docs, unused_imports)] +mod ciphernode_selector; mod distance; mod index; +mod repo; mod sortition; +pub use ciphernode_selector::*; pub use distance::*; pub use index::*; +pub use repo::*; pub use sortition::*; diff --git a/packages/ciphernode/sortition/src/repo.rs b/packages/ciphernode/sortition/src/repo.rs new file mode 100644 index 00000000..2b84c184 --- /dev/null +++ b/packages/ciphernode/sortition/src/repo.rs @@ -0,0 +1,14 @@ +use config::StoreKeys; +use data::{Repositories, Repository}; + +use crate::SortitionModule; + +pub trait SortitionRepositoryFactory { + fn sortition(&self) -> Repository; +} + +impl SortitionRepositoryFactory for Repositories { + fn sortition(&self) -> Repository { + Repository::new(self.store.scope(StoreKeys::sortition())) + } +} diff --git a/packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs b/packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs index b2011ed5..2a04720a 100644 --- a/packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs +++ b/packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs @@ -1,4 +1,6 @@ +use aggregator::{PlaintextAggregatorFeature, PublicKeyAggregatorFeature}; use cipher::Cipher; +use data::RepositoriesFactory; use data::{DataStore, InMemStore}; use enclave_core::{ CiphernodeAdded, CiphernodeSelected, CiphertextOutputPublished, DecryptionshareCreated, @@ -6,17 +8,16 @@ use enclave_core::{ KeyshareCreated, OrderedSet, PlaintextAggregated, PublicKeyAggregated, ResetHistory, Seed, Shutdown, }; -use fhe::{setup_crp_params, ParamsWithCrp, SharedRng}; +use fhe::{setup_crp_params, FheFeature, ParamsWithCrp, SharedRng}; +use keyshare::KeyshareFeature; use logger::SimpleLogger; -use net::{correlation_id::CorrelationId, events::NetworkPeerEvent, NetworkManager}; -use router::{ - CiphernodeSelector, E3RequestRouter, FheFeature, KeyshareFeature, PlaintextAggregatorFeature, - PublicKeyAggregatorFeature, RepositoriesFactory, -}; -use sortition::Sortition; +use net::{events::NetworkPeerEvent, NetworkManager}; +use router::E3RequestRouter; +use sortition::SortitionRepositoryFactory; +use sortition::{CiphernodeSelector, Sortition}; use actix::prelude::*; -use alloy::{primitives::Address, signers::k256::sha2::digest::Reset}; +use alloy::primitives::Address; use anyhow::*; use fhe_rs::{ bfv::{BfvParameters, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}, @@ -26,7 +27,7 @@ use fhe_traits::{FheEncoder, FheEncrypter, Serialize}; use rand::Rng; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; -use std::{env, path::Path, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use tokio::sync::{broadcast, Mutex}; use tokio::{sync::mpsc, time::sleep}; @@ -456,7 +457,6 @@ async fn test_stopped_keyshares_retain_state() -> Result<()> { EnclaveEvent::PlaintextAggregated { data, .. } => Some(data.decrypted_output.clone()), _ => None, }); - assert_eq!(actual, Some(expected)); Ok(())