moved epoch advancement logic into separate file

This commit is contained in:
Jędrzej Stuczyński
2024-02-14 14:31:46 +00:00
parent 629081b5ec
commit 46875cdf2f
16 changed files with 575 additions and 486 deletions
@@ -93,7 +93,9 @@ pub struct Epoch {
pub state: EpochState,
pub epoch_id: EpochId,
pub time_configuration: TimeConfiguration,
pub finish_timestamp: Option<Timestamp>,
#[serde(alias = "finish_timestamp")]
pub deadline: Option<Timestamp>,
}
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<u64> {
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() {
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -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),
}
}
@@ -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(),
@@ -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(),
@@ -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)
@@ -0,0 +1,449 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// 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<Response, ContractError> {
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::<Result<Vec<Addr>, _>>()?;
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());
}
}
@@ -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<Response, ContractError> {
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::<Result<Vec<Addr>, _>>()?;
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
@@ -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<Vec<(Member, u64)>> = Mutex::new(Vec::new());
pub fn add_fixture_dealer(deps: DepsMut<'_>) {
@@ -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();
}
@@ -1,29 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// 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 }
);
}
@@ -0,0 +1,65 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// 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 }
);
}
+15 -1
View File
@@ -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<u64>) -> 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)
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -236,7 +236,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
// 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