From 46875cdf2fedb7a2e315f97c5d2a6d8955fb48e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 14 Feb 2024 14:31:46 +0000 Subject: [PATCH] moved epoch advancement logic into separate file --- .../coconut-dkg/src/types.rs | 8 +- common/credential-utils/src/utils.rs | 2 +- contracts/coconut-dkg/src/contract.rs | 4 +- .../coconut-dkg/src/dealers/transactions.rs | 4 +- .../coconut-dkg/src/dealings/transactions.rs | 4 +- .../coconut-dkg/src/epoch_state/queries.rs | 4 +- .../transactions/advance_epoch_state.rs | 449 ++++++++++++++++++ .../{transactions.rs => transactions/mod.rs} | 443 +---------------- .../coconut-dkg/src/support/tests/helpers.rs | 1 + .../verification_key_shares/transactions.rs | 26 +- .../src/redoing_upon_threshold.rs | 29 -- .../coconut-test/src/reset_upon_threshold.rs | 65 +++ contracts/coconut-test/src/test_wrapper.rs | 16 +- contracts/coconut-test/src/tests.rs | 2 +- nym-api/src/coconut/comm.rs | 2 +- nym-api/src/coconut/dkg/controller/mod.rs | 2 +- 16 files changed, 575 insertions(+), 486 deletions(-) create mode 100644 contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs rename contracts/coconut-dkg/src/epoch_state/{transactions.rs => transactions/mod.rs} (51%) delete mode 100644 contracts/coconut-test/src/redoing_upon_threshold.rs create mode 100644 contracts/coconut-test/src/reset_upon_threshold.rs diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index c68c1f9515..329c7408b4 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -93,7 +93,9 @@ pub struct Epoch { pub state: EpochState, pub epoch_id: EpochId, pub time_configuration: TimeConfiguration, - pub finish_timestamp: Option, + + #[serde(alias = "finish_timestamp")] + pub deadline: Option, } impl Epoch { @@ -126,12 +128,12 @@ impl Epoch { state, epoch_id, time_configuration, - finish_timestamp: duration.map(|d| current_timestamp.plus_seconds(d)), + deadline: duration.map(|d| current_timestamp.plus_seconds(d)), } } pub fn final_timestamp_secs(&self) -> Option { - let mut finish = self.finish_timestamp?.seconds(); + let mut finish = self.deadline?.seconds(); let time_configuration = self.time_configuration; let mut curr_epoch_state = self.state; while let Some(state) = curr_epoch_state.next() { diff --git a/common/credential-utils/src/utils.rs b/common/credential-utils/src/utils.rs index c937d48fc3..e1e7c09850 100644 --- a/common/credential-utils/src/utils.rs +++ b/common/credential-utils/src/utils.rs @@ -91,7 +91,7 @@ where .as_secs(); if epoch.state.is_final() { - if let Some(finish_timestamp) = epoch.finish_timestamp { + if let Some(finish_timestamp) = epoch.deadline { if current_timestamp_secs + SAFETY_BUFFER_SECS >= finish_timestamp.seconds() { info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60); exit(0); diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index 1ab567d59c..9937ff820a 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -15,7 +15,7 @@ use crate::epoch_state::queries::{ }; use crate::epoch_state::storage::CURRENT_EPOCH; use crate::epoch_state::transactions::{ - advance_epoch_state, try_initiate_dkg, try_surpassed_threshold, + try_advance_epoch_state, try_initiate_dkg, try_surpassed_threshold, }; use crate::error::ContractError; use crate::state::queries::query_state; @@ -118,7 +118,7 @@ pub fn execute( try_verify_verification_key_share(deps, info, owner, resharing) } ExecuteMsg::SurpassedThreshold {} => try_surpassed_threshold(deps, env), - ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env), + ExecuteMsg::AdvanceEpochState {} => try_advance_epoch_state(deps, env), } } diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index 7c47f655d4..8e974379a3 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -81,7 +81,7 @@ pub fn try_add_dealer( pub(crate) mod tests { use super::*; use crate::dealers::storage::current_dealers; - use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg}; + use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg}; use crate::support::tests::fixtures::dealer_details_fixture; use crate::support::tests::helpers; use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, GROUP_MEMBERS}; @@ -156,7 +156,7 @@ pub(crate) mod tests { .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); add_fixture_dealer(deps.as_mut()); - advance_epoch_state(deps.as_mut(), env).unwrap(); + try_advance_epoch_state(deps.as_mut(), env).unwrap(); let ret = try_add_dealer( deps.as_mut(), diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index 07b2298bb9..e64c4d575c 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -206,7 +206,7 @@ pub fn try_commit_dealings_chunk( pub(crate) mod tests { use super::*; use crate::epoch_state::storage::CURRENT_EPOCH; - use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg}; + use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg}; use crate::support::tests::fixtures::{ dealer_details_fixture, dealing_metadata_fixture, partial_dealing_fixture, }; @@ -250,7 +250,7 @@ pub(crate) mod tests { .time .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); add_fixture_dealer(deps.as_mut()); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let ret = try_commit_dealings_chunk( deps.as_mut(), diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs index 6c07c65629..b4dc6cd310 100644 --- a/contracts/coconut-dkg/src/epoch_state/queries.rs +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -37,7 +37,7 @@ pub(crate) mod test { let mut deps = init_contract(); let epoch = query_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!(epoch.state, EpochState::WaitingInitialisation); - assert_eq!(epoch.finish_timestamp, None); + assert_eq!(epoch.deadline, None); let env = mock_env(); try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); @@ -48,7 +48,7 @@ pub(crate) mod test { EpochState::PublicKeySubmission { resharing: false } ); assert_eq!( - epoch.finish_timestamp.unwrap(), + epoch.deadline.unwrap(), env.block .time .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs) diff --git a/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs b/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs new file mode 100644 index 0000000000..9aeceb8fe1 --- /dev/null +++ b/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs @@ -0,0 +1,449 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::dealers::storage::current_dealers; +use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD}; +use crate::epoch_state::transactions::{ + dealers_eq_members, replacement_threshold_surpassed, reset_dkg_state, +}; +use crate::error::ContractError; +use crate::state::storage::STATE; +use crate::verification_key_shares::storage::verified_dealers; +use cosmwasm_std::{Addr, DepsMut, Env, Order, Response}; +use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData}; + +pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result { + let current_epoch = CURRENT_EPOCH.load(deps.storage)?; + if current_epoch.state == EpochState::WaitingInitialisation { + return Err(ContractError::WaitingInitialisation); + } + + if let Some(finish_timestamp) = current_epoch.deadline { + if finish_timestamp > env.block.time { + return Err(ContractError::EarlyEpochStateAdvancement( + finish_timestamp + .minus_seconds(env.block.time.seconds()) + .seconds(), + )); + } + } + + let next_epoch = if let Some(state) = current_epoch.state.next() { + // We are during DKG process + let mut new_state = state; + if let EpochState::DealingExchange { .. } = state { + let current_dealers = current_dealers() + .keys(deps.storage, None, None, Order::Ascending) + .collect::, _>>()?; + let group_members = + STATE + .load(deps.storage)? + .group_addr + .list_members(&deps.querier, None, None)?; + if current_dealers.len() < group_members.len() { + // If not all group members registered yet, we just stay in the same state until + // they either register or they get kicked out of the group + new_state = current_epoch.state; + } else { + // note: ceiling in integer division can be achieved via q = (x + y - 1) / y; + let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3; + THRESHOLD.save(deps.storage, &threshold)?; + } + }; + Epoch::new( + new_state, + current_epoch.epoch_id, + current_epoch.time_configuration, + env.block.time, + ) + } else if dealers_eq_members(&deps)? { + // The dealer set hasn't changed, so we only extend the finish timestamp + // The epoch remains the same, as we use it as key for storing VKs + + // TODO: change that behaviour in the following PR + Epoch::new( + current_epoch.state, + current_epoch.epoch_id, + current_epoch.time_configuration, + env.block.time, + ) + } else { + // Dealer set changed, we need to redo DKG... + let state = if replacement_threshold_surpassed(&deps)? { + // ... in reset mode + INITIAL_REPLACEMENT_DATA.remove(deps.storage); + EpochState::PublicKeySubmission { resharing: false } + } else { + // ... in reshare mode + if INITIAL_REPLACEMENT_DATA.may_load(deps.storage)?.is_some() { + INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| { + // TODO: FIXME: for second reshare the added set of dealers won't be allowed to participate + data.initial_height = env.block.height; + Ok(data) + })?; + } else { + let replacement_data = InitialReplacementData { + initial_dealers: verified_dealers(deps.storage)?, + initial_height: env.block.height, + }; + INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?; + } + + EpochState::PublicKeySubmission { resharing: true } + }; + reset_dkg_state(deps.storage)?; + Epoch::new( + state, + current_epoch.epoch_id + 1, + current_epoch.time_configuration, + env.block.time, + ) + }; + CURRENT_EPOCH.save(deps.storage, &next_epoch)?; + + Ok(Response::default()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dealers::storage::past_dealers; + use crate::epoch_state::transactions::try_initiate_dkg; + use crate::error::ContractError::EarlyEpochStateAdvancement; + use crate::support::tests::fixtures::{dealer_details_fixture, vk_share_fixture}; + use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS, GROUP_MEMBERS}; + use crate::verification_key_shares::storage::vk_shares; + use cosmwasm_std::testing::{mock_env, mock_info}; + use cw4::Member; + use nym_coconut_dkg_common::types::TimeConfiguration; + + #[test] + fn advance_state() { + let mut deps = init_contract(); + let mut env = mock_env(); + + { + let mut group = GROUP_MEMBERS.lock().unwrap(); + + group.push(( + Member { + addr: "owner1".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner2".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner3".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner4".to_string(), + weight: 10, + }, + 1, + )); + } + + // can't advance the state if dkg hasn't been initiated + assert_eq!( + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + ContractError::WaitingInitialisation + ); + + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!( + epoch.state, + EpochState::PublicKeySubmission { resharing: false } + ); + assert_eq!( + epoch.deadline.unwrap(), + env.block + .time + .plus_seconds(epoch.time_configuration.public_key_submission_time_secs) + ); + + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.public_key_submission_time_secs - 1); + assert_eq!( + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(1) + ); + + env.block.time = env.block.time.plus_seconds(1); + try_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 { resharing: false } + ); + + // setup dealer details + let all_shares: [_; 4] = + std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0)); + for share in all_shares.iter() { + vk_shares() + .save(deps.as_mut().storage, (&share.owner, 0), share) + .unwrap(); + } + let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1)); + for details in all_details.iter() { + current_dealers() + .save(deps.as_mut().storage, &details.address, details) + .unwrap(); + } + + assert!(INITIAL_REPLACEMENT_DATA + .may_load(&deps.storage) + .unwrap() + .is_none()); + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.public_key_submission_time_secs); + try_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 { resharing: false } + ); + assert_eq!( + epoch.deadline.unwrap(), + env.block + .time + .plus_seconds(epoch.time_configuration.dealing_exchange_time_secs) + ); + + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.dealing_exchange_time_secs - 2); + assert_eq!( + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(2) + ); + + env.block.time = env.block.time.plus_seconds(3); + try_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 { resharing: false } + ); + assert_eq!( + epoch.deadline.unwrap(), + env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_submission_time_secs + ) + ); + + env.block.time = env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_submission_time_secs + - 2, + ); + assert_eq!( + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(2) + ); + + env.block.time = env.block.time.plus_seconds(3); + try_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 { resharing: false } + ); + assert_eq!( + epoch.deadline.unwrap(), + env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_validation_time_secs + ) + ); + + env.block.time = env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_validation_time_secs + - 3, + ); + assert_eq!( + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(3) + ); + + env.block.time = env.block.time.plus_seconds(3); + try_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 { resharing: false } + ); + assert_eq!( + epoch.deadline.unwrap(), + env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_finalization_time_secs + ) + ); + + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().verification_key_finalization_time_secs - 1); + assert_eq!( + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(1) + ); + + env.block.time = env.block.time.plus_seconds(1); + try_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.deadline.unwrap(), + env.block + .time + .plus_seconds(epoch.time_configuration.in_progress_time_secs) + ); + + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.in_progress_time_secs - 100); + assert_eq!( + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(100) + ); + + env.block.time = env.block.time.plus_seconds(50); + assert_eq!( + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(50) + ); + + // Group hasn't changed, so we remain in the same epoch, with updated finish timestamp + env.block.time = env.block.time.plus_seconds(100); + let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let expected_epoch = Epoch::new( + EpochState::InProgress, + prev_epoch.epoch_id, + prev_epoch.time_configuration, + env.block.time, + ); + assert_eq!(curr_epoch, expected_epoch); + + // Group changed slightly, so re-run dkg in reshare mode + *GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = ( + Member { + addr: "owner5".to_string(), + weight: 10, + }, + 1, + ); + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.in_progress_time_secs); + let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let expected_epoch = Epoch::new( + EpochState::PublicKeySubmission { resharing: true }, + prev_epoch.epoch_id + 1, + prev_epoch.time_configuration, + env.block.time, + ); + assert_eq!(curr_epoch, expected_epoch); + assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); + let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap(); + let expected_replacement_data = InitialReplacementData { + initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(), + initial_height: 12345, + }; + assert_eq!(replacement_data, expected_replacement_data); + + let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2)); + for details in all_details.iter() { + past_dealers() + .remove(deps.as_mut().storage, &details.address) + .unwrap(); + current_dealers() + .save(deps.as_mut().storage, &details.address, details) + .unwrap(); + } + for times in [ + epoch.time_configuration.public_key_submission_time_secs, + epoch.time_configuration.dealing_exchange_time_secs, + epoch + .time_configuration + .verification_key_submission_time_secs, + epoch + .time_configuration + .verification_key_validation_time_secs, + epoch + .time_configuration + .verification_key_finalization_time_secs, + ] { + env.block.time = env.block.time.plus_seconds(times); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + } + + let all_shares: [_; 4] = std::array::from_fn(|i| { + let mut share = vk_share_fixture(&format!("owner{}", i + 1), 1); + share.verified = i % 2 == 0; + share + }); + for share in all_shares.iter() { + vk_shares() + .save(deps.as_mut().storage, (&share.owner, 0), share) + .unwrap(); + } + + // Group changed even more, surpassing threshold, so re-run dkg in reset mode + *GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = ( + Member { + addr: "owner6".to_string(), + weight: 10, + }, + 1, + ); + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.in_progress_time_secs); + let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let expected_epoch = Epoch::new( + EpochState::PublicKeySubmission { resharing: true }, + prev_epoch.epoch_id + 1, + prev_epoch.time_configuration, + env.block.time, + ); + assert_eq!(curr_epoch, expected_epoch); + assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); + } +} diff --git a/contracts/coconut-dkg/src/epoch_state/transactions.rs b/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs similarity index 51% rename from contracts/coconut-dkg/src/epoch_state/transactions.rs rename to contracts/coconut-dkg/src/epoch_state/transactions/mod.rs index 6ea62b8251..69c93fa7ac 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs @@ -2,13 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage::{current_dealers, past_dealers}; -use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD}; +use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::storage::{DKG_ADMIN, STATE}; use crate::verification_key_shares::storage::verified_dealers; use cosmwasm_std::{Addr, Deps, DepsMut, Env, MessageInfo, Order, Response, Storage}; -use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData}; +use nym_coconut_dkg_common::types::{Epoch, EpochState}; + +pub use advance_epoch_state::try_advance_epoch_state; + +pub mod advance_epoch_state; fn reset_dkg_state(storage: &mut dyn Storage) -> Result<(), ContractError> { THRESHOLD.remove(storage); @@ -91,98 +95,6 @@ pub(crate) fn try_initiate_dkg( Ok(Response::default()) } -pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result { - let current_epoch = CURRENT_EPOCH.load(deps.storage)?; - if current_epoch.state == EpochState::WaitingInitialisation { - return Err(ContractError::WaitingInitialisation); - } - - if let Some(finish_timestamp) = current_epoch.finish_timestamp { - if finish_timestamp > env.block.time { - return Err(ContractError::EarlyEpochStateAdvancement( - finish_timestamp - .minus_seconds(env.block.time.seconds()) - .seconds(), - )); - } - } - - let next_epoch = if let Some(state) = current_epoch.state.next() { - // We are during DKG process - let mut new_state = state; - if let EpochState::DealingExchange { .. } = state { - let current_dealers = current_dealers() - .keys(deps.storage, None, None, Order::Ascending) - .collect::, _>>()?; - let group_members = - STATE - .load(deps.storage)? - .group_addr - .list_members(&deps.querier, None, None)?; - if current_dealers.len() < group_members.len() { - // If not all group members registered yet, we just stay in the same state until - // they either register or they get kicked out of the group - new_state = current_epoch.state; - } else { - // note: ceiling in integer division can be achieved via q = (x + y - 1) / y; - let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3; - THRESHOLD.save(deps.storage, &threshold)?; - } - }; - Epoch::new( - new_state, - current_epoch.epoch_id, - current_epoch.time_configuration, - env.block.time, - ) - } else if dealers_eq_members(&deps)? { - // The dealer set hasn't changed, so we only extend the finish timestamp - // The epoch remains the same, as we use it as key for storing VKs - - // TODO: change that behaviour in the following PR - Epoch::new( - current_epoch.state, - current_epoch.epoch_id, - current_epoch.time_configuration, - env.block.time, - ) - } else { - // Dealer set changed, we need to redo DKG... - let state = if replacement_threshold_surpassed(&deps)? { - // ... in reset mode - INITIAL_REPLACEMENT_DATA.remove(deps.storage); - EpochState::PublicKeySubmission { resharing: false } - } else { - // ... in reshare mode - if INITIAL_REPLACEMENT_DATA.may_load(deps.storage)?.is_some() { - INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| { - // TODO: FIXME: for second reshare the added set of dealers won't be allowed to participate - data.initial_height = env.block.height; - Ok(data) - })?; - } else { - let replacement_data = InitialReplacementData { - initial_dealers: verified_dealers(deps.storage)?, - initial_height: env.block.height, - }; - INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?; - } - - EpochState::PublicKeySubmission { resharing: true } - }; - reset_dkg_state(deps.storage)?; - Epoch::new( - state, - current_epoch.epoch_id + 1, - current_epoch.time_configuration, - env.block.time, - ) - }; - CURRENT_EPOCH.save(deps.storage, &next_epoch)?; - - Ok(Response::default()) -} - pub(crate) fn try_surpassed_threshold( deps: DepsMut<'_>, env: Env, @@ -209,7 +121,8 @@ pub(crate) fn try_surpassed_threshold( #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::error::ContractError::EarlyEpochStateAdvancement; + use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA; + use crate::epoch_state::transactions::advance_epoch_state::try_advance_epoch_state; use crate::support::tests::fixtures::{dealer_details_fixture, vk_share_fixture}; use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS, GROUP_MEMBERS}; use crate::verification_key_shares::storage::vk_shares; @@ -217,7 +130,9 @@ pub(crate) mod tests { use cosmwasm_std::Addr; use cw4::Member; use cw_controllers::AdminError; - use nym_coconut_dkg_common::types::{DealerDetails, EpochState, TimeConfiguration}; + use nym_coconut_dkg_common::types::{ + DealerDetails, EpochState, InitialReplacementData, TimeConfiguration, + }; use rusty_fork::rusty_fork_test; // Because of the global variable handling group, we need individual process for each test @@ -378,335 +293,7 @@ pub(crate) mod tests { } } - #[test] - fn advance_state() { - let mut deps = init_contract(); - let mut env = mock_env(); - { - let mut group = GROUP_MEMBERS.lock().unwrap(); - - group.push(( - Member { - addr: "owner1".to_string(), - weight: 10, - }, - 1, - )); - group.push(( - Member { - addr: "owner2".to_string(), - weight: 10, - }, - 1, - )); - group.push(( - Member { - addr: "owner3".to_string(), - weight: 10, - }, - 1, - )); - group.push(( - Member { - addr: "owner4".to_string(), - weight: 10, - }, - 1, - )); - } - - // can't advance the state if dkg hasn't been initiated - assert_eq!( - advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), - ContractError::WaitingInitialisation - ); - - try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); - assert_eq!( - epoch.state, - EpochState::PublicKeySubmission { resharing: false } - ); - assert_eq!( - epoch.finish_timestamp.unwrap(), - env.block - .time - .plus_seconds(epoch.time_configuration.public_key_submission_time_secs) - ); - - env.block.time = env - .block - .time - .plus_seconds(epoch.time_configuration.public_key_submission_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::PublicKeySubmission { resharing: false } - ); - - // setup dealer details - let all_shares: [_; 4] = - std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0)); - for share in all_shares.iter() { - vk_shares() - .save(deps.as_mut().storage, (&share.owner, 0), share) - .unwrap(); - } - let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1)); - for details in all_details.iter() { - current_dealers() - .save(deps.as_mut().storage, &details.address, details) - .unwrap(); - } - - assert!(INITIAL_REPLACEMENT_DATA - .may_load(&deps.storage) - .unwrap() - .is_none()); - env.block.time = env - .block - .time - .plus_seconds(epoch.time_configuration.public_key_submission_time_secs); - 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 { resharing: false } - ); - assert_eq!( - epoch.finish_timestamp.unwrap(), - env.block - .time - .plus_seconds(epoch.time_configuration.dealing_exchange_time_secs) - ); - - env.block.time = env - .block - .time - .plus_seconds(epoch.time_configuration.dealing_exchange_time_secs - 2); - assert_eq!( - advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), - EarlyEpochStateAdvancement(2) - ); - - 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 { resharing: false } - ); - assert_eq!( - epoch.finish_timestamp.unwrap(), - env.block.time.plus_seconds( - epoch - .time_configuration - .verification_key_submission_time_secs - ) - ); - - env.block.time = env.block.time.plus_seconds( - epoch - .time_configuration - .verification_key_submission_time_secs - - 2, - ); - assert_eq!( - advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), - EarlyEpochStateAdvancement(2) - ); - - 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 { resharing: false } - ); - assert_eq!( - epoch.finish_timestamp.unwrap(), - env.block.time.plus_seconds( - epoch - .time_configuration - .verification_key_validation_time_secs - ) - ); - - env.block.time = env.block.time.plus_seconds( - epoch - .time_configuration - .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 { resharing: false } - ); - assert_eq!( - epoch.finish_timestamp.unwrap(), - env.block.time.plus_seconds( - epoch - .time_configuration - .verification_key_finalization_time_secs - ) - ); - - env.block.time = env - .block - .time - .plus_seconds(TimeConfiguration::default().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.unwrap(), - env.block - .time - .plus_seconds(epoch.time_configuration.in_progress_time_secs) - ); - - env.block.time = env - .block - .time - .plus_seconds(epoch.time_configuration.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) - ); - - // Group hasn't changed, so we remain in the same epoch, with updated finish timestamp - env.block.time = env.block.time.plus_seconds(100); - let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); - let expected_epoch = Epoch::new( - EpochState::InProgress, - prev_epoch.epoch_id, - prev_epoch.time_configuration, - env.block.time, - ); - assert_eq!(curr_epoch, expected_epoch); - - // Group changed slightly, so re-run dkg in reshare mode - *GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = ( - Member { - addr: "owner5".to_string(), - weight: 10, - }, - 1, - ); - env.block.time = env - .block - .time - .plus_seconds(epoch.time_configuration.in_progress_time_secs); - let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); - let expected_epoch = Epoch::new( - EpochState::PublicKeySubmission { resharing: true }, - prev_epoch.epoch_id + 1, - prev_epoch.time_configuration, - env.block.time, - ); - assert_eq!(curr_epoch, expected_epoch); - assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); - let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap(); - let expected_replacement_data = InitialReplacementData { - initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(), - initial_height: 12345, - }; - assert_eq!(replacement_data, expected_replacement_data); - - let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2)); - for details in all_details.iter() { - past_dealers() - .remove(deps.as_mut().storage, &details.address) - .unwrap(); - current_dealers() - .save(deps.as_mut().storage, &details.address, details) - .unwrap(); - } - for times in [ - epoch.time_configuration.public_key_submission_time_secs, - epoch.time_configuration.dealing_exchange_time_secs, - epoch - .time_configuration - .verification_key_submission_time_secs, - epoch - .time_configuration - .verification_key_validation_time_secs, - epoch - .time_configuration - .verification_key_finalization_time_secs, - ] { - env.block.time = env.block.time.plus_seconds(times); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - } - - let all_shares: [_; 4] = std::array::from_fn(|i| { - let mut share = vk_share_fixture(&format!("owner{}", i + 1), 1); - share.verified = i % 2 == 0; - share - }); - for share in all_shares.iter() { - vk_shares() - .save(deps.as_mut().storage, (&share.owner, 0), share) - .unwrap(); - } - - // Group changed even more, surpassing threshold, so re-run dkg in reset mode - *GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = ( - Member { - addr: "owner6".to_string(), - weight: 10, - }, - 1, - ); - env.block.time = env - .block - .time - .plus_seconds(epoch.time_configuration.in_progress_time_secs); - let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); - let expected_epoch = Epoch::new( - EpochState::PublicKeySubmission { resharing: true }, - prev_epoch.epoch_id + 1, - prev_epoch.time_configuration, - env.block.time, - ); - assert_eq!(curr_epoch, expected_epoch); - assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); - } #[test] fn surpass_threshold() { @@ -779,7 +366,7 @@ pub(crate) mod tests { time_configuration.verification_key_finalization_time_secs, ] { env.block.time = env.block.time.plus_seconds(times); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); } let curr_epoch = CURRENT_EPOCH.load(&deps.storage).unwrap(); assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2); @@ -829,7 +416,7 @@ pub(crate) mod tests { let env = mock_env(); let initial_epoch_info = CURRENT_EPOCH.load(&deps.storage).unwrap(); - assert!(initial_epoch_info.finish_timestamp.is_none()); + assert!(initial_epoch_info.deadline.is_none()); // can only be executed by the admin let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info("not an admin", &[])) @@ -856,7 +443,7 @@ pub(crate) mod tests { initial_epoch_info.time_configuration ); assert_eq!( - epoch.finish_timestamp.unwrap(), + epoch.deadline.unwrap(), env.block .time .plus_seconds(epoch.time_configuration.public_key_submission_time_secs) @@ -921,7 +508,7 @@ pub(crate) mod tests { .block .time .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); - advance_epoch_state(deps.as_mut(), env).unwrap(); + try_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/support/tests/helpers.rs b/contracts/coconut-dkg/src/support/tests/helpers.rs index b6b241dd4a..5117c76841 100644 --- a/contracts/coconut-dkg/src/support/tests/helpers.rs +++ b/contracts/coconut-dkg/src/support/tests/helpers.rs @@ -20,6 +20,7 @@ pub const ADMIN_ADDRESS: &str = "admin address"; pub const GROUP_CONTRACT: &str = "group contract address"; pub const MULTISIG_CONTRACT: &str = "multisig contract address"; +// wtf, why is this a thing? pub(crate) static GROUP_MEMBERS: Mutex> = Mutex::new(Vec::new()); pub fn add_fixture_dealer(deps: DepsMut<'_>) { diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index 8ff202ec04..583d8c5533 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -94,7 +94,7 @@ pub fn try_verify_verification_key_share( #[cfg(test)] mod tests { use super::*; - use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg}; + use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg}; use crate::support::tests::helpers; use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, MULTISIG_CONTRACT}; use cosmwasm_std::testing::{mock_env, mock_info}; @@ -117,12 +117,12 @@ mod tests { .block .time .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); env.block.time = env .block .time .plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let dealer = Addr::unchecked("requester"); let announce_address = String::from("localhost"); let dealer_details = DealerDetails { @@ -182,12 +182,12 @@ mod tests { .block .time .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); env.block.time = env .block .time .plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let ret = try_commit_verification_key_share( deps.as_mut(), env.clone(), @@ -256,22 +256,22 @@ mod tests { .block .time .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); env.block.time = env .block .time .plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); env.block.time = env .block .time .plus_seconds(TimeConfiguration::default().verification_key_submission_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); env.block.time = env .block .time .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); - advance_epoch_state(deps.as_mut(), env).unwrap(); + try_advance_epoch_state(deps.as_mut(), env).unwrap(); let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone(), false) .unwrap_err(); @@ -304,12 +304,12 @@ mod tests { .block .time .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); env.block.time = env .block .time .plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let dealer_details = DealerDetails { address: Addr::unchecked(&owner), @@ -331,12 +331,12 @@ mod tests { .block .time .plus_seconds(TimeConfiguration::default().verification_key_submission_time_secs); - advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); env.block.time = env .block .time .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); - advance_epoch_state(deps.as_mut(), env).unwrap(); + try_advance_epoch_state(deps.as_mut(), env).unwrap(); try_verify_verification_key_share(deps.as_mut(), multisig_info, owner, false).unwrap(); } diff --git a/contracts/coconut-test/src/redoing_upon_threshold.rs b/contracts/coconut-test/src/redoing_upon_threshold.rs deleted file mode 100644 index 8e196453fe..0000000000 --- a/contracts/coconut-test/src/redoing_upon_threshold.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::test_wrapper::TestSetup; -use nym_coconut_dkg_common::types::EpochState; - -#[test] -fn adding_enough_members_causes_dkg_redoing() { - let mut test = TestSetup::new(); - - let member1 = test.add_mock_group_member(None); - let member2 = test.add_mock_group_member(None); - - test.begin_dkg(); - test.full_dummy_dkg(vec![member1, member2], false); - - let member3 = test.add_mock_group_member(None); - let member4 = test.add_mock_group_member(None); - let member5 = test.add_mock_group_member(None); - - test.skip_to_dkg_state_end(); - test.unchecked_advance_dkg_epoch(); - - let epoch = test.epoch(); - assert_eq!( - epoch.state, - EpochState::PublicKeySubmission { resharing: false } - ); -} diff --git a/contracts/coconut-test/src/reset_upon_threshold.rs b/contracts/coconut-test/src/reset_upon_threshold.rs new file mode 100644 index 0000000000..a36597f666 --- /dev/null +++ b/contracts/coconut-test/src/reset_upon_threshold.rs @@ -0,0 +1,65 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_wrapper::TestSetup; +use nym_coconut_dkg_common::types::EpochState; + +#[test] +fn removing_enough_members_causes_dkg_reset() { + let mut test = TestSetup::new(); + + let member1 = test.add_mock_group_member(None); + let member2 = test.add_mock_group_member(None); + let member3 = test.add_mock_group_member(None); + let member4 = test.add_mock_group_member(None); + let member5 = test.add_mock_group_member(None); + + test.begin_dkg(); + test.full_dummy_dkg( + vec![ + member1, + member2, + member3.clone(), + member4.clone(), + member5.clone(), + ], + false, + ); + + test.remove_group_member(&member3); + test.remove_group_member(&member4); + test.remove_group_member(&member5); + + test.skip_to_dkg_state_end(); + test.unchecked_advance_dkg_epoch(); + + let epoch = test.epoch(); + assert_eq!( + epoch.state, + EpochState::PublicKeySubmission { resharing: false } + ); +} + +#[test] +fn adding_enough_members_causes_dkg_reset() { + let mut test = TestSetup::new(); + + let member1 = test.add_mock_group_member(None); + let member2 = test.add_mock_group_member(None); + + test.begin_dkg(); + test.full_dummy_dkg(vec![member1, member2], false); + + let _member3 = test.add_mock_group_member(None); + let _member4 = test.add_mock_group_member(None); + let _member5 = test.add_mock_group_member(None); + + test.skip_to_dkg_state_end(); + test.unchecked_advance_dkg_epoch(); + + let epoch = test.epoch(); + assert_eq!( + epoch.state, + EpochState::PublicKeySubmission { resharing: false } + ); +} diff --git a/contracts/coconut-test/src/test_wrapper.rs b/contracts/coconut-test/src/test_wrapper.rs index f69dd0737c..975fdb531c 100644 --- a/contracts/coconut-test/src/test_wrapper.rs +++ b/contracts/coconut-test/src/test_wrapper.rs @@ -207,6 +207,20 @@ impl TestSetup { self.global_admin.clone() } + pub fn remove_group_member(&mut self, addr: &Addr) { + self.app + .execute_contract( + self.admin(), + self.group_contract.addr(), + &nym_group_contract_common::msg::ExecuteMsg::UpdateMembers { + remove: vec![addr.to_string()], + add: vec![], + }, + &[], + ) + .unwrap(); + } + pub fn add_mock_group_member(&mut self, weight: Option) -> Addr { let member_addr = self.random_address(); let weight = weight.unwrap_or(10); @@ -245,7 +259,7 @@ impl TestSetup { self.app.update_block(|block| { block.height += 42; - if let Some(finish_timestamp) = epoch.finish_timestamp { + if let Some(finish_timestamp) = epoch.deadline { block.time = finish_timestamp.plus_seconds(BLOCK_TIME_SECS); } else { block.time = block.time.plus_seconds(BLOCK_TIME_SECS) diff --git a/contracts/coconut-test/src/tests.rs b/contracts/coconut-test/src/tests.rs index 3ad7c1bd64..4c1310dbb8 100644 --- a/contracts/coconut-test/src/tests.rs +++ b/contracts/coconut-test/src/tests.rs @@ -3,7 +3,7 @@ mod deposit_and_release; mod helpers; -mod redoing_upon_threshold; +mod reset_upon_threshold; mod spend_credential_creates_proposal; mod submit_vk_creates_proposal; mod test_wrapper; diff --git a/nym-api/src/coconut/comm.rs b/nym-api/src/coconut/comm.rs index 6f8a82a462..0cb84fc894 100644 --- a/nym-api/src/coconut/comm.rs +++ b/nym-api/src/coconut/comm.rs @@ -43,7 +43,7 @@ impl CachedEpoch { async fn update(&mut self, epoch: Epoch) -> Result<()> { let now = OffsetDateTime::now_utc(); - let validity_duration = if let Some(epoch_finish) = epoch.finish_timestamp { + let validity_duration = if let Some(epoch_finish) = epoch.deadline { let state_end = OffsetDateTime::from_unix_timestamp(epoch_finish.seconds() as i64).unwrap(); let until_epoch_state_end = state_end - now; diff --git a/nym-api/src/coconut/dkg/controller/mod.rs b/nym-api/src/coconut/dkg/controller/mod.rs index fec729e466..8393b52770 100644 --- a/nym-api/src/coconut/dkg/controller/mod.rs +++ b/nym-api/src/coconut/dkg/controller/mod.rs @@ -236,7 +236,7 @@ impl DkgController { // add a bit of variance so that all apis wouldn't attempt to trigger it at the same time let variance = self.rng.gen_range(0..=60); - if let Some(epoch_finish) = epoch.finish_timestamp { + if let Some(epoch_finish) = epoch.deadline { let now = OffsetDateTime::now_utc(); if now.unix_timestamp() > epoch_finish.seconds() as i64 + variance { // TODO: make sure to not overload validator in case its running slow