From 254302ec38917c33cc4ff6031f1b366de32967ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Fri, 16 Dec 2022 12:21:39 +0200 Subject: [PATCH] Feature/auto dkg advance (#2714) * Add dkg epoch * Make epoch state advancement dependent only on time * Nym api tries advancing the dkg epoch state * Update the time table a bit It still needs to be changed before production, as the sign-up timeframe needs to be something like a few days. * Update changelog * Fix tests * Fix clippy after rustc update --- CHANGELOG.md | 2 + .../src/nymd/traits/dkg_query_client.rs | 6 +- .../src/nymd/traits/dkg_signing_client.rs | 16 ++ .../coconut-dkg/src/msg.rs | 1 - .../coconut-dkg/src/types.rs | 35 +++- contracts/coconut-dkg/src/contract.rs | 26 ++- contracts/coconut-dkg/src/dealers/queries.rs | 11 +- .../coconut-dkg/src/dealers/transactions.rs | 10 +- contracts/coconut-dkg/src/dealings/queries.rs | 6 - .../coconut-dkg/src/dealings/transactions.rs | 9 +- .../coconut-dkg/src/epoch_state/queries.rs | 23 ++- .../coconut-dkg/src/epoch_state/storage.rs | 4 +- .../src/epoch_state/transactions.rs | 178 +++++++++++++----- .../coconut-dkg/src/epoch_state/utils.rs | 14 +- contracts/coconut-dkg/src/error.rs | 3 + contracts/coconut-dkg/src/state/mod.rs | 1 - .../coconut-dkg/src/support/tests/fixtures.rs | 2 +- .../coconut-dkg/src/support/tests/helpers.rs | 1 - .../src/verification_key_shares/queries.rs | 5 - .../verification_key_shares/transactions.rs | 56 ++++-- .../src/submit_vk_creates_proposal.rs | 3 +- nym-api/src/coconut/client.rs | 5 +- nym-api/src/coconut/dkg/client.rs | 12 +- nym-api/src/coconut/dkg/controller.rs | 21 ++- nym-api/src/coconut/tests.rs | 18 +- nym-api/src/nymd_client.rs | 16 +- 26 files changed, 335 insertions(+), 149 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eaa8b5bea..2a6d66b676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - all-binaries: improved error logging ([#2686]) - native client: bring shutdown logic up to the same level as socks5-client +- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement ([#2670]) [#2686]: https://github.com/nymtech/nym/pull/2686 +[#2670]: https://github.com/nymtech/nym/pull/2670 ## [v1.1.3] (2022-12-13) diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs index b67627d316..ce69556137 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs @@ -8,13 +8,13 @@ use coconut_dkg_common::dealer::{ DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse, }; use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; -use coconut_dkg_common::types::EpochState; +use coconut_dkg_common::types::Epoch; use coconut_dkg_common::verification_key::PagedVKSharesResponse; use cosmrs::AccountId; #[async_trait] pub trait DkgQueryClient { - async fn get_current_epoch_state(&self) -> Result; + async fn get_current_epoch(&self) -> Result; async fn get_current_epoch_threshold(&self) -> Result, NymdError>; async fn get_dealer_details( &self, @@ -49,7 +49,7 @@ impl DkgQueryClient for NymdClient where C: CosmWasmClient + Send + Sync, { - async fn get_current_epoch_state(&self) -> Result { + async fn get_current_epoch(&self) -> Result { let request = DkgQueryMsg::GetCurrentEpochState {}; self.client .query_contract_smart(self.coconut_dkg_contract_address(), &request) diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs index b0d1dfaf08..6fee83ac5d 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs @@ -12,6 +12,7 @@ use contracts_common::dealings::ContractSafeBytes; #[async_trait] pub trait DkgSigningClient { + async fn advance_dkg_epoch_state(&self, fee: Option) -> Result; async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, @@ -37,6 +38,21 @@ impl DkgSigningClient for NymdClient where C: SigningCosmWasmClient + Send + Sync, { + async fn advance_dkg_epoch_state(&self, fee: Option) -> Result { + let req = DkgExecuteMsg::AdvanceEpochState {}; + + self.client + .execute( + self.address(), + self.coconut_dkg_contract_address(), + &req, + fee.unwrap_or_default(), + "advancing DKG state", + vec![], + ) + .await + } + async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index b2d0f1ae20..7a85486eb0 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -11,7 +11,6 @@ use serde::{Deserialize, Serialize}; pub struct InstantiateMsg { pub group_addr: String, pub multisig_addr: String, - pub admin: String, pub mix_denom: String, } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index ffb1653919..4de725cebf 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -6,15 +6,48 @@ use std::fmt::{Display, Formatter}; pub use crate::dealer::{DealerDetails, PagedDealerResponse}; pub use contracts_common::dealings::ContractSafeBytes; -pub use cosmwasm_std::{Addr, Coin}; +pub use cosmwasm_std::{Addr, Coin, Timestamp}; pub type EncodedBTEPublicKeyWithProof = String; pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str; pub type NodeIndex = u64; +// The time sign-up is open for dealers to join (2 minutes) +pub const PUBLIC_KEY_SUBMISSION_TIME_SECS: u64 = 60 * 2; +pub const DEALING_EXCHANGE_TIME_SECS: u64 = 60 * 5; +pub const VERIFICATION_KEY_SUBMISSION_TIME_SECS: u64 = 60 * 5; +pub const VERIFICATION_KEY_VALIDATION_TIME_SECS: u64 = 60; +pub const VERIFICATION_KEY_FINALIZATION_TIME_SECS: u64 = 60; +// The time an epoch lasts (2 weeks) +pub const IN_PROGRESS_TIME_SECS: u64 = 60 * 60 * 24 * 14; + // 2 public attributes, 2 private attributes, 1 fixed for coconut credential pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; +#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] +#[serde(rename_all = "snake_case")] +pub struct Epoch { + pub state: EpochState, + pub finish_timestamp: Timestamp, +} + +impl Epoch { + pub fn new(state: EpochState, current_timestamp: Timestamp) -> Self { + let duration = match state { + EpochState::PublicKeySubmission => PUBLIC_KEY_SUBMISSION_TIME_SECS, + EpochState::DealingExchange => DEALING_EXCHANGE_TIME_SECS, + EpochState::VerificationKeySubmission => VERIFICATION_KEY_SUBMISSION_TIME_SECS, + EpochState::VerificationKeyValidation => VERIFICATION_KEY_VALIDATION_TIME_SECS, + EpochState::VerificationKeyFinalization => VERIFICATION_KEY_FINALIZATION_TIME_SECS, + EpochState::InProgress => IN_PROGRESS_TIME_SECS, + }; + Epoch { + state, + finish_timestamp: current_timestamp.plus_seconds(duration), + } + } +} + // currently (it is still extremely likely to change, we might be able to get rid of verification key-related complaints), // the epoch can be in the following states (in order): // 1. PublicKeySubmission -> potential dealers are submitting their BTE and ed25519 public keys to participate in dealing exchange diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index 5394e845af..650cf312f0 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -7,16 +7,16 @@ use crate::dealers::queries::{ use crate::dealers::transactions::try_add_dealer; use crate::dealings::queries::query_dealings_paged; use crate::dealings::transactions::try_commit_dealings; -use crate::epoch_state::queries::{query_current_epoch_state, query_current_epoch_threshold}; -use crate::epoch_state::storage::CURRENT_EPOCH_STATE; +use crate::epoch_state::queries::{query_current_epoch, query_current_epoch_threshold}; +use crate::epoch_state::storage::CURRENT_EPOCH; use crate::epoch_state::transactions::advance_epoch_state; use crate::error::ContractError; -use crate::state::{State, ADMIN, MULTISIG, STATE}; +use crate::state::{State, MULTISIG, STATE}; use crate::verification_key_shares::queries::query_vk_shares_paged; use crate::verification_key_shares::transactions::try_commit_verification_key_share; use crate::verification_key_shares::transactions::try_verify_verification_key_share; use coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use coconut_dkg_common::types::EpochState; +use coconut_dkg_common::types::{Epoch, EpochState}; use cosmwasm_std::{ entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, }; @@ -30,13 +30,11 @@ use cw4::Cw4Contract; #[entry_point] pub fn instantiate( mut deps: DepsMut<'_>, - _env: Env, + env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> Result { - let admin_addr = deps.api.addr_validate(&msg.admin)?; let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?; - ADMIN.set(deps.branch(), Some(admin_addr))?; MULTISIG.set(deps.branch(), Some(multisig_addr.clone()))?; let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| { @@ -52,7 +50,10 @@ pub fn instantiate( }; STATE.save(deps.storage, &state)?; - CURRENT_EPOCH_STATE.save(deps.storage, &EpochState::default())?; + CURRENT_EPOCH.save( + deps.storage, + &Epoch::new(EpochState::default(), env.block.time), + )?; Ok(Response::default()) } @@ -79,14 +80,14 @@ pub fn execute( ExecuteMsg::VerifyVerificationKeyShare { owner } => { try_verify_verification_key_share(deps, info, owner) } - ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, info), + ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env), } } #[entry_point] pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { let response = match msg { - QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch_state(deps.storage)?)?, + QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch(deps.storage)?)?, QueryMsg::GetCurrentEpochThreshold {} => { to_binary(&query_current_epoch_threshold(deps.storage)?)? } @@ -120,9 +121,8 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result, mapping: &IndexedDealersMap<'_>, size: usize) { for n in 0..size { @@ -95,8 +94,7 @@ pub(crate) mod tests { #[test] fn dealers_empty_on_init() { - let mut deps = init_contract(); - let env = mock_env(); + let deps = init_contract(); for mapping in [storage::current_dealers(), storage::past_dealers()] { let page1 = query_dealers(deps.as_ref(), None, None, &mapping).unwrap(); @@ -107,8 +105,6 @@ pub(crate) mod tests { #[test] fn dealers_paged_retrieval_obeys_limits() { let mut deps = init_contract(); - let env = mock_env(); - let owner = Addr::unchecked("owner"); let limit = 2; for mapping in [storage::current_dealers(), storage::past_dealers()] { @@ -124,7 +120,6 @@ pub(crate) mod tests { #[test] fn dealers_paged_retrieval_has_default_limit() { let mut deps = init_contract(); - let env = mock_env(); for mapping in [storage::current_dealers(), storage::past_dealers()] { fill_dealers(deps.as_mut(), &mapping, 1000); @@ -141,7 +136,6 @@ pub(crate) mod tests { #[test] fn dealers_paged_retrieval_has_max_limit() { let mut deps = init_contract(); - let env = mock_env(); // query with a crazily high limit in an attempt to use too many resources let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT; @@ -163,7 +157,6 @@ pub(crate) mod tests { #[test] fn dealers_pagination_works() { let mut deps = init_contract(); - let env = mock_env(); let per_page = 2; diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index 6c5ccbb8db..6566581221 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -70,21 +70,21 @@ pub fn try_add_dealer( pub(crate) mod tests { use super::*; use crate::epoch_state::transactions::advance_epoch_state; - use crate::support::tests::fixtures::dealer_details_fixture; use crate::support::tests::helpers; - use crate::support::tests::helpers::ADMIN_ADDRESS; - use cosmwasm_std::testing::mock_info; + use coconut_dkg_common::types::PUBLIC_KEY_SUBMISSION_TIME_SECS; + use cosmwasm_std::testing::{mock_env, mock_info}; #[test] fn invalid_state() { let mut deps = helpers::init_contract(); let owner = Addr::unchecked("owner"); + let mut env = mock_env(); let info = mock_info(owner.as_str(), &[]); - let dealer_details = dealer_details_fixture(1); let bte_key_with_proof = String::from("bte_key_with_proof"); let announce_address = String::from("localhost:8000"); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); let ret = try_add_dealer( deps.as_mut(), diff --git a/contracts/coconut-dkg/src/dealings/queries.rs b/contracts/coconut-dkg/src/dealings/queries.rs index 7fe476a458..0bf0452c14 100644 --- a/contracts/coconut-dkg/src/dealings/queries.rs +++ b/contracts/coconut-dkg/src/dealings/queries.rs @@ -50,7 +50,6 @@ pub(crate) mod tests { use crate::dealings::storage::{DEALINGS_PAGE_DEFAULT_LIMIT, DEALINGS_PAGE_MAX_LIMIT}; use crate::support::tests::fixtures::dealing_bytes_fixture; use crate::support::tests::helpers::init_contract; - use cosmwasm_std::testing::mock_env; use cosmwasm_std::{Addr, DepsMut}; fn fill_dealings(deps: DepsMut<'_>, size: usize) { @@ -68,7 +67,6 @@ pub(crate) mod tests { #[test] fn empty_on_bad_idx() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1000); for idx in TOTAL_DEALINGS as u64..100 * TOTAL_DEALINGS as u64 { @@ -89,7 +87,6 @@ pub(crate) mod tests { #[test] fn dealings_paged_retrieval_obeys_limits() { let mut deps = init_contract(); - let env = mock_env(); let limit = 2; fill_dealings(deps.as_mut(), 1000); @@ -103,7 +100,6 @@ pub(crate) mod tests { #[test] fn dealings_paged_retrieval_has_default_limit() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1000); for idx in 0..TOTAL_DEALINGS as u64 { @@ -117,7 +113,6 @@ pub(crate) mod tests { #[test] fn dealings_paged_retrieval_has_max_limit() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1000); // query with a crazily high limit in an attempt to use too many resources @@ -135,7 +130,6 @@ pub(crate) mod tests { #[test] fn dealings_pagination_works() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1); diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index 7320768979..fe2e5544cb 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -42,16 +42,16 @@ pub(crate) mod tests { use crate::epoch_state::transactions::advance_epoch_state; use crate::support::tests::fixtures::dealing_bytes_fixture; use crate::support::tests::helpers; - use crate::support::tests::helpers::ADMIN_ADDRESS; use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::TOTAL_DEALINGS; - use cosmwasm_std::testing::mock_info; + use coconut_dkg_common::types::PUBLIC_KEY_SUBMISSION_TIME_SECS; + use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::Addr; #[test] fn invalid_commit_dealing() { let mut deps = helpers::init_contract(); let owner = Addr::unchecked("owner"); + let mut env = mock_env(); let info = mock_info(owner.as_str(), &[]); let dealing_bytes = dealing_bytes_fixture(); @@ -65,7 +65,8 @@ pub(crate) mod tests { } ); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err(); diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs index a7027ca488..3a68e0117c 100644 --- a/contracts/coconut-dkg/src/epoch_state/queries.rs +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -1,15 +1,13 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::{CURRENT_EPOCH_STATE, THRESHOLD}; +use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD}; use crate::error::ContractError; -use coconut_dkg_common::types::EpochState; +use coconut_dkg_common::types::Epoch; use cosmwasm_std::Storage; -pub(crate) fn query_current_epoch_state( - storage: &dyn Storage, -) -> Result { - CURRENT_EPOCH_STATE +pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result { + CURRENT_EPOCH .load(storage) .map_err(|_| ContractError::EpochNotInitialised) } @@ -24,12 +22,21 @@ pub(crate) fn query_current_epoch_threshold( pub(crate) mod test { use super::*; use crate::support::tests::helpers::init_contract; + use coconut_dkg_common::types::{EpochState, PUBLIC_KEY_SUBMISSION_TIME_SECS}; + use cosmwasm_std::testing::mock_env; #[test] fn query_state() { let mut deps = init_contract(); - let state = query_current_epoch_state(deps.as_mut().storage).unwrap(); - assert_eq!(state, EpochState::PublicKeySubmission); + let epoch = query_current_epoch(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::PublicKeySubmission); + assert_eq!( + epoch.finish_timestamp, + mock_env() + .block + .time + .plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS) + ); } #[test] diff --git a/contracts/coconut-dkg/src/epoch_state/storage.rs b/contracts/coconut-dkg/src/epoch_state/storage.rs index b5c670d899..d5aac5b041 100644 --- a/contracts/coconut-dkg/src/epoch_state/storage.rs +++ b/contracts/coconut-dkg/src/epoch_state/storage.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::types::EpochState; +use coconut_dkg_common::types::Epoch; use cw_storage_plus::Item; -pub(crate) const CURRENT_EPOCH_STATE: Item<'_, EpochState> = Item::new("current_epoch_state"); +pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch"); pub const THRESHOLD: Item = Item::new("threshold"); diff --git a/contracts/coconut-dkg/src/epoch_state/transactions.rs b/contracts/coconut-dkg/src/epoch_state/transactions.rs index fbb597f668..e2e75a1678 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions.rs @@ -2,25 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage::current_dealers; -use crate::epoch_state::storage::{CURRENT_EPOCH_STATE, THRESHOLD}; +use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD}; use crate::error::ContractError; -use crate::state::ADMIN; -use coconut_dkg_common::types::EpochState; -use cosmwasm_std::{DepsMut, MessageInfo, Order, Response}; +use coconut_dkg_common::types::{Epoch, EpochState}; +use cosmwasm_std::{DepsMut, Env, Order, Response}; -pub(crate) fn advance_epoch_state( - deps: DepsMut<'_>, - info: MessageInfo, -) -> Result { - ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - let current_epoch_state = - CURRENT_EPOCH_STATE.update::<_, ContractError>(deps.storage, |mut epoch_state| { - // TODO: When defaulting to the first state, some action will probably need to be taken on the - // rest of the contract, as we're starting with a new set of signers - epoch_state = epoch_state.next().unwrap_or_default(); - Ok(epoch_state) - })?; - if current_epoch_state == EpochState::DealingExchange { +pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result { + let epoch = CURRENT_EPOCH.load(deps.storage)?; + if epoch.finish_timestamp > env.block.time { + return Err(ContractError::EarlyEpochStateAdvancement( + epoch + .finish_timestamp + .minus_seconds(env.block.time.seconds()) + .seconds(), + )); + } + + let current_epoch = CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |mut epoch| { + // TODO: When defaulting to the first state, some action will probably need to be taken on the + // rest of the contract, as we're starting with a new set of signers + epoch = Epoch::new(epoch.state.next().unwrap_or_default(), env.block.time); + Ok(epoch) + })?; + if current_epoch.state == EpochState::DealingExchange { let current_dealer_count = current_dealers() .keys(deps.storage, None, None, Order::Ascending) .count(); @@ -34,64 +38,147 @@ pub(crate) fn advance_epoch_state( #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS}; - use coconut_dkg_common::types::{DealerDetails, EpochState}; - use cosmwasm_std::testing::mock_info; + use crate::error::ContractError::EarlyEpochStateAdvancement; + use crate::support::tests::helpers::init_contract; + use coconut_dkg_common::types::{ + DealerDetails, EpochState, DEALING_EXCHANGE_TIME_SECS, IN_PROGRESS_TIME_SECS, + PUBLIC_KEY_SUBMISSION_TIME_SECS, VERIFICATION_KEY_FINALIZATION_TIME_SECS, + VERIFICATION_KEY_SUBMISSION_TIME_SECS, VERIFICATION_KEY_VALIDATION_TIME_SECS, + }; + use cosmwasm_std::testing::mock_env; use cosmwasm_std::Addr; - use cw_controllers::AdminError; #[test] fn advance_state() { let mut deps = init_contract(); - let info = mock_info("requester", &[]); - let admin_info = mock_info(ADMIN_ADDRESS, &[]); + let mut env = mock_env(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::PublicKeySubmission); assert_eq!( - advance_epoch_state(deps.as_mut(), info).unwrap_err(), - ContractError::Admin(AdminError::NotAdmin {}) + epoch.finish_timestamp, + env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS - 1); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::DealingExchange + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(1) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(1); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::DealingExchange); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::VerificationKeySubmission + epoch.finish_timestamp, + env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS - 2); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::VerificationKeyValidation + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(2) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(3); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::VerificationKeySubmission); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::VerificationKeyFinalization + epoch.finish_timestamp, + env.block + .time + .plus_seconds(VERIFICATION_KEY_SUBMISSION_TIME_SECS) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_SUBMISSION_TIME_SECS - 2); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::InProgress + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(2) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(3); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::VerificationKeyValidation); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::PublicKeySubmission + epoch.finish_timestamp, + env.block + .time + .plus_seconds(VERIFICATION_KEY_VALIDATION_TIME_SECS) + ); + + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_VALIDATION_TIME_SECS - 3); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(3) + ); + + env.block.time = env.block.time.plus_seconds(3); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::VerificationKeyFinalization); + assert_eq!( + epoch.finish_timestamp, + env.block + .time + .plus_seconds(VERIFICATION_KEY_FINALIZATION_TIME_SECS) + ); + + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_FINALIZATION_TIME_SECS - 1); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(1) + ); + + env.block.time = env.block.time.plus_seconds(1); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::InProgress); + assert_eq!( + epoch.finish_timestamp, + env.block.time.plus_seconds(IN_PROGRESS_TIME_SECS) + ); + + env.block.time = env.block.time.plus_seconds(IN_PROGRESS_TIME_SECS - 100); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(100) + ); + + env.block.time = env.block.time.plus_seconds(50); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(50) + ); + + env.block.time = env.block.time.plus_seconds(100); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::PublicKeySubmission); + assert_eq!( + epoch.finish_timestamp, + env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS) ); } #[test] fn verify_threshold() { let mut deps = init_contract(); - let admin_info = mock_info(ADMIN_ADDRESS, &[]); + let mut env = mock_env(); assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none()); @@ -111,7 +198,8 @@ pub(crate) mod tests { .unwrap(); } - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); assert_eq!( THRESHOLD.may_load(deps.as_mut().storage).unwrap().unwrap(), 67 diff --git a/contracts/coconut-dkg/src/epoch_state/utils.rs b/contracts/coconut-dkg/src/epoch_state/utils.rs index b944ad361d..280436ee2e 100644 --- a/contracts/coconut-dkg/src/epoch_state/utils.rs +++ b/contracts/coconut-dkg/src/epoch_state/utils.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::CURRENT_EPOCH_STATE; +use crate::epoch_state::storage::CURRENT_EPOCH; use crate::error::ContractError; use coconut_dkg_common::types::EpochState; use cosmwasm_std::Storage; @@ -10,7 +10,7 @@ pub(crate) fn check_epoch_state( storage: &dyn Storage, against: EpochState, ) -> Result<(), ContractError> { - let epoch_state = CURRENT_EPOCH_STATE.load(storage)?; + let epoch_state = CURRENT_EPOCH.load(storage)?.state; if epoch_state != against { Err(ContractError::IncorrectEpochState { current_state: epoch_state.to_string(), @@ -25,14 +25,20 @@ pub(crate) fn check_epoch_state( pub(crate) mod test { use super::*; use crate::support::tests::helpers::init_contract; + use coconut_dkg_common::types::Epoch; + use cosmwasm_std::testing::mock_env; #[test] pub fn check_state() { let mut deps = init_contract(); + let env = mock_env(); for fixed_state in EpochState::default().all_until(EpochState::InProgress) { - CURRENT_EPOCH_STATE - .save(deps.as_mut().storage, &fixed_state) + CURRENT_EPOCH + .save( + deps.as_mut().storage, + &Epoch::new(fixed_state, env.block.time), + ) .unwrap(); for against_state in EpochState::default().all_until(EpochState::InProgress) { let ret = check_epoch_state(deps.as_mut().storage, against_state); diff --git a/contracts/coconut-dkg/src/error.rs b/contracts/coconut-dkg/src/error.rs index cd359579d0..0baeae5edd 100644 --- a/contracts/coconut-dkg/src/error.rs +++ b/contracts/coconut-dkg/src/error.rs @@ -23,6 +23,9 @@ pub enum ContractError { #[error("This sender is already a dealer for the epoch")] AlreadyADealer, + #[error("Too soon to advance epoch state. {0} more seconds until it can be advanced")] + EarlyEpochStateAdvancement(u64), + #[error("Epoch hasn't been correctly initialised!")] EpochNotInitialised, diff --git a/contracts/coconut-dkg/src/state/mod.rs b/contracts/coconut-dkg/src/state/mod.rs index 729618ec60..533bd98a0a 100644 --- a/contracts/coconut-dkg/src/state/mod.rs +++ b/contracts/coconut-dkg/src/state/mod.rs @@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize}; // unique items pub const STATE: Item = Item::new("state"); -pub const ADMIN: Admin = Admin::new("admin"); pub const MULTISIG: Admin = Admin::new("multisig"); #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] diff --git a/contracts/coconut-dkg/src/support/tests/fixtures.rs b/contracts/coconut-dkg/src/support/tests/fixtures.rs index e0bdee4e4d..19aad555d1 100644 --- a/contracts/coconut-dkg/src/support/tests/fixtures.rs +++ b/contracts/coconut-dkg/src/support/tests/fixtures.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::dealer::{ContractDealing, DealerDetails}; +use coconut_dkg_common::dealer::DealerDetails; use coconut_dkg_common::types::ContractSafeBytes; use coconut_dkg_common::verification_key::ContractVKShare; use cosmwasm_std::Addr; diff --git a/contracts/coconut-dkg/src/support/tests/helpers.rs b/contracts/coconut-dkg/src/support/tests/helpers.rs index ec46ff0215..b59ee40bcf 100644 --- a/contracts/coconut-dkg/src/support/tests/helpers.rs +++ b/contracts/coconut-dkg/src/support/tests/helpers.rs @@ -17,7 +17,6 @@ pub fn init_contract() -> OwnedDeps> let msg = InstantiateMsg { group_addr: String::from(GROUP_CONTRACT), multisig_addr: String::from(MULTISIG_CONTRACT), - admin: String::from(ADMIN_ADDRESS), mix_denom: TEST_MIX_DENOM.to_string(), }; let env = mock_env(); diff --git a/contracts/coconut-dkg/src/verification_key_shares/queries.rs b/contracts/coconut-dkg/src/verification_key_shares/queries.rs index e9c4d00dca..331c42e58a 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/queries.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/queries.rs @@ -45,7 +45,6 @@ pub(crate) mod tests { use crate::verification_key_shares::storage::{ VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT, VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT, }; - use cosmwasm_std::testing::mock_env; use cosmwasm_std::Addr; #[test] @@ -58,7 +57,6 @@ pub(crate) mod tests { #[test] fn vk_shares_paged_retrieval_obeys_limits() { let mut deps = init_contract(); - let env = mock_env(); let limit = 2; for n in 0..1000 { let vk_share = vk_share_fixture(n); @@ -75,7 +73,6 @@ pub(crate) mod tests { #[test] fn vk_shares_paged_retrieval_has_default_limit() { let mut deps = init_contract(); - let env = mock_env(); for n in 0..1000 { let vk_share = vk_share_fixture(n); let sender = Addr::unchecked(format!("owner{}", n)); @@ -96,7 +93,6 @@ pub(crate) mod tests { #[test] fn vk_shares_paged_retrieval_has_max_limit() { let mut deps = init_contract(); - let env = mock_env(); for n in 0..1000 { let vk_share = vk_share_fixture(n); let sender = Addr::unchecked(format!("owner{}", n)); @@ -117,7 +113,6 @@ pub(crate) mod tests { #[test] fn vk_shares_pagination_works() { let mut deps = init_contract(); - let env = mock_env(); let vk_share = vk_share_fixture(1); let sender = Addr::unchecked(format!("owner{}", 1)); diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index 0f76fc7811..69df1bad7b 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -73,20 +73,21 @@ pub fn try_verify_verification_key_share( #[cfg(test)] mod tests { use super::*; - use crate::epoch_state::storage::CURRENT_EPOCH_STATE; use crate::epoch_state::transactions::advance_epoch_state; use crate::support::tests::helpers; - use crate::support::tests::helpers::{ADMIN_ADDRESS, MULTISIG_CONTRACT}; + use crate::support::tests::helpers::MULTISIG_CONTRACT; use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::EpochState; + use coconut_dkg_common::types::{ + EpochState, DEALING_EXCHANGE_TIME_SECS, PUBLIC_KEY_SUBMISSION_TIME_SECS, + VERIFICATION_KEY_SUBMISSION_TIME_SECS, VERIFICATION_KEY_VALIDATION_TIME_SECS, + }; use cosmwasm_std::testing::{mock_env, mock_info}; - use cosmwasm_std::Storage; use cw_controllers::AdminError; #[test] fn commit_vk_share() { let mut deps = helpers::init_contract(); - let env = mock_env(); + let mut env = mock_env(); let info = mock_info("requester", &[]); let share = "share".to_string(); @@ -104,8 +105,10 @@ mod tests { expected_state: EpochState::VerificationKeySubmission.to_string() } ); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let ret = try_commit_verification_key_share( deps.as_mut(), env.clone(), @@ -147,6 +150,7 @@ mod tests { #[test] fn invalid_verify_vk_share() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let info = mock_info("requester", &[]); let owner = Addr::unchecked("owner"); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); @@ -161,10 +165,20 @@ mod tests { } ); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_VALIDATION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone()).unwrap_err(); @@ -183,14 +197,16 @@ mod tests { #[test] fn verify_vk_share() { let mut deps = helpers::init_contract(); - let env = mock_env(); + let mut env = mock_env(); let owner = Addr::unchecked("owner"); let info = mock_info(owner.as_ref(), &[]); let share = "share".to_string(); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let dealer_details = DealerDetails { address: owner.clone(), @@ -204,8 +220,16 @@ mod tests { try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone()) .unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_VALIDATION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone()).unwrap(); } diff --git a/contracts/coconut-test/src/submit_vk_creates_proposal.rs b/contracts/coconut-test/src/submit_vk_creates_proposal.rs index 15bbd65341..1122115b6d 100644 --- a/contracts/coconut-test/src/submit_vk_creates_proposal.rs +++ b/contracts/coconut-test/src/submit_vk_creates_proposal.rs @@ -71,7 +71,6 @@ fn dkg_proposal() { let msg = DkgInstantiateMsg { group_addr: group_contract_addr.to_string(), multisig_addr: multisig_contract_addr.to_string(), - admin: Addr::unchecked(OWNER).to_string(), mix_denom: TEST_COIN_DENOM.to_string(), }; let coconut_dkg_contract_addr = app @@ -109,6 +108,7 @@ fn dkg_proposal() { .unwrap(); for _ in 0..2 { + app.update_block(|block| block.time = block.time.plus_seconds(1000)); app.execute_contract( Addr::unchecked(OWNER), coconut_dkg_contract_addr.clone(), @@ -175,6 +175,7 @@ fn dkg_proposal() { .unwrap(); for _ in 0..2 { + app.update_block(|block| block.time = block.time.plus_seconds(1000)); app.execute_contract( Addr::unchecked(OWNER), coconut_dkg_contract_addr.clone(), diff --git a/nym-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs index 87a64a4e6e..92af589921 100644 --- a/nym-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -4,7 +4,7 @@ use crate::coconut::error::Result; use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch}; use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use contracts_common::dealings::ContractSafeBytes; use cw3::ProposalResponse; @@ -22,7 +22,7 @@ pub trait Client { &self, blinded_serial_number: String, ) -> Result; - async fn get_current_epoch_state(&self) -> Result; + async fn get_current_epoch(&self) -> Result; async fn get_current_epoch_threshold(&self) -> Result>; async fn get_self_registered_dealer_details(&self) -> Result; async fn get_current_dealers(&self) -> Result>; @@ -31,6 +31,7 @@ pub trait Client { async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option) -> Result<()>; async fn execute_proposal(&self, proposal_id: u64) -> Result<()>; + async fn advance_epoch_state(&self) -> Result<()>; async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, diff --git a/nym-api/src/coconut/dkg/client.rs b/nym-api/src/coconut/dkg/client.rs index d8a0cf5d69..61b583b84b 100644 --- a/nym-api/src/coconut/dkg/client.rs +++ b/nym-api/src/coconut/dkg/client.rs @@ -4,7 +4,7 @@ use crate::coconut::client::Client; use crate::coconut::error::CoconutError; use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, NodeIndex}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, NodeIndex}; use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use contracts_common::dealings::ContractSafeBytes; use cw3::ProposalResponse; @@ -35,13 +35,13 @@ impl DkgClient { self.inner.address().await } - pub(crate) async fn get_current_epoch_state(&self) -> Result { - let mut ret = self.inner.get_current_epoch_state().await; + pub(crate) async fn get_current_epoch(&self) -> Result { + let mut ret = self.inner.get_current_epoch().await; for _ in 0..Self::RETRIES { if ret.is_ok() { return ret; } - ret = self.inner.get_current_epoch_state().await; + ret = self.inner.get_current_epoch().await; } ret } @@ -86,6 +86,10 @@ impl DkgClient { self.inner.list_proposals().await } + pub(crate) async fn advance_epoch_state(&self) -> Result<(), CoconutError> { + self.inner.advance_epoch_state().await + } + pub(crate) async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, diff --git a/nym-api/src/coconut/dkg/controller.rs b/nym-api/src/coconut/dkg/controller.rs index c54074141d..bebb68f4d1 100644 --- a/nym-api/src/coconut/dkg/controller.rs +++ b/nym-api/src/coconut/dkg/controller.rs @@ -18,7 +18,7 @@ use dkg::bte::keys::KeyPair as DkgKeyPair; use rand::rngs::OsRng; use rand::RngCore; use std::path::PathBuf; -use std::time::Duration; +use std::time::{Duration, SystemTime}; use task::TaskClient; use tokio::time::interval; use validator_client::nymd::SigningNymdClient; @@ -83,16 +83,16 @@ impl DkgController { } pub(crate) async fn handle_epoch_state(&mut self) { - match self.dkg_client.get_current_epoch_state().await { + match self.dkg_client.get_current_epoch().await { Err(e) => warn!("Could not get current epoch state {}", e), - Ok(epoch_state) => { - if let Err(e) = self.state.is_consistent(epoch_state).await { + Ok(epoch) => { + if let Err(e) = self.state.is_consistent(epoch.state).await { error!( "Epoch state is corrupted - {}, the process should be terminated", e ); } - let ret = match epoch_state { + let ret = match epoch.state { EpochState::PublicKeySubmission => { public_key_submission(&self.dkg_client, &mut self.state).await } @@ -122,7 +122,7 @@ impl DkgController { }; if let Err(e) = ret { warn!("Could not handle this iteration for the epoch state: {}", e); - } else if epoch_state != EpochState::InProgress { + } else if epoch.state != EpochState::InProgress { let persistent_state = PersistentState::from(&self.state); if let Err(e) = persistent_state.save_to_file(self.state.persistent_state_path()) @@ -130,6 +130,15 @@ impl DkgController { warn!("Could not backup the state for this iteration: {}", e); } } + if let Ok(current_timestamp) = + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + { + if current_timestamp.as_secs() >= epoch.finish_timestamp.seconds() { + // We try advancing the epoch state, on a best-effort basis + info!("Trying to advance the epoch"); + self.dkg_client.advance_epoch_state().await.ok(); + } + } } } } diff --git a/nym-api/src/coconut/tests.rs b/nym-api/src/coconut/tests.rs index 3408dda0a3..d0165e1f1e 100644 --- a/nym-api/src/coconut/tests.rs +++ b/nym-api/src/coconut/tests.rs @@ -40,7 +40,7 @@ use coconut_dkg_common::dealer::{ ContractDealing, DealerDetails, DealerDetailsResponse, DealerType, }; use coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX}; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, TOTAL_DEALINGS}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, TOTAL_DEALINGS}; use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use contracts_common::dealings::ContractSafeBytes; use crypto::asymmetric::{encryption, identity}; @@ -66,7 +66,7 @@ pub(crate) struct DummyClient { proposal_db: Arc>>, spent_credential_db: Arc>>, - epoch_state: Arc>, + epoch: Arc>, dealer_details: Arc>>, threshold: Arc>>, dealings: Arc>>>, @@ -80,7 +80,7 @@ impl DummyClient { tx_db: Arc::new(RwLock::new(HashMap::new())), proposal_db: Arc::new(RwLock::new(HashMap::new())), spent_credential_db: Arc::new(RwLock::new(HashMap::new())), - epoch_state: Arc::new(RwLock::new(EpochState::default())), + epoch: Arc::new(RwLock::new(Epoch::default())), dealer_details: Arc::new(RwLock::new(HashMap::new())), threshold: Arc::new(RwLock::new(None)), dealings: Arc::new(RwLock::new(HashMap::new())), @@ -109,8 +109,8 @@ impl DummyClient { self } - pub fn _with_epoch_state(mut self, epoch_state: &Arc>) -> Self { - self.epoch_state = Arc::clone(epoch_state); + pub fn _with_epoch(mut self, epoch: &Arc>) -> Self { + self.epoch = Arc::clone(epoch); self } @@ -188,8 +188,8 @@ impl super::client::Client for DummyClient { }) } - async fn get_current_epoch_state(&self) -> Result { - Ok(*self.epoch_state.read().unwrap()) + async fn get_current_epoch(&self) -> Result { + Ok(*self.epoch.read().unwrap()) } async fn get_current_epoch_threshold(&self) -> Result> { @@ -273,6 +273,10 @@ impl super::client::Client for DummyClient { Ok(()) } + async fn advance_epoch_state(&self) -> Result<()> { + todo!() + } + async fn register_dealer( &self, bte_public_key_with_proof: EncodedBTEPublicKeyWithProof, diff --git a/nym-api/src/nymd_client.rs b/nym-api/src/nymd_client.rs index d14a793fd4..f7780b2871 100644 --- a/nym-api/src/nymd_client.rs +++ b/nym-api/src/nymd_client.rs @@ -30,7 +30,7 @@ use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse #[cfg(feature = "coconut")] use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; #[cfg(feature = "coconut")] -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch}; #[cfg(feature = "coconut")] use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; #[cfg(feature = "coconut")] @@ -329,8 +329,8 @@ where .await?) } - async fn get_current_epoch_state(&self) -> crate::coconut::error::Result { - Ok(self.0.read().await.nymd.get_current_epoch_state().await?) + async fn get_current_epoch(&self) -> crate::coconut::error::Result { + Ok(self.0.read().await.nymd.get_current_epoch().await?) } async fn get_current_epoch_threshold( @@ -405,6 +405,16 @@ where Ok(()) } + async fn advance_epoch_state(&self) -> crate::coconut::error::Result<()> { + self.0 + .write() + .await + .nymd + .advance_dkg_epoch_state(None) + .await?; + Ok(()) + } + async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof,