From 7a9b989db90e9851caa96a77e6f5536be9ea6cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 5 Jan 2024 17:34:36 +0000 Subject: [PATCH] [wip]: improving error recovery during key submission phase --- .../coconut-dkg/src/dealers/transactions.rs | 2 + nym-api/src/coconut/dkg/controller/keys.rs | 55 +- nym-api/src/coconut/dkg/controller/mod.rs | 51 +- nym-api/src/coconut/dkg/dealing.rs | 27 +- nym-api/src/coconut/dkg/public_key.rs | 136 ++- .../coconut/dkg/{state.rs => state/mod.rs} | 83 +- nym-api/src/coconut/dkg/state/registration.rs | 17 + nym-api/src/coconut/dkg/verification_key.rs | 472 +++++--- nym-api/src/coconut/keypair.rs | 26 +- nym-api/src/coconut/tests/mod.rs | 1072 +++++++++-------- nym-api/src/main.rs | 3 +- 11 files changed, 1102 insertions(+), 842 deletions(-) rename nym-api/src/coconut/dkg/{state.rs => state/mod.rs} (86%) create mode 100644 nym-api/src/coconut/dkg/state/registration.rs diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index 112a350313..7c47f655d4 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -34,6 +34,8 @@ fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<() Ok(()) } +// future optimisation: +// for a recurring dealer just let it refresh the keys without having to do all the storage operations pub fn try_add_dealer( mut deps: DepsMut<'_>, info: MessageInfo, diff --git a/nym-api/src/coconut/dkg/controller/keys.rs b/nym-api/src/coconut/dkg/controller/keys.rs index f1528d305c..eefd277937 100644 --- a/nym-api/src/coconut/dkg/controller/keys.rs +++ b/nym-api/src/coconut/dkg/controller/keys.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::support::config; -use anyhow::Context; +use anyhow::{anyhow, bail, Context}; +use nym_coconut_dkg_common::types::EpochId; use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use rand::{CryptoRng, RngCore}; +use thiserror::__private::AsDisplay; pub(crate) fn init_bte_keypair( rng: &mut R, @@ -18,8 +20,8 @@ pub(crate) fn init_bte_keypair( &config.storage_paths.decryption_key_path, &config.storage_paths.public_key_with_proof_path, ), - )?; - Ok(()) + ) + .context("DKG BTE keypair store failure") } pub(crate) fn load_bte_keypair(config: &config::CoconutSigner) -> anyhow::Result { @@ -43,3 +45,50 @@ pub(crate) fn load_coconut_keypair_if_exists( .context("coconut keypair load failure") .map(Some) } + +pub(crate) fn persist_coconut_keypair( + keys: &nym_coconut_interface::KeyPair, + store_paths: &nym_pemstore::KeyPairPath, +) -> anyhow::Result<()> { + nym_pemstore::store_keypair(keys, store_paths).context("coconut keypair store failure") +} + +pub(crate) fn archive_coconut_keypair( + store_paths: &nym_pemstore::KeyPairPath, + epoch_id: EpochId, +) -> anyhow::Result<()> { + if !store_paths.private_key_path.exists() { + bail!( + "private key does not exist at {}", + store_paths.private_key_path.as_display() + ) + } + + let dir = store_paths + .private_key_path + .parent() + .ok_or(anyhow!("the private key does not have a valid parent"))?; + let filename = store_paths + .private_key_path + .file_name() + .ok_or(anyhow!("the private key does not have a filename"))? + .to_str() + .ok_or(anyhow!("the key filename is not valid UTF8"))?; + let archive_path = dir.join(format!("epoch-{epoch_id}-{filename}.archived")); + std::fs::rename(&store_paths.private_key_path, archive_path)?; + + let dir = store_paths + .public_key_path + .parent() + .ok_or(anyhow!("the public key does not have a valid parent"))?; + let filename = store_paths + .public_key_path + .file_name() + .ok_or(anyhow!("the public key does not have a filename"))? + .to_str() + .ok_or(anyhow!("the key filename is not valid UTF8"))?; + let archive_path = dir.join(format!("epoch-{epoch_id}-{filename}.archived")); + std::fs::rename(&store_paths.public_key_path, archive_path)?; + + Ok(()) +} diff --git a/nym-api/src/coconut/dkg/controller/mod.rs b/nym-api/src/coconut/dkg/controller/mod.rs index 3ccb61e7ee..660fcaf5ae 100644 --- a/nym-api/src/coconut/dkg/controller/mod.rs +++ b/nym-api/src/coconut/dkg/controller/mod.rs @@ -8,8 +8,7 @@ use crate::coconut::dkg::verification_key::{ verification_key_finalization, verification_key_validation, }; use crate::coconut::dkg::{ - dealing::dealing_exchange, public_key::public_key_submission, - verification_key::verification_key_submission, + dealing::dealing_exchange, verification_key::verification_key_submission, }; use crate::coconut::keypair::KeyPair as CoconutKeyPair; use crate::nyxd; @@ -19,6 +18,7 @@ use nym_coconut_dkg_common::types::{Epoch, EpochId, EpochState}; use nym_crypto::asymmetric::identity; use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use nym_task::{TaskClient, TaskManager}; +use rand::rngs::OsRng; use rand::{CryptoRng, RngCore}; use std::path::PathBuf; use std::time::Duration; @@ -28,11 +28,11 @@ use tokio::time::interval; mod error; pub(crate) mod keys; -pub(crate) struct DkgController { - dkg_client: DkgClient, +pub(crate) struct DkgController { + pub(crate) dkg_client: DkgClient, secret_key_path: PathBuf, verification_key_path: PathBuf, - state: State, + pub(crate) state: State, rng: R, polling_rate: Duration, } @@ -71,6 +71,10 @@ impl DkgController { }) } + pub(crate) fn coconut_keypaths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new(&self.secret_key_path, &self.verification_key_path) + } + fn persist_state(&self) -> Result<(), DkgError> { // if !self.state.coconut_keypair_is_some().await { // // Delete the files just in case the process is killed before the new keys are generated @@ -124,13 +128,17 @@ impl DkgController { } async fn handle_awaiting_initialisation(&mut self) -> Result<(), DkgError> { - info!("DKG hasn't been initialised yet"); - return Ok(()); + info!("DKG hasn't been initialised yet - nothing to do"); + Ok(()) } - async fn handle_key_submission(&mut self, resharing: bool) -> Result<(), DkgError> { + async fn handle_key_submission( + &mut self, + epoch_id: EpochId, + resharing: bool, + ) -> Result<(), DkgError> { debug!("DKG: public key submission (resharing: {resharing})"); - public_key_submission(&self.dkg_client, &mut self.state, resharing) + self.public_key_submission(epoch_id, resharing) .await .map_err(|source| DkgError::PublicKeySubmissionFailure { source }) } @@ -152,12 +160,10 @@ impl DkgController { epoch_id: EpochId, resharing: bool, ) -> Result<(), DkgError> { - debug!("DKG: verification key sumbission (resharing: {resharing})"); + debug!("DKG: verification key submission (resharing: {resharing})"); - let keypair_path = nym_pemstore::KeyPairPath::new( - self.secret_key_path.clone(), - self.verification_key_path.clone(), - ); + let keypair_path = + nym_pemstore::KeyPairPath::new(&self.secret_key_path, &self.verification_key_path); verification_key_submission( &self.dkg_client, &mut self.state, @@ -219,7 +225,8 @@ impl DkgController { match epoch.state { EpochState::WaitingInitialisation => self.handle_awaiting_initialisation().await?, EpochState::PublicKeySubmission { resharing } => { - self.handle_key_submission(resharing).await? + self.handle_key_submission(epoch.epoch_id, resharing) + .await? } EpochState::DealingExchange { resharing } => { self.handle_dealing_exchange(resharing).await? @@ -294,3 +301,17 @@ impl DkgController { Ok(()) } } + +#[cfg(test)] +impl DkgController { + pub(crate) fn test_mock(dkg_client: DkgClient, state: State) -> DkgController { + DkgController { + dkg_client, + secret_key_path: Default::default(), + verification_key_path: Default::default(), + state, + rng: OsRng, + polling_rate: Default::default(), + } + } +} diff --git a/nym-api/src/coconut/dkg/dealing.rs b/nym-api/src/coconut/dkg/dealing.rs index 0c2cac55b5..7492af8167 100644 --- a/nym-api/src/coconut/dkg/dealing.rs +++ b/nym-api/src/coconut/dkg/dealing.rs @@ -47,18 +47,19 @@ pub(crate) async fn dealing_exchange( let prior_resharing_secrets = if let Some(mut keypair) = state.take_coconut_keypair().await { // Double check that we are in resharing mode if resharing { - let sk = keypair.secret_key(); - if sk.size() + 1 != expected_key_size as usize { - return Err(CoconutError::CorruptedCoconutKeyPair); - } - - let (x, mut scalars) = sk.into_raw(); - - // We can now erase the keypair from memory - debug!("Removing coconut keypair from memory"); - keypair.zeroize(); - scalars.push(x); - scalars + todo!() + // let sk = keypair.secret_key(); + // if sk.size() + 1 != expected_key_size as usize { + // return Err(CoconutError::CorruptedCoconutKeyPair); + // } + // + // let (x, mut scalars) = sk.into_raw(); + // + // // We can now erase the keypair from memory + // debug!("Removing coconut keypair from memory"); + // keypair.zeroize(); + // scalars.push(x); + // scalars } else { log::warn!("Coconut key hasn't been reset in memory. The state might be corrupt"); vec![] @@ -340,7 +341,7 @@ pub(crate) mod tests { let params = dkg::params(); let mut keys = ttp_keygen(&Parameters::new(4).unwrap(), 3, 4).unwrap(); let coconut_keypair = KeyPair::new(); - coconut_keypair.set(Some(keys.pop().unwrap())).await; + coconut_keypair.set(0, keys.pop().unwrap()).await; let identity_keypair = identity::KeyPair::new(&mut thread_rng()); let mut state = State::new( diff --git a/nym-api/src/coconut/dkg/public_key.rs b/nym-api/src/coconut/dkg/public_key.rs index a03e6c6bb9..953264a922 100644 --- a/nym-api/src/coconut/dkg/public_key.rs +++ b/nym-api/src/coconut/dkg/public_key.rs @@ -1,74 +1,73 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::coconut::dkg::client::DkgClient; -use crate::coconut::dkg::state::State; +use crate::coconut::dkg::controller::DkgController; use crate::coconut::error::CoconutError; use log::debug; -use nym_coconut_dkg_common::dealer::DealerType; +use nym_coconut_dkg_common::types::EpochId; +use rand::{CryptoRng, RngCore}; -pub(crate) async fn public_key_submission( - dkg_client: &DkgClient, - state: &mut State, - resharing: bool, -) -> Result<(), CoconutError> { - if state.was_in_progress() { - let own_address = dkg_client.get_address().await.as_ref().to_string(); - let is_initial_dealer = dkg_client - .get_initial_dealers() - .await? - .map(|data| data.initial_dealers.iter().any(|d| *d == own_address)) - .unwrap_or(false); - let reset_coconut_keypair = !resharing || !is_initial_dealer; - debug!( - "Resetting state, with coconut keypair reset: {}", - reset_coconut_keypair - ); - state.reset_persistent(reset_coconut_keypair).await; - } - if state.node_index().is_some() { - debug!("Node index was set previously, nothing to do"); - return Ok(()); - } +impl DkgController { + pub(crate) async fn public_key_submission( + &mut self, + epoch_id: EpochId, + resharing: bool, + ) -> Result<(), CoconutError> { + self.state.init_dkg_state(epoch_id); + let registration_state = self.state.registration_state(epoch_id); - let bte_key = bs58::encode(&state.dkg_keypair().public_key().to_bytes()).into_string(); - let dealer_details = dkg_client.get_self_registered_dealer_details().await?; - let index = if let Some(details) = dealer_details.details { - if dealer_details.dealer_type == DealerType::Past { - // If it was a dealer in a previous epoch, re-register it for this epoch - debug!("Registering for the current DKG round, with keys from a previous epoch"); - dkg_client - .register_dealer( - bte_key, - state.identity_key().to_base58_string(), - state.announce_address().to_string(), - resharing, - ) - .await?; + // check if we have already submitted the key + if registration_state.completed() { + // the only way this could be a false positive is if the chain forked and blocks got reverted, + // but I don't think we have to worry about that + debug!("we have already submitted the keys for this epoch"); + return Ok(()); } - details.assigned_index - } else { - debug!("Registering for the first time to be a dealer"); - // First time registration - dkg_client - .register_dealer( - bte_key, - state.identity_key().to_base58_string(), - state.announce_address().to_string(), - resharing, - ) - .await? - }; - state.set_node_index(Some(index)); - info!("DKG: Using node index {}", index); - Ok(()) + // if we have coconut keys available, it means we have already completed the DKG before (in previous epoch) + // in which case, archive and reset those keys + if let Some((old_epoch, _)) = self.state.take_coconut_keypair().await { + debug!("resetting and archiving old coconut keypair"); + let store_path = self.coconut_keypaths(); + // archive_coconut_keypair(&store_path, old_epoch)? + // + } + + // FAILURE CASE: + // check if we have already sent the registration transaction, but it timed out or got stuck in the mempool and + // eventually got executed without us knowing about it + // in that case we MUST recover the assigned index since we won't be allowed to register again + let dealer_details = self.dkg_client.get_self_registered_dealer_details().await?; + if dealer_details.dealer_type.is_current() { + if let Some(details) = dealer_details.details { + // the tx did actually go through + self.state.registration_state_mut(epoch_id).assigned_index = + Some(details.assigned_index); + info!("DKG: recovered node index: {}", details.assigned_index); + return Ok(()); + } + } + + let bte_key = bs58::encode(&self.state.dkg_keypair().public_key().to_bytes()).into_string(); + let identity_key = self.state.identity_key().to_base58_string(); + let announce_address = self.state.announce_address().to_string(); + + let assigned_index = self + .dkg_client + .register_dealer(bte_key, identity_key, announce_address, resharing) + .await?; + self.state.registration_state_mut(epoch_id).assigned_index = Some(assigned_index); + info!("DKG: Using node index {assigned_index}"); + + Ok(()) + } } #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::coconut::dkg::state::PersistentState; + use crate::coconut::dkg::client::DkgClient; + use crate::coconut::dkg::state::{PersistentState, State}; use crate::coconut::tests::DummyClient; use crate::coconut::KeyPair; use nym_crypto::asymmetric::identity; @@ -97,30 +96,29 @@ pub(crate) mod tests { *identity_keypair.public_key(), KeyPair::new(), ); + let mut controller = DkgController::test_mock(dkg_client, state); - assert!(dkg_client + assert!(controller + .dkg_client .get_self_registered_dealer_details() .await .unwrap() .details .is_none()); - public_key_submission(&dkg_client, &mut state, false) - .await - .unwrap(); - let client_idx = dkg_client + controller.public_key_submission(0, false).await.unwrap(); + let client_idx = controller + .dkg_client .get_self_registered_dealer_details() .await .unwrap() .details .unwrap() .assigned_index; - assert_eq!(state.node_index().unwrap(), client_idx); + assert_eq!(controller.state.node_index().unwrap(), client_idx); // keeps the same index from chain, not calling register_dealer again - state.set_node_index(None); - public_key_submission(&dkg_client, &mut state, false) - .await - .unwrap(); - assert_eq!(state.node_index().unwrap(), client_idx); + controller.state.set_node_index(None); + controller.public_key_submission(0, false).await.unwrap(); + assert_eq!(controller.state.node_index().unwrap(), client_idx); } } diff --git a/nym-api/src/coconut/dkg/state.rs b/nym-api/src/coconut/dkg/state/mod.rs similarity index 86% rename from nym-api/src/coconut/dkg/state.rs rename to nym-api/src/coconut/dkg/state/mod.rs index 502cfe6798..0a9a43ebe4 100644 --- a/nym-api/src/coconut/dkg/state.rs +++ b/nym-api/src/coconut/dkg/state/mod.rs @@ -1,7 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +mod registration; + use crate::coconut::dkg::complaints::ComplaintReason; +use crate::coconut::dkg::state::registration::RegistrationState; use crate::coconut::error::CoconutError; use crate::coconut::keypair::KeyPair as CoconutKeyPair; use cosmwasm_std::Addr; @@ -11,6 +14,8 @@ use nym_coconut_dkg_common::types::{DealingIndex, EpochId, EpochState}; use nym_crypto::asymmetric::identity; use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof}; use nym_dkg::{Dealing, NodeIndex, RecoveredVerificationKeys, Threshold}; +use nym_validator_client::nyxd::{tx, Hash}; +use rocket::form::validate::Contains; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::{BTreeMap, HashMap}; @@ -271,8 +276,18 @@ impl PersistentState { } } +#[derive(Default)] +pub(crate) struct DkgState { + pub(crate) registration: RegistrationState, +} + pub(crate) struct State { + /// Path to the file containing the persistent state persistent_state_path: PathBuf, + + dkg_instances: HashMap, + + // announce_address: Url, identity_key: identity::PublicKey, dkg_keypair: DkgKeyPair, @@ -298,28 +313,33 @@ impl State { identity_key: identity::PublicKey, coconut_keypair: CoconutKeyPair, ) -> Self { - State { - persistent_state_path, - announce_address, - identity_key, - dkg_keypair, - coconut_keypair, - node_index: persistent_state.node_index, - dealers: persistent_state.dealers, - generated_dealings: persistent_state.generated_dealings, - receiver_index: persistent_state.receiver_index, - threshold: persistent_state.threshold, - recovered_vks: persistent_state.recovered_vks, - proposal_id: persistent_state.proposal_id, - voted_vks: persistent_state.voted_vks, - executed_proposal: persistent_state.executed_proposal, - was_in_progress: persistent_state.was_in_progress, - } + todo!() + // State { + // persistent_state_path, + // announce_address, + // identity_key, + // dkg_keypair, + // coconut_keypair, + // node_index: persistent_state.node_index, + // dealers: persistent_state.dealers, + // generated_dealings: persistent_state.generated_dealings, + // receiver_index: persistent_state.receiver_index, + // threshold: persistent_state.threshold, + // recovered_vks: persistent_state.recovered_vks, + // proposal_id: persistent_state.proposal_id, + // voted_vks: persistent_state.voted_vks, + // executed_proposal: persistent_state.executed_proposal, + // was_in_progress: persistent_state.was_in_progress, + // } + } + + pub fn persist(&self) -> Result<(), CoconutError> { + PersistentState::from(self).save_to_file(self.persistent_state_path()) } pub async fn reset_persistent(&mut self, reset_coconut_keypair: bool) { if reset_coconut_keypair { - self.coconut_keypair.set(None).await; + self.coconut_keypair.invalidate().await; } self.node_index = Default::default(); self.dealers = Default::default(); @@ -332,6 +352,26 @@ impl State { self.was_in_progress = Default::default(); } + pub fn init_dkg_state(&mut self, epoch_id: EpochId) { + if !self.dkg_instances.contains_key(&epoch_id) { + self.dkg_instances.insert(epoch_id, Default::default()); + } + } + + pub fn registration_state(&self, epoch_id: EpochId) -> &RegistrationState { + // safety: before any accessors are called, `init_dkg_state` is used at the beginning of submission handler + &self.dkg_instances[&epoch_id].registration + } + + pub fn registration_state_mut(&mut self, epoch_id: EpochId) -> &mut RegistrationState { + // safety: before any accessors are called, `init_dkg_state` is used at the beginning of submission handler + &mut self.dkg_instances.get_mut(&epoch_id).unwrap().registration + } + + pub fn already_registered(&self, epoch_id: EpochId) -> bool { + self.registration_state(epoch_id).completed() + } + pub fn persistent_state_path(&self) -> &Path { self.persistent_state_path.as_path() } @@ -352,7 +392,7 @@ impl State { self.coconut_keypair.get().await.is_some() } - pub async fn take_coconut_keypair(&self) -> Option { + pub async fn take_coconut_keypair(&self) -> Option<(EpochId, nym_coconut::KeyPair)> { self.coconut_keypair.take().await } @@ -438,9 +478,10 @@ impl State { pub async fn set_coconut_keypair( &mut self, - coconut_keypair: Option, + epoch_id: EpochId, + coconut_keypair: nym_coconut_interface::KeyPair, ) { - self.coconut_keypair.set(coconut_keypair).await + self.coconut_keypair.set(epoch_id, coconut_keypair).await } pub fn set_node_index(&mut self, node_index: Option) { diff --git a/nym-api/src/coconut/dkg/state/registration.rs b/nym-api/src/coconut/dkg/state/registration.rs new file mode 100644 index 0000000000..713a45f980 --- /dev/null +++ b/nym-api/src/coconut/dkg/state/registration.rs @@ -0,0 +1,17 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_dkg::NodeIndex; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct RegistrationState { + pub(crate) assigned_index: Option, +} + +impl RegistrationState { + /// Specifies whether this dealer has already registered in the particular DKG epoch + pub fn completed(&self) -> bool { + self.assigned_index.is_some() + } +} diff --git a/nym-api/src/coconut/dkg/verification_key.rs b/nym-api/src/coconut/dkg/verification_key.rs index 9d0bf01256..f7f7cb4699 100644 --- a/nym-api/src/coconut/dkg/verification_key.rs +++ b/nym-api/src/coconut/dkg/verification_key.rs @@ -243,7 +243,7 @@ pub(crate) async fn verification_key_submission( proposal_id ); state.set_proposal_id(proposal_id); - state.set_coconut_keypair(Some(coconut_keypair)).await; + state.set_coconut_keypair(epoch_id, coconut_keypair).await; info!("DKG: Submitted own verification key"); Ok(()) @@ -352,8 +352,8 @@ pub(crate) async fn verification_key_finalization( #[cfg(test)] pub(crate) mod tests { use super::*; + use crate::coconut::dkg::controller::DkgController; use crate::coconut::dkg::dealing::dealing_exchange; - use crate::coconut::dkg::public_key::public_key_submission; use crate::coconut::dkg::state::PersistentState; use crate::coconut::tests::DummyClient; use crate::coconut::KeyPair; @@ -405,7 +405,7 @@ pub(crate) mod tests { "n1jfrs6cmw9t7dv0x8cgny6geunzjh56n2s89fkv", ]; - async fn prepare_clients_and_states(db: &MockContractDb) -> Vec<(DkgClient, State)> { + async fn prepare_clients_and_states(db: &MockContractDb) -> Vec { let params = dkg::params(); let mut clients_and_states = vec![]; let identity_keypair = identity::KeyPair::new(&mut thread_rng()); @@ -429,52 +429,50 @@ pub(crate) mod tests { *identity_keypair.public_key(), KeyPair::new(), ); - clients_and_states.push((dkg_client, state)); + clients_and_states.push(DkgController::test_mock(dkg_client, state)); } - for (dkg_client, state) in clients_and_states.iter_mut() { - public_key_submission(dkg_client, state, false) - .await - .unwrap(); + for controller in clients_and_states.iter_mut() { + controller.public_key_submission(0, false).await.unwrap(); } clients_and_states } - async fn prepare_clients_and_states_with_dealing( - db: &MockContractDb, - ) -> Vec<(DkgClient, State)> { + async fn prepare_clients_and_states_with_dealing(db: &MockContractDb) -> Vec { let mut clients_and_states = prepare_clients_and_states(db).await; - for (dkg_client, state) in clients_and_states.iter_mut() { - dealing_exchange(dkg_client, state, OsRng, false) + for controller in clients_and_states.iter_mut() { + dealing_exchange(&controller.dkg_client, &mut controller.state, OsRng, false) .await .unwrap(); } clients_and_states } - async fn prepare_clients_and_states_with_submission( - db: &MockContractDb, - ) -> Vec<(DkgClient, State)> { + async fn prepare_clients_and_states_with_submission(db: &MockContractDb) -> Vec { let mut clients_and_states = prepare_clients_and_states_with_dealing(db).await; - for (dkg_client, state) in clients_and_states.iter_mut() { + for controller in clients_and_states.iter_mut() { let random_file: usize = OsRng.gen(); let private_key_path = temp_dir().join(format!("private{}.pem", random_file)); let public_key_path = temp_dir().join(format!("public{}.pem", random_file)); let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone()); - verification_key_submission(dkg_client, state, 0, &keypair_path, false) - .await - .unwrap(); + verification_key_submission( + &controller.dkg_client, + &mut controller.state, + 0, + &keypair_path, + false, + ) + .await + .unwrap(); std::fs::remove_file(private_key_path).unwrap(); std::fs::remove_file(public_key_path).unwrap(); } clients_and_states } - async fn prepare_clients_and_states_with_validation( - db: &MockContractDb, - ) -> Vec<(DkgClient, State)> { + async fn prepare_clients_and_states_with_validation(db: &MockContractDb) -> Vec { let mut clients_and_states = prepare_clients_and_states_with_submission(db).await; - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_validation(dkg_client, state, false) + for controller in clients_and_states.iter_mut() { + verification_key_validation(&controller.dkg_client, &mut controller.state, false) .await .unwrap(); } @@ -483,10 +481,10 @@ pub(crate) mod tests { async fn prepare_clients_and_states_with_finalization( db: &MockContractDb, - ) -> Vec<(DkgClient, State)> { + ) -> Vec { let mut clients_and_states = prepare_clients_and_states_with_validation(db).await; - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_finalization(dkg_client, state, false) + for controller in clients_and_states.iter_mut() { + verification_key_finalization(&controller.dkg_client, &mut controller.state, false) .await .unwrap(); } @@ -498,12 +496,22 @@ pub(crate) mod tests { async fn check_dealers_filter_all_good() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap(); + let contract_state = clients_and_states[0] + .dkg_client + .get_contract_state() + .await + .unwrap(); - for (dkg_client, state) in clients_and_states.iter_mut() { - let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false) - .await - .unwrap(); + for controller in clients_and_states.iter_mut() { + let filtered = deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + false, + ) + .await + .unwrap(); assert_eq!(filtered.len(), contract_state.key_size as usize); for mapping in filtered.iter() { assert_eq!(mapping.len(), 4); @@ -516,7 +524,11 @@ pub(crate) mod tests { async fn check_dealers_filter_one_bad_dealing() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap(); + let contract_state = clients_and_states[0] + .dkg_client + .get_contract_state() + .await + .unwrap(); // corrupt just one dealing db.dealings_db @@ -532,12 +544,19 @@ pub(crate) mod tests { validator_dealings.push(last); }); - for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false) - .await - .unwrap(); + for controller in clients_and_states.iter_mut().skip(1) { + let filtered = deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + false, + ) + .await + .unwrap(); assert_eq!(filtered.len(), contract_state.key_size as usize); - let corrupted_status = state + let corrupted_status = controller + .state .all_dealers() .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) .unwrap() @@ -552,25 +571,36 @@ pub(crate) mod tests { async fn check_dealers_resharing_filter_one_missing_dealing() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states(&db).await; - let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap(); + let contract_state = clients_and_states[0] + .dkg_client + .get_contract_state() + .await + .unwrap(); // add all but the first dealing - for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - dealing_exchange(dkg_client, state, OsRng, true) + for controller in clients_and_states.iter_mut().skip(1) { + dealing_exchange(&controller.dkg_client, &mut controller.state, OsRng, true) .await .unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { + for controller in clients_and_states.iter_mut().skip(1) { *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])], initial_height: 1, }); - let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, true) - .await - .unwrap(); + let filtered = deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + true, + ) + .await + .unwrap(); assert_eq!(filtered.len(), contract_state.key_size as usize); - let corrupted_status = state + let corrupted_status = controller + .state .all_dealers() .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) .unwrap() @@ -586,25 +616,36 @@ pub(crate) mod tests { async fn check_dealers_resharing_filter_one_noninitial_missing_dealing() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states(&db).await; - let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap(); + let contract_state = clients_and_states[0] + .dkg_client + .get_contract_state() + .await + .unwrap(); // add all but the first dealing - for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - dealing_exchange(dkg_client, state, OsRng, true) + for controller in clients_and_states.iter_mut().skip(1) { + dealing_exchange(&controller.dkg_client, &mut controller.state, OsRng, true) .await .unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { + for controller in clients_and_states.iter_mut().skip(1) { *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { initial_dealers: vec![], initial_height: 1, }); - let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, true) - .await - .unwrap(); + let filtered = deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + true, + ) + .await + .unwrap(); assert_eq!(filtered.len(), contract_state.key_size as usize); - assert!(state + assert!(controller + .state .all_dealers() .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) .unwrap() @@ -618,7 +659,11 @@ pub(crate) mod tests { async fn check_dealers_filter_all_bad_dealings() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap(); + let contract_state = clients_and_states[0] + .dkg_client + .get_contract_state() + .await + .unwrap(); // corrupt all dealings of one address db.dealings_db @@ -634,15 +679,22 @@ pub(crate) mod tests { }); }); - for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false) - .await - .unwrap(); + for controller in clients_and_states.iter_mut().skip(1) { + let filtered = deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + false, + ) + .await + .unwrap(); assert_eq!(filtered.len(), contract_state.key_size as usize); for mapping in filtered.iter() { assert_eq!(mapping.len(), 3); } - let corrupted_status = state + let corrupted_status = controller + .state .all_dealers() .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) .unwrap() @@ -657,7 +709,11 @@ pub(crate) mod tests { async fn check_dealers_filter_malformed_dealing() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap(); + let contract_state = clients_and_states[0] + .dkg_client + .get_contract_state() + .await + .unwrap(); // corrupt just one dealing db.dealings_db @@ -673,17 +729,30 @@ pub(crate) mod tests { validator_dealings.push(last); }); - for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - deterministic_filter_dealers(dkg_client, state, 0, 2, false) - .await - .unwrap(); + for controller in clients_and_states.iter_mut().skip(1) { + deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + false, + ) + .await + .unwrap(); // second filter will leave behind the bad dealer and surface why it was left out // in the first place - let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false) - .await - .unwrap(); + let filtered = deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + false, + ) + .await + .unwrap(); assert_eq!(filtered.len(), contract_state.key_size as usize); - let corrupted_status = state + let corrupted_status = controller + .state .all_dealers() .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) .unwrap() @@ -698,7 +767,11 @@ pub(crate) mod tests { async fn check_dealers_filter_dealing_verification_error() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap(); + let contract_state = clients_and_states[0] + .dkg_client + .get_contract_state() + .await + .unwrap(); // corrupt just one dealing db.dealings_db @@ -719,17 +792,30 @@ pub(crate) mod tests { validator_dealings.push(last); }); - for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - deterministic_filter_dealers(dkg_client, state, 0, 2, false) - .await - .unwrap(); + for controller in clients_and_states.iter_mut().skip(1) { + deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + false, + ) + .await + .unwrap(); // second filter will leave behind the bad dealer and surface why it was left out // in the first place - let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false) - .await - .unwrap(); + let filtered = deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + false, + ) + .await + .unwrap(); assert_eq!(filtered.len(), contract_state.key_size as usize); - let corrupted_status = state + let corrupted_status = controller + .state .all_dealers() .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) .unwrap() @@ -744,11 +830,17 @@ pub(crate) mod tests { async fn partial_keypair_derivation() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - for (dkg_client, state) in clients_and_states.iter_mut() { - let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false) - .await - .unwrap(); - assert!(derive_partial_keypair(state, 2, filtered).is_ok()); + for controller in clients_and_states.iter_mut() { + let filtered = deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + false, + ) + .await + .unwrap(); + assert!(derive_partial_keypair(&mut controller.state, 2, filtered).is_ok()); } } @@ -772,11 +864,17 @@ pub(crate) mod tests { validator_dealings.push(last); }); - for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false) - .await - .unwrap(); - assert!(derive_partial_keypair(state, 2, filtered).is_ok()); + for controller in clients_and_states.iter_mut().skip(1) { + let filtered = deterministic_filter_dealers( + &controller.dkg_client, + &mut controller.state, + 0, + 2, + false, + ) + .await + .unwrap(); + assert!(derive_partial_keypair(&mut controller.state, 2, filtered).is_ok()); } } @@ -786,13 +884,13 @@ pub(crate) mod tests { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await; - for (_, state) in clients_and_states.iter_mut() { + for controller in clients_and_states.iter_mut() { assert!(db .proposal_db .read() .unwrap() - .contains_key(&state.proposal_id_value().unwrap())); - assert!(state.coconut_keypair_is_some().await); + .contains_key(&controller.state.proposal_id_value().unwrap())); + assert!(controller.state.coconut_keypair_is_some().await); } } @@ -801,12 +899,12 @@ pub(crate) mod tests { async fn validate_verification_key() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_validation(&db).await; - for (_, state) in clients_and_states.iter_mut() { + for controller in clients_and_states.iter_mut() { let proposal = db .proposal_db .read() .unwrap() - .get(&state.proposal_id_value().unwrap()) + .get(&controller.state.proposal_id_value().unwrap()) .unwrap() .clone(); assert_eq!(proposal.status, Status::Passed); @@ -825,18 +923,18 @@ pub(crate) mod tests { .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) .and_modify(|share| share.share.push('x')); - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_validation(dkg_client, state, false) + for controller in clients_and_states.iter_mut() { + verification_key_validation(&controller.dkg_client, &mut controller.state, false) .await .unwrap(); } - for (idx, (_, state)) in clients_and_states.iter().enumerate() { + for (idx, controller) in clients_and_states.iter().enumerate() { let proposal = db .proposal_db .read() .unwrap() - .get(&state.proposal_id_value().unwrap()) + .get(&controller.state.proposal_id_value().unwrap()) .unwrap() .clone(); if idx == 0 { @@ -867,18 +965,18 @@ pub(crate) mod tests { .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) .and_modify(|share| share.share = second_share); - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_validation(dkg_client, state, false) + for controller in clients_and_states.iter_mut() { + verification_key_validation(&controller.dkg_client, &mut controller.state, false) .await .unwrap(); } - for (idx, (_, state)) in clients_and_states.iter().enumerate() { + for (idx, controller) in clients_and_states.iter().enumerate() { let proposal = db .proposal_db .read() .unwrap() - .get(&state.proposal_id_value().unwrap()) + .get(&controller.state.proposal_id_value().unwrap()) .unwrap() .clone(); if idx == 0 { @@ -895,12 +993,12 @@ pub(crate) mod tests { let db = MockContractDb::new(); let clients_and_states = prepare_clients_and_states_with_finalization(&db).await; - for (_, state) in clients_and_states.iter() { + for controller in clients_and_states.iter() { let proposal = db .proposal_db .read() .unwrap() - .get(&state.proposal_id_value().unwrap()) + .get(&controller.state.proposal_id_value().unwrap()) .unwrap() .clone(); assert_eq!(proposal.status, Status::Executed); @@ -912,21 +1010,22 @@ pub(crate) mod tests { async fn reshare_preserves_keys() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await; - for (_, state) in clients_and_states.iter_mut() { - state.set_was_in_progress(); + for controller in clients_and_states.iter_mut() { + controller.state.set_was_in_progress(); } let mut vks = vec![]; let mut indices = vec![]; - for (_, state) in clients_and_states.iter() { - let vk = state + for controller in clients_and_states.iter() { + let vk = controller + .state .coconut_keypair() .await .as_ref() .unwrap() .verification_key() .clone(); - let index = state.node_index().unwrap(); + let index = controller.state.node_index().unwrap(); vks.push(vk); indices.push(index); } @@ -961,46 +1060,51 @@ pub(crate) mod tests { *db.dealings_db.write().unwrap() = Default::default(); *db.verification_share_db.write().unwrap() = Default::default(); let mut initial_dealers = vec![]; - for (dkg_client, _) in clients_and_states.iter() { - let client_address = Addr::unchecked(dkg_client.get_address().await.as_ref()); + for controller in clients_and_states.iter() { + let client_address = + Addr::unchecked(controller.dkg_client.get_address().await.as_ref()); initial_dealers.push(client_address); } *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { initial_dealers, initial_height: 1, }); - *clients_and_states.first_mut().unwrap() = (new_dkg_client, state); + *clients_and_states.first_mut().unwrap() = DkgController::test_mock(new_dkg_client, state); - for (dkg_client, state) in clients_and_states.iter_mut() { - public_key_submission(dkg_client, state, true) + for controller in clients_and_states.iter_mut() { + controller.public_key_submission(0, true).await.unwrap(); + } + + for controller in clients_and_states.iter_mut() { + dealing_exchange(&controller.dkg_client, &mut controller.state, OsRng, true) .await .unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut() { - dealing_exchange(dkg_client, state, OsRng, true) - .await - .unwrap(); - } - - for (dkg_client, state) in clients_and_states.iter_mut() { + for controller in clients_and_states.iter_mut() { let random_file: usize = OsRng.gen(); let private_key_path = temp_dir().join(format!("private{}.pem", random_file)); let public_key_path = temp_dir().join(format!("public{}.pem", random_file)); let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone()); - verification_key_submission(dkg_client, state, 0, &keypair_path, true) - .await - .unwrap(); + verification_key_submission( + &controller.dkg_client, + &mut controller.state, + 0, + &keypair_path, + true, + ) + .await + .unwrap(); std::fs::remove_file(private_key_path).unwrap(); std::fs::remove_file(public_key_path).unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_validation(dkg_client, state, true) + for controller in clients_and_states.iter_mut() { + verification_key_validation(&controller.dkg_client, &mut controller.state, true) .await .unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_finalization(dkg_client, state, true) + for controller in clients_and_states.iter_mut() { + verification_key_finalization(&controller.dkg_client, &mut controller.state, true) .await .unwrap(); } @@ -1013,15 +1117,16 @@ pub(crate) mod tests { let mut vks = vec![]; let mut indices = vec![]; - for (_, state) in clients_and_states.iter() { - let vk = state + for controller in clients_and_states.iter() { + let vk = controller + .state .coconut_keypair() .await .as_ref() .unwrap() .verification_key() .clone(); - let index = state.node_index().unwrap(); + let index = controller.state.node_index().unwrap(); vks.push(vk); indices.push(index); } @@ -1034,8 +1139,8 @@ pub(crate) mod tests { async fn reshare_after_reset() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await; - for (_, state) in clients_and_states.iter_mut() { - state.set_was_in_progress(); + for controller in clients_and_states.iter_mut() { + controller.state.set_was_in_progress(); } let new_dkg_client = DkgClient::new( @@ -1088,39 +1193,43 @@ pub(crate) mod tests { *db.dealings_db.write().unwrap() = Default::default(); *db.verification_share_db.write().unwrap() = Default::default(); clients_and_states.pop().unwrap(); - let (initial_client2, initial_state2) = clients_and_states.pop().unwrap(); - clients_and_states.push((new_dkg_client, state)); - clients_and_states.push((new_dkg_client2, state2)); + let controller2 = clients_and_states.pop().unwrap(); + clients_and_states.push(DkgController::test_mock(new_dkg_client, state)); + clients_and_states.push(DkgController::test_mock(new_dkg_client2, state2)); // DKG in reset mode - for (dkg_client, state) in clients_and_states.iter_mut() { - public_key_submission(dkg_client, state, false) + for controller in clients_and_states.iter_mut() { + controller.public_key_submission(0, false).await.unwrap(); + } + for controller in clients_and_states.iter_mut() { + dealing_exchange(&controller.dkg_client, &mut controller.state, OsRng, false) .await .unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut() { - dealing_exchange(dkg_client, state, OsRng, false) - .await - .unwrap(); - } - for (dkg_client, state) in clients_and_states.iter_mut() { + for controller in clients_and_states.iter_mut() { let random_file: usize = OsRng.gen(); let private_key_path = temp_dir().join(format!("private{}.pem", random_file)); let public_key_path = temp_dir().join(format!("public{}.pem", random_file)); let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone()); - verification_key_submission(dkg_client, state, 0, &keypair_path, false) - .await - .unwrap(); + verification_key_submission( + &controller.dkg_client, + &mut controller.state, + 0, + &keypair_path, + false, + ) + .await + .unwrap(); std::fs::remove_file(private_key_path).unwrap(); std::fs::remove_file(public_key_path).unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_validation(dkg_client, state, false) + for controller in clients_and_states.iter_mut() { + verification_key_validation(&controller.dkg_client, &mut controller.state, false) .await .unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_finalization(dkg_client, state, false) + for controller in clients_and_states.iter_mut() { + verification_key_finalization(&controller.dkg_client, &mut controller.state, false) .await .unwrap(); } @@ -1130,22 +1239,23 @@ pub(crate) mod tests { .unwrap() .values() .all(|proposal| { proposal.status == Status::Executed })); - for (_, state) in clients_and_states.iter_mut() { - state.set_was_in_progress(); + for controller in clients_and_states.iter_mut() { + controller.state.set_was_in_progress(); } // DKG in reshare mode let mut vks = vec![]; let mut indices = vec![]; - for (_, state) in clients_and_states.iter() { - let vk = state + for controller in clients_and_states.iter() { + let vk = controller + .state .coconut_keypair() .await .as_ref() .unwrap() .verification_key() .clone(); - let index = state.node_index().unwrap(); + let index = controller.state.node_index().unwrap(); vks.push(vk); indices.push(index); } @@ -1157,47 +1267,52 @@ pub(crate) mod tests { *db.dealings_db.write().unwrap() = Default::default(); *db.verification_share_db.write().unwrap() = Default::default(); let mut initial_dealers = vec![]; - for (dkg_client, _) in clients_and_states.iter() { - let client_address = Addr::unchecked(dkg_client.get_address().await.as_ref()); + for controller in clients_and_states.iter() { + let client_address = + Addr::unchecked(controller.dkg_client.get_address().await.as_ref()); initial_dealers.push(client_address); } *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { initial_dealers, initial_height: 1, }); - *clients_and_states.last_mut().unwrap() = (initial_client2, initial_state2); + *clients_and_states.last_mut().unwrap() = controller2; - for (dkg_client, state) in clients_and_states.iter_mut() { - public_key_submission(dkg_client, state, true) + for controller in clients_and_states.iter_mut() { + controller.public_key_submission(0, true).await.unwrap(); + } + + for controller in clients_and_states.iter_mut() { + dealing_exchange(&controller.dkg_client, &mut controller.state, OsRng, true) .await .unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut() { - dealing_exchange(dkg_client, state, OsRng, true) - .await - .unwrap(); - } - - for (dkg_client, state) in clients_and_states.iter_mut() { + for controller in clients_and_states.iter_mut() { let random_file: usize = OsRng.gen(); let private_key_path = temp_dir().join(format!("private{}.pem", random_file)); let public_key_path = temp_dir().join(format!("public{}.pem", random_file)); let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone()); - verification_key_submission(dkg_client, state, 0, &keypair_path, true) - .await - .unwrap(); + verification_key_submission( + &controller.dkg_client, + &mut controller.state, + 0, + &keypair_path, + true, + ) + .await + .unwrap(); std::fs::remove_file(private_key_path).unwrap(); std::fs::remove_file(public_key_path).unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_validation(dkg_client, state, true) + for controller in clients_and_states.iter_mut() { + verification_key_validation(&controller.dkg_client, &mut controller.state, true) .await .unwrap(); } - for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_finalization(dkg_client, state, true) + for controller in clients_and_states.iter_mut() { + verification_key_finalization(&controller.dkg_client, &mut controller.state, true) .await .unwrap(); } @@ -1210,15 +1325,16 @@ pub(crate) mod tests { let mut vks = vec![]; let mut indices = vec![]; - for (_, state) in clients_and_states.iter() { - let vk = state + for controller in clients_and_states.iter() { + let vk = controller + .state .coconut_keypair() .await .as_ref() .unwrap() .verification_key() .clone(); - let index = state.node_index().unwrap(); + let index = controller.state.node_index().unwrap(); vks.push(vk); indices.push(index); } diff --git a/nym-api/src/coconut/keypair.rs b/nym-api/src/coconut/keypair.rs index 57b7872971..85939b46ca 100644 --- a/nym-api/src/coconut/keypair.rs +++ b/nym-api/src/coconut/keypair.rs @@ -1,31 +1,41 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use nym_coconut_dkg_common::types::EpochId; +use std::collections::HashMap; use std::sync::Arc; use tokio::sync::{RwLock, RwLockReadGuard}; #[derive(Clone, Debug)] pub struct KeyPair { - inner: Arc>>, + // keys: Arc>>, + keys: Arc>>, + // issued_for_epoch: Arc, } impl KeyPair { pub fn new() -> Self { Self { - inner: Arc::new(RwLock::new(None)), + keys: Arc::new(RwLock::new(None)), } } - pub async fn take(&self) -> Option { - self.inner.write().await.take() + pub async fn take(&self) -> Option<(EpochId, nym_coconut::KeyPair)> { + self.keys.write().await.take() } pub async fn get(&self) -> RwLockReadGuard<'_, Option> { - self.inner.read().await + todo!() + // self.keys.read().await } - pub async fn set(&self, keypair: Option) { - let mut w_lock = self.inner.write().await; - *w_lock = keypair; + pub async fn set(&self, epoch_id: EpochId, keypair: nym_coconut_interface::KeyPair) { + todo!() + // let mut w_lock = self.keys.write().await; + // *w_lock = Some(keypair); + } + + pub async fn invalidate(&self) { + *self.keys.write().await = None } } diff --git a/nym-api/src/coconut/tests/mod.rs b/nym-api/src/coconut/tests/mod.rs index 089a11a275..d1318e6fea 100644 --- a/nym-api/src/coconut/tests/mod.rs +++ b/nym-api/src/coconut/tests/mod.rs @@ -662,41 +662,42 @@ struct TestFixture { impl TestFixture { async fn new() -> Self { - let mut rng = OsRng; - let params = Parameters::new(4).unwrap(); - let coconut_keypair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); - let identity = identity::KeyPair::new(&mut rng); - let epoch = Arc::new(AtomicU64::new(1)); - let comm_channel = - DummyCommunicationChannel::new(coconut_keypair.verification_key().clone()) - .with_epoch(epoch.clone()); - let storage = nym_api_storage_fixture(&identity).await; - - let staged_key_pair = crate::coconut::KeyPair::new(); - staged_key_pair.set(Some(coconut_keypair)).await; - - let tx_db = Arc::new(RwLock::new(HashMap::new())); - let nyxd_client = - DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()) - .with_tx_db(&tx_db); - - let rocket = rocket::build().attach(coconut::stage( - nyxd_client, - TEST_COIN_DENOM.to_string(), - identity, - staged_key_pair, - comm_channel, - storage.clone(), - )); - - TestFixture { - rocket: Client::tracked(rocket) - .await - .expect("valid rocket instance"), - storage, - tx_db, - epoch, - } + todo!() + // let mut rng = OsRng; + // let params = Parameters::new(4).unwrap(); + // let coconut_keypair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); + // let identity = identity::KeyPair::new(&mut rng); + // let epoch = Arc::new(AtomicU64::new(1)); + // let comm_channel = + // DummyCommunicationChannel::new(coconut_keypair.verification_key().clone()) + // .with_epoch(epoch.clone()); + // let storage = nym_api_storage_fixture(&identity).await; + // + // let staged_key_pair = crate::coconut::KeyPair::new(); + // staged_key_pair.set(Some(coconut_keypair)).await; + // + // let tx_db = Arc::new(RwLock::new(HashMap::new())); + // let nyxd_client = + // DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()) + // .with_tx_db(&tx_db); + // + // let rocket = rocket::build().attach(coconut::stage( + // nyxd_client, + // TEST_COIN_DENOM.to_string(), + // identity, + // staged_key_pair, + // comm_channel, + // storage.clone(), + // )); + // + // TestFixture { + // rocket: Client::tracked(rocket) + // .await + // .expect("valid rocket instance"), + // storage, + // tx_db, + // epoch, + // } } fn set_epoch(&self, epoch: u64) { @@ -805,511 +806,514 @@ async fn already_issued() { #[tokio::test] async fn state_functions() { - let mut rng = OsRng; - let identity = identity::KeyPair::new(&mut rng); - - let nyxd_client = - DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()); - let params = Parameters::new(4).unwrap(); - let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); - let mut db_dir = std::env::temp_dir(); - db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage = NymApiStorage::init(db_dir).await.unwrap(); - let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key().clone()); - let staged_key_pair = crate::coconut::KeyPair::new(); - staged_key_pair.set(Some(key_pair)).await; - let state = State::new( - nyxd_client, - TEST_COIN_DENOM.to_string(), - identity, - staged_key_pair, - comm_channel, - storage.clone(), - ); - - let tx_hash = "6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E" - .parse() - .unwrap(); - assert!(state.already_issued(tx_hash).await.unwrap().is_none()); - - let (_, request_body) = voucher_request_fixture(coin(1234, TEST_COIN_DENOM), None); - let commitments = request_body.encode_commitments(); - let public = request_body.public_attributes_plain.clone(); - let sig = blinded_signature_fixture(); - storage - .store_issued_credential( - 42, - tx_hash, - &sig, - dummy_signature(), - commitments.clone(), - public.clone(), - ) - .await - .unwrap(); - - assert_eq!( - state - .already_issued(tx_hash) - .await - .unwrap() - .unwrap() - .to_bytes(), - blinded_signature_fixture().to_bytes() - ); - - let blinded_signature = BlindedSignature::from_bytes(&[ - 183, 217, 166, 113, 40, 123, 74, 25, 72, 31, 136, 19, 125, 95, 217, 228, 96, 113, 25, 240, - 12, 102, 125, 11, 174, 20, 216, 82, 192, 71, 27, 194, 48, 20, 17, 95, 243, 179, 82, 21, 57, - 143, 101, 19, 22, 186, 147, 13, 147, 238, 39, 119, 15, 36, 251, 131, 250, 38, 185, 113, - 187, 40, 227, 107, 134, 190, 123, 183, 126, 176, 226, 173, 147, 137, 17, 175, 13, 115, 78, - 222, 119, 93, 146, 116, 229, 0, 152, 51, 232, 2, 102, 204, 147, 202, 254, 243, - ]) - .unwrap(); - - // Check that the new payload is not stored if there was already something signed for tx_hash - let storage_err = storage - .store_issued_credential( - 42, - tx_hash, - &blinded_signature, - dummy_signature(), - commitments.clone(), - public.clone(), - ) - .await; - assert!(storage_err.is_err()); - - // And use a new hash to store a new signature - let tx_hash = "97D64C38D6601B1F0FD3A82E20D252685CB7A210AFB0261018590659AB82B0BF" - .parse() - .unwrap(); - - storage - .store_issued_credential( - 42, - tx_hash, - &blinded_signature, - dummy_signature(), - commitments.clone(), - public.clone(), - ) - .await - .unwrap(); - - // Check that the same value for tx_hash is returned - assert_eq!( - state - .already_issued(tx_hash) - .await - .unwrap() - .unwrap() - .to_bytes(), - blinded_signature.to_bytes() - ); + todo!() + // let mut rng = OsRng; + // let identity = identity::KeyPair::new(&mut rng); + // + // let nyxd_client = + // DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()); + // let params = Parameters::new(4).unwrap(); + // let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); + // let mut db_dir = std::env::temp_dir(); + // db_dir.push(&key_pair.verification_key().to_bs58()[..8]); + // let storage = NymApiStorage::init(db_dir).await.unwrap(); + // let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key().clone()); + // let staged_key_pair = crate::coconut::KeyPair::new(); + // staged_key_pair.set(Some(key_pair)).await; + // let state = State::new( + // nyxd_client, + // TEST_COIN_DENOM.to_string(), + // identity, + // staged_key_pair, + // comm_channel, + // storage.clone(), + // ); + // + // let tx_hash = "6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E" + // .parse() + // .unwrap(); + // assert!(state.already_issued(tx_hash).await.unwrap().is_none()); + // + // let (_, request_body) = voucher_request_fixture(coin(1234, TEST_COIN_DENOM), None); + // let commitments = request_body.encode_commitments(); + // let public = request_body.public_attributes_plain.clone(); + // let sig = blinded_signature_fixture(); + // storage + // .store_issued_credential( + // 42, + // tx_hash, + // &sig, + // dummy_signature(), + // commitments.clone(), + // public.clone(), + // ) + // .await + // .unwrap(); + // + // assert_eq!( + // state + // .already_issued(tx_hash) + // .await + // .unwrap() + // .unwrap() + // .to_bytes(), + // blinded_signature_fixture().to_bytes() + // ); + // + // let blinded_signature = BlindedSignature::from_bytes(&[ + // 183, 217, 166, 113, 40, 123, 74, 25, 72, 31, 136, 19, 125, 95, 217, 228, 96, 113, 25, 240, + // 12, 102, 125, 11, 174, 20, 216, 82, 192, 71, 27, 194, 48, 20, 17, 95, 243, 179, 82, 21, 57, + // 143, 101, 19, 22, 186, 147, 13, 147, 238, 39, 119, 15, 36, 251, 131, 250, 38, 185, 113, + // 187, 40, 227, 107, 134, 190, 123, 183, 126, 176, 226, 173, 147, 137, 17, 175, 13, 115, 78, + // 222, 119, 93, 146, 116, 229, 0, 152, 51, 232, 2, 102, 204, 147, 202, 254, 243, + // ]) + // .unwrap(); + // + // // Check that the new payload is not stored if there was already something signed for tx_hash + // let storage_err = storage + // .store_issued_credential( + // 42, + // tx_hash, + // &blinded_signature, + // dummy_signature(), + // commitments.clone(), + // public.clone(), + // ) + // .await; + // assert!(storage_err.is_err()); + // + // // And use a new hash to store a new signature + // let tx_hash = "97D64C38D6601B1F0FD3A82E20D252685CB7A210AFB0261018590659AB82B0BF" + // .parse() + // .unwrap(); + // + // storage + // .store_issued_credential( + // 42, + // tx_hash, + // &blinded_signature, + // dummy_signature(), + // commitments.clone(), + // public.clone(), + // ) + // .await + // .unwrap(); + // + // // Check that the same value for tx_hash is returned + // assert_eq!( + // state + // .already_issued(tx_hash) + // .await + // .unwrap() + // .unwrap() + // .to_bytes(), + // blinded_signature.to_bytes() + // ); } #[tokio::test] async fn blind_sign_correct() { - let tx_hash = - Hash::from_str("7C41AF8266D91DE55E1C8F4712E6A952A165ED3D8C27C7B00428CBD0DE00A52B").unwrap(); - - let params = Parameters::new(4).unwrap(); - let mut rng = OsRng; - let nym_api_identity = identity::KeyPair::new(&mut rng); - - let identity_keypair = identity::KeyPair::new(&mut rng); - let encryption_keypair = encryption::KeyPair::new(&mut rng); - let voucher = BandwidthVoucher::new( - ¶ms, - "1234".to_string(), - VOUCHER_INFO.to_string(), - tx_hash, - identity::PrivateKey::from_base58_string(identity_keypair.private_key().to_base58_string()) - .unwrap(), - encryption::PrivateKey::from_bytes(&encryption_keypair.private_key().to_bytes()).unwrap(), - ); - - let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); - let mut db_dir = std::env::temp_dir(); - db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage = NymApiStorage::init(db_dir).await.unwrap(); - let tx_db = Arc::new(RwLock::new(HashMap::new())); - - let tx_entry = deposit_tx_fixture(&voucher); - - tx_db.write().unwrap().insert(tx_hash, tx_entry.clone()); - let nyxd_client = - DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()) - .with_tx_db(&tx_db); - let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key().clone()); - let staged_key_pair = crate::coconut::KeyPair::new(); - staged_key_pair.set(Some(key_pair)).await; - - let rocket = rocket::build().attach(coconut::stage( - nyxd_client, - TEST_COIN_DENOM.to_string(), - nym_api_identity, - staged_key_pair, - comm_channel, - storage.clone(), - )); - let client = Client::tracked(rocket) - .await - .expect("valid rocket instance"); - - let request_signature = voucher.sign(); - - let request_body = BlindSignRequestBody::new( - voucher.blind_sign_request().clone(), - tx_hash, - request_signature, - voucher.get_public_attributes_plain(), - ); - - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_BLIND_SIGN - )) - .json(&request_body) - .dispatch() - .await; - - assert_eq!(response.status(), Status::Ok); - // This is a more direct way, but there's a bug which makes it hang https://github.com/SergioBenitez/Rocket/issues/1893 - // assert!(response.into_json::().is_some()); - let blinded_signature_response = - serde_json::from_str::(&response.into_string().await.unwrap()); - assert!(blinded_signature_response.is_ok()); + todo!() + // let tx_hash = + // Hash::from_str("7C41AF8266D91DE55E1C8F4712E6A952A165ED3D8C27C7B00428CBD0DE00A52B").unwrap(); + // + // let params = Parameters::new(4).unwrap(); + // let mut rng = OsRng; + // let nym_api_identity = identity::KeyPair::new(&mut rng); + // + // let identity_keypair = identity::KeyPair::new(&mut rng); + // let encryption_keypair = encryption::KeyPair::new(&mut rng); + // let voucher = BandwidthVoucher::new( + // ¶ms, + // "1234".to_string(), + // VOUCHER_INFO.to_string(), + // tx_hash, + // identity::PrivateKey::from_base58_string(identity_keypair.private_key().to_base58_string()) + // .unwrap(), + // encryption::PrivateKey::from_bytes(&encryption_keypair.private_key().to_bytes()).unwrap(), + // ); + // + // let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); + // let mut db_dir = std::env::temp_dir(); + // db_dir.push(&key_pair.verification_key().to_bs58()[..8]); + // let storage = NymApiStorage::init(db_dir).await.unwrap(); + // let tx_db = Arc::new(RwLock::new(HashMap::new())); + // + // let tx_entry = deposit_tx_fixture(&voucher); + // + // tx_db.write().unwrap().insert(tx_hash, tx_entry.clone()); + // let nyxd_client = + // DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()) + // .with_tx_db(&tx_db); + // let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key().clone()); + // let staged_key_pair = crate::coconut::KeyPair::new(); + // staged_key_pair.set(Some(key_pair)).await; + // + // let rocket = rocket::build().attach(coconut::stage( + // nyxd_client, + // TEST_COIN_DENOM.to_string(), + // nym_api_identity, + // staged_key_pair, + // comm_channel, + // storage.clone(), + // )); + // let client = Client::tracked(rocket) + // .await + // .expect("valid rocket instance"); + // + // let request_signature = voucher.sign(); + // + // let request_body = BlindSignRequestBody::new( + // voucher.blind_sign_request().clone(), + // tx_hash, + // request_signature, + // voucher.get_public_attributes_plain(), + // ); + // + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_BLIND_SIGN + // )) + // .json(&request_body) + // .dispatch() + // .await; + // + // assert_eq!(response.status(), Status::Ok); + // // This is a more direct way, but there's a bug which makes it hang https://github.com/SergioBenitez/Rocket/issues/1893 + // // assert!(response.into_json::().is_some()); + // let blinded_signature_response = + // serde_json::from_str::(&response.into_string().await.unwrap()); + // assert!(blinded_signature_response.is_ok()); } #[tokio::test] async fn verification_of_bandwidth_credential() { - // Setup variables - let validator_address = AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(); - let proposal_db = Arc::new(RwLock::new(HashMap::new())); - let spent_credential_db = Arc::new(RwLock::new(HashMap::new())); - let nyxd_client = DummyClient::new(validator_address.clone()) - .with_proposal_db(&proposal_db) - .with_spent_credential_db(&spent_credential_db); - let mut db_dir = std::env::temp_dir(); - let params = Parameters::new(4).unwrap(); - let mut key_pairs = ttp_keygen(¶ms, 1, 1).unwrap(); - let voucher_value = 1234u64; - let voucher_info = "voucher info"; - let public_attributes = [ - hash_to_scalar(voucher_value.to_string()), - hash_to_scalar(voucher_info), - ]; - let public_attributes_ref = vec![&public_attributes[0], &public_attributes[1]]; - let indices: Vec = key_pairs - .iter() - .enumerate() - .map(|(idx, _)| (idx + 1) as u64) - .collect(); - let theta = - theta_from_keys_and_attributes(¶ms, &key_pairs, &indices, &public_attributes_ref) - .unwrap(); - let key_pair = key_pairs.remove(0); - db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage1 = NymApiStorage::init(db_dir).await.unwrap(); - let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key().clone()); - let staged_key_pair = crate::coconut::KeyPair::new(); - staged_key_pair.set(Some(key_pair)).await; - let mut rng = OsRng; - let identity = identity::KeyPair::new(&mut rng); - - let rocket = rocket::build().attach(coconut::stage( - nyxd_client.clone(), - TEST_COIN_DENOM.to_string(), - identity, - staged_key_pair, - comm_channel.clone(), - storage1.clone(), - )); - - let client = Client::tracked(rocket) - .await - .expect("valid rocket instance"); - - let credential = Credential::new(4, theta.clone(), voucher_value, voucher_info.to_string(), 0); - let proposal_id = 42; - // The address is not used, so we can use a duplicate - let gateway_cosmos_addr = validator_address.clone(); - let req = - VerifyCredentialBody::new(credential.clone(), proposal_id, gateway_cosmos_addr.clone()); - - // Test endpoint with not proposal for the proposal id - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&req) - .dispatch() - .await; - assert_eq!(response.status(), Status::BadRequest); - assert_eq!( - response.into_string().await.unwrap(), - CoconutError::IncorrectProposal { - reason: "proposal not found".to_string() - } - .to_string() - ); - - let mut proposal = ProposalResponse { - id: proposal_id, - title: String::new(), - description: String::from("25mnnoCcUfeizfC85avvroFg2prpEZBgJbJM2SLtkgyyUkoAU3cqJiqWmg8cMHEPjfFf5sQF92SMAM2vbEoLZvUjenvXhadTLdA4TqMYArJpihyqirW2AhGoNehtcdcK5gnH"), - msgs: vec![], - status: cw3::Status::Open, - expires: cw_utils::Expiration::Never {}, - threshold: cw_utils::ThresholdResponse::AbsolutePercentage { - percentage: Decimal::from_ratio(2u32, 3u32), - total_weight: 100, - }, - proposer: Addr::unchecked("proposer"), - deposit: None, - }; - - // Test the endpoint with a different blinded serial number in the description - proposal_db - .write() - .unwrap() - .insert(proposal_id, proposal.clone()); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&req) - .dispatch() - .await; - assert_eq!(response.status(), Status::BadRequest); - assert_eq!( - response.into_string().await.unwrap(), - CoconutError::IncorrectProposal { - reason: "incorrect blinded serial number in description".to_string() - } - .to_string() - ); - - // Test the endpoint with no msg in the proposal action - proposal.description = credential.blinded_serial_number(); - proposal_db - .write() - .unwrap() - .insert(proposal_id, proposal.clone()); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&req) - .dispatch() - .await; - assert_eq!(response.status(), Status::BadRequest); - assert_eq!( - response.into_string().await.unwrap(), - CoconutError::IncorrectProposal { - reason: "action is not to release funds".to_string() - } - .to_string() - ); - - // Test the endpoint without any credential recorded in the Coconut Bandwidth Contract - let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); - let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { - funds: funds.clone().into(), - }; - let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::new(), - msg: to_binary(&msg).unwrap(), - funds: vec![], - }); - proposal.msgs = vec![cosmos_msg]; - proposal_db - .write() - .unwrap() - .insert(proposal_id, proposal.clone()); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&req) - .dispatch() - .await; - assert_eq!(response.status(), Status::BadRequest); - assert_eq!( - response.into_string().await.unwrap(), - CoconutError::InvalidCredentialStatus { - status: "spent credential not found".to_string() - } - .to_string() - ); - - spent_credential_db.write().unwrap().insert( - credential.blinded_serial_number(), - SpendCredentialResponse::new(None), - ); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&req) - .dispatch() - .await; - assert_eq!(response.status(), Status::BadRequest); - assert_eq!( - response.into_string().await.unwrap(), - CoconutError::InvalidCredentialStatus { - status: "Inexistent".to_string() - } - .to_string() - ); - - // Test the endpoint with a credential that doesn't verify correctly - let mut spent_credential = SpendCredential::new( - funds.clone().into(), - credential.blinded_serial_number(), - Addr::unchecked("unimportant"), - ); - spent_credential_db.write().unwrap().insert( - credential.blinded_serial_number(), - SpendCredentialResponse::new(Some(spent_credential.clone())), - ); - let bad_credential = Credential::new( - 4, - theta.clone(), - voucher_value, - String::from("bad voucher info"), - 0, - ); - let bad_req = - VerifyCredentialBody::new(bad_credential, proposal_id, gateway_cosmos_addr.clone()); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&bad_req) - .dispatch() - .await; - assert_eq!(response.status(), Status::Ok); - let verify_credential_response = - serde_json::from_str::(&response.into_string().await.unwrap()) - .unwrap(); - assert!(!verify_credential_response.verification_result); - assert_eq!( - cw3::Status::Rejected, - proposal_db - .read() - .unwrap() - .get(&proposal_id) - .unwrap() - .status - ); - - // Test the endpoint with a proposal that has a different value for the funds to be released - // then what's in the credential - let funds = Coin::new((voucher_value + 10) as u128, TEST_COIN_DENOM); - let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { - funds: funds.clone().into(), - }; - let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::new(), - msg: to_binary(&msg).unwrap(), - funds: vec![], - }); - proposal.msgs = vec![cosmos_msg]; - proposal_db - .write() - .unwrap() - .insert(proposal_id, proposal.clone()); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&req) - .dispatch() - .await; - assert_eq!(response.status(), Status::Ok); - let verify_credential_response = - serde_json::from_str::(&response.into_string().await.unwrap()) - .unwrap(); - assert!(!verify_credential_response.verification_result); - assert_eq!( - cw3::Status::Rejected, - proposal_db - .read() - .unwrap() - .get(&proposal_id) - .unwrap() - .status - ); - - // Test the endpoint with every dependency met - let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); - let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { - funds: funds.clone().into(), - }; - let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::new(), - msg: to_binary(&msg).unwrap(), - funds: vec![], - }); - proposal.msgs = vec![cosmos_msg]; - proposal_db - .write() - .unwrap() - .insert(proposal_id, proposal.clone()); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&req) - .dispatch() - .await; - assert_eq!(response.status(), Status::Ok); - let verify_credential_response = - serde_json::from_str::(&response.into_string().await.unwrap()) - .unwrap(); - assert!(verify_credential_response.verification_result); - assert_eq!( - cw3::Status::Passed, - proposal_db - .read() - .unwrap() - .get(&proposal_id) - .unwrap() - .status - ); - - // Test the endpoint with the credential marked as Spent in the Coconut Bandwidth Contract - spent_credential.mark_as_spent(); - spent_credential_db.write().unwrap().insert( - credential.blinded_serial_number(), - SpendCredentialResponse::new(Some(spent_credential)), - ); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&req) - .dispatch() - .await; - assert_eq!(response.status(), Status::BadRequest); - assert_eq!( - response.into_string().await.unwrap(), - CoconutError::InvalidCredentialStatus { - status: "Spent".to_string() - } - .to_string() - ); + todo!() + // // Setup variables + // let validator_address = AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(); + // let proposal_db = Arc::new(RwLock::new(HashMap::new())); + // let spent_credential_db = Arc::new(RwLock::new(HashMap::new())); + // let nyxd_client = DummyClient::new(validator_address.clone()) + // .with_proposal_db(&proposal_db) + // .with_spent_credential_db(&spent_credential_db); + // let mut db_dir = std::env::temp_dir(); + // let params = Parameters::new(4).unwrap(); + // let mut key_pairs = ttp_keygen(¶ms, 1, 1).unwrap(); + // let voucher_value = 1234u64; + // let voucher_info = "voucher info"; + // let public_attributes = [ + // hash_to_scalar(voucher_value.to_string()), + // hash_to_scalar(voucher_info), + // ]; + // let public_attributes_ref = vec![&public_attributes[0], &public_attributes[1]]; + // let indices: Vec = key_pairs + // .iter() + // .enumerate() + // .map(|(idx, _)| (idx + 1) as u64) + // .collect(); + // let theta = + // theta_from_keys_and_attributes(¶ms, &key_pairs, &indices, &public_attributes_ref) + // .unwrap(); + // let key_pair = key_pairs.remove(0); + // db_dir.push(&key_pair.verification_key().to_bs58()[..8]); + // let storage1 = NymApiStorage::init(db_dir).await.unwrap(); + // let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key().clone()); + // let staged_key_pair = crate::coconut::KeyPair::new(); + // staged_key_pair.set(Some(key_pair)).await; + // let mut rng = OsRng; + // let identity = identity::KeyPair::new(&mut rng); + // + // let rocket = rocket::build().attach(coconut::stage( + // nyxd_client.clone(), + // TEST_COIN_DENOM.to_string(), + // identity, + // staged_key_pair, + // comm_channel.clone(), + // storage1.clone(), + // )); + // + // let client = Client::tracked(rocket) + // .await + // .expect("valid rocket instance"); + // + // let credential = Credential::new(4, theta.clone(), voucher_value, voucher_info.to_string(), 0); + // let proposal_id = 42; + // // The address is not used, so we can use a duplicate + // let gateway_cosmos_addr = validator_address.clone(); + // let req = + // VerifyCredentialBody::new(credential.clone(), proposal_id, gateway_cosmos_addr.clone()); + // + // // Test endpoint with not proposal for the proposal id + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::BadRequest); + // assert_eq!( + // response.into_string().await.unwrap(), + // CoconutError::IncorrectProposal { + // reason: "proposal not found".to_string() + // } + // .to_string() + // ); + // + // let mut proposal = ProposalResponse { + // id: proposal_id, + // title: String::new(), + // description: String::from("25mnnoCcUfeizfC85avvroFg2prpEZBgJbJM2SLtkgyyUkoAU3cqJiqWmg8cMHEPjfFf5sQF92SMAM2vbEoLZvUjenvXhadTLdA4TqMYArJpihyqirW2AhGoNehtcdcK5gnH"), + // msgs: vec![], + // status: cw3::Status::Open, + // expires: cw_utils::Expiration::Never {}, + // threshold: cw_utils::ThresholdResponse::AbsolutePercentage { + // percentage: Decimal::from_ratio(2u32, 3u32), + // total_weight: 100, + // }, + // proposer: Addr::unchecked("proposer"), + // deposit: None, + // }; + // + // // Test the endpoint with a different blinded serial number in the description + // proposal_db + // .write() + // .unwrap() + // .insert(proposal_id, proposal.clone()); + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::BadRequest); + // assert_eq!( + // response.into_string().await.unwrap(), + // CoconutError::IncorrectProposal { + // reason: "incorrect blinded serial number in description".to_string() + // } + // .to_string() + // ); + // + // // Test the endpoint with no msg in the proposal action + // proposal.description = credential.blinded_serial_number(); + // proposal_db + // .write() + // .unwrap() + // .insert(proposal_id, proposal.clone()); + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::BadRequest); + // assert_eq!( + // response.into_string().await.unwrap(), + // CoconutError::IncorrectProposal { + // reason: "action is not to release funds".to_string() + // } + // .to_string() + // ); + // + // // Test the endpoint without any credential recorded in the Coconut Bandwidth Contract + // let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); + // let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + // funds: funds.clone().into(), + // }; + // let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { + // contract_addr: String::new(), + // msg: to_binary(&msg).unwrap(), + // funds: vec![], + // }); + // proposal.msgs = vec![cosmos_msg]; + // proposal_db + // .write() + // .unwrap() + // .insert(proposal_id, proposal.clone()); + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::BadRequest); + // assert_eq!( + // response.into_string().await.unwrap(), + // CoconutError::InvalidCredentialStatus { + // status: "spent credential not found".to_string() + // } + // .to_string() + // ); + // + // spent_credential_db.write().unwrap().insert( + // credential.blinded_serial_number(), + // SpendCredentialResponse::new(None), + // ); + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::BadRequest); + // assert_eq!( + // response.into_string().await.unwrap(), + // CoconutError::InvalidCredentialStatus { + // status: "Inexistent".to_string() + // } + // .to_string() + // ); + // + // // Test the endpoint with a credential that doesn't verify correctly + // let mut spent_credential = SpendCredential::new( + // funds.clone().into(), + // credential.blinded_serial_number(), + // Addr::unchecked("unimportant"), + // ); + // spent_credential_db.write().unwrap().insert( + // credential.blinded_serial_number(), + // SpendCredentialResponse::new(Some(spent_credential.clone())), + // ); + // let bad_credential = Credential::new( + // 4, + // theta.clone(), + // voucher_value, + // String::from("bad voucher info"), + // 0, + // ); + // let bad_req = + // VerifyCredentialBody::new(bad_credential, proposal_id, gateway_cosmos_addr.clone()); + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&bad_req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::Ok); + // let verify_credential_response = + // serde_json::from_str::(&response.into_string().await.unwrap()) + // .unwrap(); + // assert!(!verify_credential_response.verification_result); + // assert_eq!( + // cw3::Status::Rejected, + // proposal_db + // .read() + // .unwrap() + // .get(&proposal_id) + // .unwrap() + // .status + // ); + // + // // Test the endpoint with a proposal that has a different value for the funds to be released + // // then what's in the credential + // let funds = Coin::new((voucher_value + 10) as u128, TEST_COIN_DENOM); + // let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + // funds: funds.clone().into(), + // }; + // let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { + // contract_addr: String::new(), + // msg: to_binary(&msg).unwrap(), + // funds: vec![], + // }); + // proposal.msgs = vec![cosmos_msg]; + // proposal_db + // .write() + // .unwrap() + // .insert(proposal_id, proposal.clone()); + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::Ok); + // let verify_credential_response = + // serde_json::from_str::(&response.into_string().await.unwrap()) + // .unwrap(); + // assert!(!verify_credential_response.verification_result); + // assert_eq!( + // cw3::Status::Rejected, + // proposal_db + // .read() + // .unwrap() + // .get(&proposal_id) + // .unwrap() + // .status + // ); + // + // // Test the endpoint with every dependency met + // let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); + // let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + // funds: funds.clone().into(), + // }; + // let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { + // contract_addr: String::new(), + // msg: to_binary(&msg).unwrap(), + // funds: vec![], + // }); + // proposal.msgs = vec![cosmos_msg]; + // proposal_db + // .write() + // .unwrap() + // .insert(proposal_id, proposal.clone()); + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::Ok); + // let verify_credential_response = + // serde_json::from_str::(&response.into_string().await.unwrap()) + // .unwrap(); + // assert!(verify_credential_response.verification_result); + // assert_eq!( + // cw3::Status::Passed, + // proposal_db + // .read() + // .unwrap() + // .get(&proposal_id) + // .unwrap() + // .status + // ); + // + // // Test the endpoint with the credential marked as Spent in the Coconut Bandwidth Contract + // spent_credential.mark_as_spent(); + // spent_credential_db.write().unwrap().insert( + // credential.blinded_serial_number(), + // SpendCredentialResponse::new(Some(spent_credential)), + // ); + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::BadRequest); + // assert_eq!( + // response.into_string().await.unwrap(), + // CoconutError::InvalidCredentialStatus { + // status: "Spent".to_string() + // } + // .to_string() + // ); } diff --git a/nym-api/src/main.rs b/nym-api/src/main.rs index 3e25a2634c..fe86b5afe1 100644 --- a/nym-api/src/main.rs +++ b/nym-api/src/main.rs @@ -73,7 +73,8 @@ async fn start_nym_api_tasks(config: Config) -> anyhow::Result // if the keypair doesnt exist (because say this API is running in the caching mode), nothing will happen if let Some(loaded_keys) = load_coconut_keypair_if_exists(&config.coconut_signer)? { - coconut_keypair_wrapper.set(Some(loaded_keys)).await + todo!() + // coconut_keypair_wrapper.set(loaded_keys).await } let identity_keypair = config.base.storage_paths.load_identity()?;