revamped dealers storage structure (for txs)

This commit is contained in:
Jędrzej Stuczyński
2024-02-15 11:47:17 +00:00
parent 29edc8799a
commit 27554f52e3
17 changed files with 201 additions and 462 deletions
@@ -17,9 +17,7 @@ pub use nym_coconut_dkg_common::{
DealingMetadataResponse, DealingStatusResponse,
},
msg::QueryMsg as DkgQueryMsg,
types::{
DealerDetails, DealingIndex, Epoch, EpochId, EpochState, InitialReplacementData, State,
},
types::{DealerDetails, DealingIndex, Epoch, EpochId, EpochState, State},
verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse},
};
@@ -16,7 +16,7 @@ use crate::{
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
DealingMetadataResponse, DealingStatusResponse,
},
types::{Epoch, InitialReplacementData, State},
types::{Epoch, State},
verification_key::{PagedVKSharesResponse, VkShareResponse},
};
#[cfg(feature = "schema")]
@@ -53,7 +53,6 @@ pub enum ExecuteMsg {
CommitDealingsChunk {
chunk: PartialContractDealing,
resharing: bool,
},
CommitVerificationKeyShare {
@@ -21,12 +21,6 @@ pub type DealingIndex = u32;
pub type ChunkIndex = u16;
pub type PartialContractDealingData = ContractSafeBytes;
#[cw_serde]
pub struct InitialReplacementData {
pub initial_dealers: Vec<Addr>,
pub initial_height: u64,
}
#[cw_serde]
#[derive(Copy)]
pub struct TimeConfiguration {
+6 -9
View File
@@ -10,13 +10,10 @@ use crate::dealings::queries::{
query_dealing_metadata, query_dealing_status,
};
use crate::dealings::transactions::{try_commit_dealings_chunk, try_submit_dealings_metadata};
use crate::epoch_state::queries::{
query_current_epoch, query_current_epoch_threshold, query_initial_dealers,
};
use crate::epoch_state::queries::{query_current_epoch, query_current_epoch_threshold};
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::{
try_advance_epoch_state, try_initiate_dkg, try_surpassed_threshold, try_trigger_reset,
try_trigger_resharing,
try_advance_epoch_state, try_initiate_dkg, try_trigger_reset, try_trigger_resharing,
};
use crate::error::ContractError;
use crate::state::queries::query_state;
@@ -109,8 +106,8 @@ pub fn execute(
chunks,
resharing,
} => try_submit_dealings_metadata(deps, info, dealing_index, chunks, resharing),
ExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
try_commit_dealings_chunk(deps, env, info, chunk, resharing)
ExecuteMsg::CommitDealingsChunk { chunk } => {
try_commit_dealings_chunk(deps, env, info, chunk)
}
ExecuteMsg::CommitVerificationKeyShare { share, resharing } => {
try_commit_verification_key_share(deps, env, info, share, resharing)
@@ -118,7 +115,7 @@ pub fn execute(
ExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => {
try_verify_verification_key_share(deps, info, owner, resharing)
}
ExecuteMsg::SurpassedThreshold {} => try_surpassed_threshold(deps, env),
ExecuteMsg::SurpassedThreshold {} => todo!(),
ExecuteMsg::AdvanceEpochState {} => try_advance_epoch_state(deps, env),
ExecuteMsg::TriggerReset {} => try_trigger_reset(deps, env, info),
ExecuteMsg::TriggerResharing {} => try_trigger_resharing(deps, env, info),
@@ -133,7 +130,7 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
QueryMsg::GetCurrentEpochThreshold {} => {
to_binary(&query_current_epoch_threshold(deps.storage)?)?
}
QueryMsg::GetInitialDealers {} => to_binary(&query_initial_dealers(deps.storage)?)?,
QueryMsg::GetInitialDealers {} => todo!(),
QueryMsg::GetDealerDetails { dealer_address } => {
to_binary(&query_dealer_details(deps, dealer_address)?)?
}
+43 -39
View File
@@ -1,53 +1,55 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage::{self, IndexedDealersMap};
use crate::dealers::storage::{self};
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use nym_coconut_dkg_common::dealer::{DealerDetailsResponse, DealerType, PagedDealerResponse};
fn query_dealers(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
underlying_map: &IndexedDealersMap<'_>,
) -> StdResult<PagedDealerResponse> {
let limit = limit
.unwrap_or(storage::DEALERS_PAGE_DEFAULT_LIMIT)
.min(storage::DEALERS_PAGE_MAX_LIMIT) as usize;
let addr = start_after
.map(|addr| deps.api.addr_validate(&addr))
.transpose()?;
let start = addr.as_ref().map(Bound::exclusive);
let dealers = underlying_map
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = dealers.last().map(|dealer| dealer.address.clone());
Ok(PagedDealerResponse::new(dealers, limit, start_next_after))
}
// fn query_dealers(
// deps: Deps<'_>,
// start_after: Option<String>,
// limit: Option<u32>,
// underlying_map: &IndexedDealersMap<'_>,
// ) -> StdResult<PagedDealerResponse> {
// let limit = limit
// .unwrap_or(storage::DEALERS_PAGE_DEFAULT_LIMIT)
// .min(storage::DEALERS_PAGE_MAX_LIMIT) as usize;
//
// let addr = start_after
// .map(|addr| deps.api.addr_validate(&addr))
// .transpose()?;
//
// let start = addr.as_ref().map(Bound::exclusive);
//
// let dealers = underlying_map
// .range(deps.storage, start, None, Order::Ascending)
// .take(limit)
// .map(|res| res.map(|item| item.1))
// .collect::<StdResult<Vec<_>>>()?;
//
// let start_next_after = dealers.last().map(|dealer| dealer.address.clone());
//
// Ok(PagedDealerResponse::new(dealers, limit, start_next_after))
// }
pub fn query_dealer_details(
deps: Deps<'_>,
dealer_address: String,
) -> StdResult<DealerDetailsResponse> {
let addr = deps.api.addr_validate(&dealer_address)?;
if let Some(current) = storage::current_dealers().may_load(deps.storage, &addr)? {
return Ok(DealerDetailsResponse::new(
Some(current),
DealerType::Current,
));
}
if let Some(past) = storage::past_dealers().may_load(deps.storage, &addr)? {
return Ok(DealerDetailsResponse::new(Some(past), DealerType::Past));
}
Ok(DealerDetailsResponse::new(None, DealerType::Unknown))
todo!()
// if let Some(current) = storage::current_dealers().may_load(deps.storage, &addr)? {
// return Ok(DealerDetailsResponse::new(
// Some(current),
// DealerType::Current,
// ));
// }
// if let Some(past) = storage::past_dealers().may_load(deps.storage, &addr)? {
// return Ok(DealerDetailsResponse::new(Some(past), DealerType::Past));
// }
// Ok(DealerDetailsResponse::new(None, DealerType::Unknown))
}
pub fn query_current_dealers_paged(
@@ -55,7 +57,8 @@ pub fn query_current_dealers_paged(
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerResponse> {
query_dealers(deps, start_after, limit, &storage::current_dealers())
todo!()
// query_dealers(deps, start_after, limit, &storage::current_dealers())
}
pub fn query_past_dealers_paged(
@@ -63,7 +66,8 @@ pub fn query_past_dealers_paged(
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerResponse> {
query_dealers(deps, start_after, limit, &storage::past_dealers())
todo!()
// query_dealers(deps, start_after, limit, &storage::past_dealers())
}
#[cfg(test)]
+72 -27
View File
@@ -1,50 +1,95 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::ContractError;
use crate::Dealer;
use cosmwasm_std::{Addr, StdResult, Storage};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, UniqueIndex};
use nym_coconut_dkg_common::types::{DealerDetails, DealerRegistrationDetails, EpochId, NodeIndex};
const CURRENT_DEALERS_PK: &str = "crd";
const PAST_DEALERS_PK: &str = "ptd";
const DEALERS_NODE_INDEX_IDX_NAMESPACE: &str = "dni";
pub(crate) const DEALERS_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const DEALERS_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) const DEALERS_PAGE_MAX_LIMIT: u32 = 50;
pub(crate) const DEALERS_PAGE_DEFAULT_LIMIT: u32 = 20;
pub(crate) const NODE_INDEX_COUNTER: Item<NodeIndex> = Item::new("node_index_counter");
// TODO:
pub(crate) const DEALERS_INDICES: Map<Dealer, NodeIndex> = Map::new("dealer_index");
pub(crate) const EPOCH_DEALERS_MAP: Map<(EpochId, Dealer), DealerRegistrationDetails> =
Map::new("epoch_dealers");
pub(crate) type IndexedDealersMap<'a> = IndexedMap<'a, &'a Addr, DealerDetails, DealersIndex<'a>>;
pub(crate) struct DealersIndex<'a> {
pub(crate) node_index: UniqueIndex<'a, NodeIndex, DealerDetails>,
}
impl<'a> IndexList<DealerDetails> for DealersIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<DealerDetails>> + '_> {
let v: Vec<&dyn Index<DealerDetails>> = vec![&self.node_index];
Box::new(v.into_iter())
/// Attempts to retrieve a pre-assign node index associated with given dealer.
/// If one doesn't exist, a new one is assigned.
pub(crate) fn get_or_assign_index(
storage: &mut dyn Storage,
dealer: Dealer,
) -> StdResult<NodeIndex> {
if let Some(index) = DEALERS_INDICES.may_load(storage, dealer)? {
return Ok(index);
}
let index = next_node_index(storage)?;
DEALERS_INDICES.save(storage, dealer, &index)?;
Ok(index)
}
pub(crate) fn current_dealers<'a>() -> IndexedDealersMap<'a> {
let indexes = DealersIndex {
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
};
IndexedMap::new(CURRENT_DEALERS_PK, indexes)
pub(crate) fn save_dealer_details_if_not_a_dealer(
storage: &mut dyn Storage,
dealer: Dealer,
epoch_id: EpochId,
details: DealerRegistrationDetails,
) -> Result<(), ContractError> {
if EPOCH_DEALERS_MAP.has(storage, (epoch_id, dealer)) {
return Err(ContractError::AlreadyADealer);
}
EPOCH_DEALERS_MAP.save(storage, (epoch_id, dealer), &details)?;
Ok(())
}
pub(crate) fn past_dealers<'a>() -> IndexedDealersMap<'a> {
let indexes = DealersIndex {
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
};
IndexedMap::new(PAST_DEALERS_PK, indexes)
pub(crate) fn ensure_dealer(
storage: &dyn Storage,
dealer: Dealer,
epoch_id: EpochId,
) -> Result<(), ContractError> {
if !EPOCH_DEALERS_MAP.has(storage, (epoch_id, dealer)) {
return Err(ContractError::NotADealer { epoch_id });
}
Ok(())
}
// note: `epoch_id` is provided purely for the error message. it has nothing to do with storage retrieval
pub(crate) fn get_dealer_index(
storage: &dyn Storage,
dealer: Dealer,
epoch_id: EpochId,
) -> Result<NodeIndex, ContractError> {
DEALERS_INDICES
.may_load(storage, dealer)?
.ok_or(ContractError::NotADealer { epoch_id })
}
pub(crate) fn get_registration_details(
storage: &dyn Storage,
dealer: Dealer,
epoch_id: EpochId,
) -> Result<DealerRegistrationDetails, ContractError> {
EPOCH_DEALERS_MAP
.may_load(storage, (epoch_id, dealer))?
.ok_or(ContractError::NotADealer { epoch_id })
}
pub(crate) fn get_dealer_details(
storage: &dyn Storage,
dealer: Dealer,
epoch_id: EpochId,
) -> Result<DealerDetails, ContractError> {
let registration_details = get_registration_details(storage, dealer, epoch_id)?;
let assigned_index = get_dealer_index(storage, dealer, epoch_id)?;
Ok(DealerDetails {
address: dealer.to_owned(),
bte_public_key_with_proof: registration_details.bte_public_key_with_proof,
ed25519_identity: registration_details.ed25519_identity,
announce_address: registration_details.announce_address,
assigned_index,
})
}
pub(crate) fn next_node_index(store: &mut dyn Storage) -> StdResult<NodeIndex> {
@@ -1,34 +1,22 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage as dealers_storage;
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA};
use crate::dealers::storage::{get_or_assign_index, save_dealer_details_if_not_a_dealer};
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::storage::STATE;
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response, StdResult};
use nym_coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState};
use crate::Dealer;
use cosmwasm_std::{Deps, DepsMut, MessageInfo, Response, StdResult};
use nym_coconut_dkg_common::dealer::DealerRegistrationDetails;
use nym_coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState};
// currently we only require that
// a) it's part of the signer group
// b) it isn't already a dealer
fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<(), ContractError> {
if dealers_storage::current_dealers()
.may_load(deps.storage, dealer)?
.is_some()
{
return Err(ContractError::AlreadyADealer);
}
fn ensure_group_member(deps: Deps, dealer: Dealer) -> Result<(), ContractError> {
let state = STATE.load(deps.storage)?;
let height = if resharing {
Some(INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height)
} else {
None
};
state
.group_addr
.is_voting_member(&deps.querier, dealer, height)?
.is_voting_member(&deps.querier, dealer, None)?
.ok_or(ContractError::Unauthorized {})?;
Ok(())
@@ -37,42 +25,35 @@ fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<()
// future optimisation:
// for a recurring dealer just let it refresh the keys without having to do all the storage operations
pub fn try_add_dealer(
mut deps: DepsMut<'_>,
deps: DepsMut<'_>,
info: MessageInfo,
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
identity_key: String,
announce_address: String,
resharing: bool,
) -> Result<Response, ContractError> {
let epoch = CURRENT_EPOCH.load(deps.storage)?;
check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?;
verify_dealer(deps.branch(), &info.sender, resharing)?;
// make sure this potential dealer actually belong to the group
ensure_group_member(deps.as_ref(), &info.sender)?;
// if it was already a dealer in the past, assign the same node index
let node_index = if let Some(prior_details) =
dealers_storage::past_dealers().may_load(deps.storage, &info.sender)?
{
// since this dealer is going to become active now, remove it from the past dealers
dealers_storage::past_dealers().replace(
deps.storage,
&info.sender,
None,
Some(&prior_details),
)?;
prior_details.assigned_index
} else {
dealers_storage::next_node_index(deps.storage)?
};
let node_index = get_or_assign_index(deps.storage, &info.sender)?;
// save the dealer into the storage
let dealer_details = DealerDetails {
address: info.sender.clone(),
// save the dealer into the storage (if it hasn't already been saved)
let dealer_details = DealerRegistrationDetails {
bte_public_key_with_proof: bte_key_with_proof,
ed25519_identity: identity_key,
announce_address,
assigned_index: node_index,
};
dealers_storage::current_dealers().save(deps.storage, &info.sender, &dealer_details)?;
save_dealer_details_if_not_a_dealer(
deps.storage,
&info.sender,
epoch.epoch_id,
dealer_details,
)?;
// increment the number of registered dealers
CURRENT_EPOCH.update(deps.storage, |epoch| -> StdResult<_> {
let mut updated_epoch = epoch;
updated_epoch.state_progress.registered_dealers += 1;
@@ -3,7 +3,7 @@
use crate::error::ContractError;
use crate::Dealer;
use cosmwasm_std::{Addr, Storage};
use cosmwasm_std::Storage;
use cw_storage_plus::{Key, Map, Path, PrimaryKey};
use nym_coconut_dkg_common::dealing::{DealingMetadata, PartialContractDealing};
use nym_coconut_dkg_common::types::{
@@ -1,11 +1,11 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage as dealers_storage;
use crate::dealers::storage::ensure_dealer;
use crate::dealings::storage::{
metadata_exists, must_read_metadata, store_metadata, StoredDealing,
};
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA};
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::storage::STATE;
@@ -13,31 +13,25 @@ use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, Storage};
use nym_coconut_dkg_common::dealing::{
DealingChunkInfo, DealingMetadata, PartialContractDealing, MAX_DEALING_CHUNKS,
};
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochState};
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId, EpochState};
// make sure the epoch is in the dealing exchange and the message sender is a valid dealer for this epoch
fn ensure_permission(
storage: &dyn Storage,
sender: &Addr,
current_epoch_id: EpochId,
resharing: bool,
) -> Result<(), ContractError> {
check_epoch_state(storage, EpochState::DealingExchange { resharing })?;
// ensure the sender is a dealer
if dealers_storage::current_dealers()
.may_load(storage, sender)?
.is_none()
{
return Err(ContractError::NotADealer);
}
if resharing
&& !INITIAL_REPLACEMENT_DATA
.load(storage)?
.initial_dealers
.contains(sender)
{
return Err(ContractError::NotAnInitialDealer);
// ensure the sender is a dealer for this epoch
ensure_dealer(storage, sender, current_epoch_id)?;
// if we're in resharing, make sure this sender has also been a dealer in the previous epoch
if resharing {
ensure_dealer(storage, sender, current_epoch_id.saturating_sub(1))?;
}
Ok(())
}
@@ -48,10 +42,10 @@ pub fn try_submit_dealings_metadata(
chunks: Vec<DealingChunkInfo>,
resharing: bool,
) -> Result<Response, ContractError> {
ensure_permission(deps.storage, &info.sender, resharing)?;
let state = STATE.load(deps.storage)?;
let epoch = CURRENT_EPOCH.load(deps.storage)?;
let state = STATE.load(deps.storage)?;
ensure_permission(deps.storage, &info.sender, epoch.epoch_id, resharing)?;
// don't allow overwriting existing metadata
if metadata_exists(deps.storage, epoch.epoch_id, &info.sender, dealing_index) {
@@ -139,9 +133,9 @@ pub fn try_commit_dealings_chunk(
env: Env,
info: MessageInfo,
chunk: PartialContractDealing,
resharing: bool,
) -> Result<Response, ContractError> {
ensure_permission(deps.storage, &info.sender, resharing)?;
// note: checking permissions is implicit as if the metadata exists,
// the sender must have been allowed to submit it
let epoch = CURRENT_EPOCH.load(deps.storage)?;
@@ -1,10 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD};
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
use crate::error::ContractError;
use cosmwasm_std::Storage;
use nym_coconut_dkg_common::types::{Epoch, InitialReplacementData};
use nym_coconut_dkg_common::types::Epoch;
pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
CURRENT_EPOCH
@@ -18,12 +18,6 @@ pub(crate) fn query_current_epoch_threshold(
Ok(THRESHOLD.may_load(storage)?)
}
pub(crate) fn query_initial_dealers(
storage: &dyn Storage,
) -> Result<Option<InitialReplacementData>, ContractError> {
Ok(INITIAL_REPLACEMENT_DATA.may_load(storage)?)
}
#[cfg(test)]
pub(crate) mod test {
use super::*;
@@ -2,11 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use cw_storage_plus::Item;
use nym_coconut_dkg_common::types::{Epoch, InitialReplacementData};
use nym_coconut_dkg_common::types::Epoch;
pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch");
pub const THRESHOLD: Item<u64> = Item::new("threshold");
#[deprecated]
pub const INITIAL_REPLACEMENT_DATA: Item<InitialReplacementData> =
Item::new("initial_replacement_data");
@@ -1,17 +1,11 @@
// 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::epoch_state::utils::{check_state_completion, needs_reset, needs_resharing};
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
use crate::epoch_state::utils::check_state_completion;
use crate::error::ContractError;
use crate::state::storage::STATE;
use crate::verification_key_shares::storage::verified_dealers;
use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response};
use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
use cosmwasm_std::{Deps, DepsMut, Env, Response};
use nym_coconut_dkg_common::types::{Epoch, EpochState};
fn ensure_can_advance_state(
deps: Deps<'_>,
@@ -49,7 +43,7 @@ pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response,
// checks whether the given phase has either completed or reached its deadline
ensure_can_advance_state(deps.as_ref(), &env, &current_epoch)?;
let next_state = match current_epoch.state.next() {
let mut next_state = match current_epoch.state.next() {
Some(next_state) => next_state,
None => {
debug_assert!(current_epoch.state.is_in_progress());
@@ -60,89 +54,21 @@ pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response,
}
};
// edge case: we have completed DKG with fewer than threshold number of verified keys.
// we have no choice but to reset since no credentials can be issued anyway
if next_state.is_in_progress() {
let threshold = THRESHOLD.load(deps.storage)?;
if (current_epoch.state_progress.verified_keys as u64) < threshold {
next_state = EpochState::PublicKeySubmission { resharing: false }
};
}
// update the epoch state
let mut next_epoch = current_epoch;
next_epoch.state = next_state;
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
Ok(Response::new())
//
//
// 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)]
@@ -1,14 +1,10 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage::{current_dealers, past_dealers};
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 cw4::Member;
use crate::state::storage::DKG_ADMIN;
use cosmwasm_std::{DepsMut, Env, MessageInfo, Order, Response, Storage};
use nym_coconut_dkg_common::types::{Epoch, EpochState};
pub use advance_epoch_state::try_advance_epoch_state;
@@ -18,66 +14,19 @@ pub mod advance_epoch_state;
fn reset_dkg_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
THRESHOLD.remove(storage);
// TODO: unbounded query. to be removed be preserving dealer details and changing storage structure
let dealers: Vec<_> = current_dealers()
.keys(storage, None, None, Order::Ascending)
.collect::<Result<_, _>>()?;
for dealer_addr in dealers {
let details = current_dealers().load(storage, &dealer_addr)?;
current_dealers().remove(storage, &dealer_addr)?;
past_dealers().save(storage, &dealer_addr, &details)?;
}
// // TODO: unbounded query. to be removed be preserving dealer details and changing storage structure
// let dealers: Vec<_> = current_dealers()
// .keys(storage, None, None, Order::Ascending)
// .collect::<Result<_, _>>()?;
//
// for dealer_addr in dealers {
// let details = current_dealers().load(storage, &dealer_addr)?;
// current_dealers().remove(storage, &dealer_addr)?;
// past_dealers().save(storage, &dealer_addr, &details)?;
// }
Ok(())
}
fn dealers_still_active(
deps: &Deps<'_>,
dealers: impl Iterator<Item = Addr>,
) -> Result<usize, ContractError> {
let state = STATE.load(deps.storage)?;
let mut still_active = 0;
for dealer_addr in dealers {
if state
.group_addr
.is_voting_member(&deps.querier, &dealer_addr, None)?
.is_some()
{
still_active += 1;
}
}
Ok(still_active)
}
#[deprecated]
fn dealers_eq_members(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
let verified_dealers = verified_dealers(deps.storage)?;
let all_dealers = verified_dealers.len();
let dealers_still_active = dealers_still_active(&deps.as_ref(), verified_dealers.into_iter())?;
let group_members = STATE
.load(deps.storage)?
.group_addr
.list_members(&deps.querier, None, None)?
.len();
Ok(dealers_still_active == all_dealers && all_dealers == group_members)
}
fn replacement_threshold_surpassed(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
let threshold = THRESHOLD.load(deps.storage)? as usize;
let initial_dealers = verified_dealers(deps.storage)?;
if initial_dealers.is_empty() {
// possibly failed DKG, just reset and start again
return Ok(true);
}
let initial_dealer_count = initial_dealers.len();
let replacement_threshold = threshold - (initial_dealers.len() + 2 - 1) / 2 + 1;
let removed_dealer_count =
initial_dealer_count - dealers_still_active(&deps.as_ref(), initial_dealers.into_iter())?;
Ok(removed_dealer_count >= replacement_threshold)
}
pub(crate) fn try_initiate_dkg(
deps: DepsMut<'_>,
env: Env,
@@ -99,29 +48,6 @@ pub(crate) fn try_initiate_dkg(
Ok(Response::default())
}
pub(crate) fn try_surpassed_threshold(
deps: DepsMut<'_>,
env: Env,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::InProgress)?;
let threshold = THRESHOLD.load(deps.storage)?;
let dealers = verified_dealers(deps.storage)?;
if dealers_still_active(&deps.as_ref(), dealers.into_iter())? < threshold as usize {
reset_dkg_state(deps.storage)?;
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
Ok(Epoch::new(
EpochState::default(),
epoch.epoch_id + 1,
epoch.time_configuration,
env.block.time,
))
})?;
}
Ok(Response::default())
}
pub(crate) fn try_trigger_reset(
deps: DepsMut<'_>,
env: Env,
+1 -74
View File
@@ -4,31 +4,9 @@
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::error::ContractError;
use crate::state::storage::STATE;
use crate::verification_key_shares::storage::{dealers, verified_dealers};
use cosmwasm_std::{Addr, Deps, StdResult, Storage};
use cw4::Member;
use cosmwasm_std::Storage;
use nym_coconut_dkg_common::types::{Epoch, EpochState};
fn all_group_members(deps: &Deps) -> Result<Vec<Member>, ContractError> {
// the maximum limit for members queries is 30.
// if we're ever thinking of going beyond it, we should fix it properly
// by the DKG contract owning the group contract (i.e. init inside init)
// and proxying all member changes and thus memoizing the members.
// alternatively by adding hooks to the contract to inform us about any changes
let members =
STATE
.load(deps.storage)?
.group_addr
.list_members(&deps.querier, None, Some(30))?;
// if we're at the limit...
if members.len() == 30 {
return Err(ContractError::PossiblyIncompleteGroupMembersQuery);
}
Ok(members)
}
// check if we completed the state, so we could short circuit the deadline
pub(crate) fn check_state_completion(
storage: &dyn Storage,
@@ -65,57 +43,6 @@ pub(crate) fn check_state_completion(
}
}
/// Checks whether the DKG needs to undergo full reset.
/// This is determined by whether the initial set of validator changed by more than a threshold number
/// of parties joining or leaving.
pub(crate) fn needs_reset(deps: Deps) -> Result<bool, ContractError> {
let current_state = CURRENT_EPOCH.load(deps.storage)?.state;
// there is a theoretical edge case where we add/remove an extra member during an in-progress exchange
// thus possibly needing reset immediately after it's done. an optimization would be to just scrap it and start it over
// but since members are added manually, we don't have to worry about it too much for now.
if !current_state.is_in_progress() {
return Err(ContractError::CantResetDuringExchange);
}
let group_members = all_group_members(&deps)?;
// below threshold => must reset since we can't do anything
todo!()
}
/// Checks whether the DKG needs to undergo resharing.
/// This is determined by whether any new group members has joined the associated group contract
pub(crate) fn needs_resharing(deps: Deps) -> Result<bool, ContractError> {
let current_state = CURRENT_EPOCH.load(deps.storage)?.state;
// there is a theoretical edge case where we add an extra member during an in-progress exchange
// thus needing resharing immediately after it's done. an optimization would be to just scrap it and start it over
// but since members are added manually, we don't have to worry about it too much for now.
if !current_state.is_in_progress() {
return Err(ContractError::CantReshareDuringExchange);
}
// TODO: we need cw4 hooks here to resolve those expensive queries
let group_members = all_group_members(&deps)?;
let epoch_dealers = dealers(deps.storage)?;
// if somebody has been a dealer, but hasn't been verified, tough luck
// we only allow for resharing for new members
// check if we have any new members
for member in group_members {
if !epoch_dealers.contains_key(&Addr::unchecked(member.addr)) {
return Ok(true);
}
}
// TODO: something about reset here
Ok(false)
}
pub(crate) fn check_epoch_state(
storage: &dyn Storage,
against: EpochState,
+2 -2
View File
@@ -45,8 +45,8 @@ pub enum ContractError {
expected_state: String,
},
#[error("This sender is not a dealer for the current epoch")]
NotADealer,
#[error("This sender is not a dealer for epoch {epoch_id}")]
NotADealer { epoch_id: EpochId },
#[error("This sender is not a dealer for the current resharing epoch")]
NotAnInitialDealer,
@@ -2,16 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE};
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::error::ContractError;
use cosmwasm_std::{Addr, Order, StdResult, Storage};
use cosmwasm_std::Addr;
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
use nym_coconut_dkg_common::types::EpochId;
use nym_coconut_dkg_common::verification_key::ContractVKShare;
use std::collections::HashMap;
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT: u32 = 30;
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT: u32 = 10;
type VKShareKey<'a> = (&'a Addr, EpochId);
@@ -36,40 +33,3 @@ pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare,
};
IndexedMap::new(VK_SHARES_PK_NAMESPACE, indexes)
}
// TODO: this is a ticking time bomb and will cause us headache when we start running out of gas... again...
// not sure how to fix it yet...
// maybe by completely removing it by relying on cw4 hooks?
#[deprecated]
pub(crate) fn verified_dealers(storage: &dyn Storage) -> Result<Vec<Addr>, ContractError> {
let epoch_id = CURRENT_EPOCH.load(storage)?.epoch_id;
Ok(vk_shares()
.idx
.epoch_id
.prefix(epoch_id)
.range(storage, None, None, Order::Ascending)
.flatten()
.filter_map(|(_, share)| {
if share.verified {
Some(share.owner)
} else {
None
}
})
.collect())
}
// TODO: this is a ticking time bomb and will cause us headache when we start running out of gas... again...
// not sure how to fix it yet...
// maybe by completely removing it by relying on cw4 hooks?
pub(crate) fn dealers(storage: &dyn Storage) -> Result<HashMap<Addr, bool>, ContractError> {
let epoch_id = CURRENT_EPOCH.load(storage)?.epoch_id;
Ok(vk_shares()
.idx
.epoch_id
.prefix(epoch_id)
.range(storage, None, None, Order::Ascending)
.map(|maybe_share| maybe_share.map(|(_, v)| (v.owner, v.verified)))
.collect::<StdResult<_>>()?)
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::constants::BLOCK_TIME_FOR_VERIFICATION_SECS;
use crate::dealers::storage as dealers_storage;
use crate::dealers::storage::get_dealer_details;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
@@ -25,11 +25,9 @@ pub fn try_commit_verification_key_share(
deps.storage,
EpochState::VerificationKeySubmission { resharing },
)?;
// ensure the sender is a dealer
let details = dealers_storage::current_dealers()
.load(deps.storage, &info.sender)
.map_err(|_| ContractError::NotADealer)?;
let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
let details = get_dealer_details(deps.storage, &info.sender, epoch_id)?;
if vk_shares()
.may_load(deps.storage, (&info.sender, epoch_id))?
.is_some()