From 27554f52e350a00a69131a175b77ea7989d53fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 15 Feb 2024 11:47:17 +0000 Subject: [PATCH] revamped dealers storage structure (for txs) --- .../nyxd/contract_traits/dkg_query_client.rs | 4 +- .../coconut-dkg/src/msg.rs | 3 +- .../coconut-dkg/src/types.rs | 6 -- contracts/coconut-dkg/src/contract.rs | 15 ++- contracts/coconut-dkg/src/dealers/queries.rs | 82 +++++++------- contracts/coconut-dkg/src/dealers/storage.rs | 99 ++++++++++++----- .../coconut-dkg/src/dealers/transactions.rs | 65 ++++------- contracts/coconut-dkg/src/dealings/storage.rs | 2 +- .../coconut-dkg/src/dealings/transactions.rs | 38 +++---- .../coconut-dkg/src/epoch_state/queries.rs | 10 +- .../coconut-dkg/src/epoch_state/storage.rs | 6 +- .../transactions/advance_epoch_state.rs | 102 +++--------------- .../src/epoch_state/transactions/mod.rs | 98 +++-------------- .../coconut-dkg/src/epoch_state/utils.rs | 75 +------------ contracts/coconut-dkg/src/error.rs | 4 +- .../src/verification_key_shares/storage.rs | 46 +------- .../verification_key_shares/transactions.rs | 8 +- 17 files changed, 201 insertions(+), 462 deletions(-) diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs index 5a3c019396..80cac432c9 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs @@ -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}, }; diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index cd18a23efb..d8a35d9ef3 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -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 { diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index 38632bf3a3..bd0f1d4e1a 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -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, - pub initial_height: u64, -} - #[cw_serde] #[derive(Copy)] pub struct TimeConfiguration { diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index b01e6828ba..02189255bb 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -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 { 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)?)? } diff --git a/contracts/coconut-dkg/src/dealers/queries.rs b/contracts/coconut-dkg/src/dealers/queries.rs index f7ef4209c1..c7117f926c 100644 --- a/contracts/coconut-dkg/src/dealers/queries.rs +++ b/contracts/coconut-dkg/src/dealers/queries.rs @@ -1,53 +1,55 @@ // Copyright 2022 - Nym Technologies SA // 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, - limit: Option, - underlying_map: &IndexedDealersMap<'_>, -) -> StdResult { - 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::>>()?; - - 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, +// limit: Option, +// underlying_map: &IndexedDealersMap<'_>, +// ) -> StdResult { +// 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::>>()?; +// +// 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 { 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, limit: Option, ) -> StdResult { - 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, limit: Option, ) -> StdResult { - query_dealers(deps, start_after, limit, &storage::past_dealers()) + todo!() + // query_dealers(deps, start_after, limit, &storage::past_dealers()) } #[cfg(test)] diff --git a/contracts/coconut-dkg/src/dealers/storage.rs b/contracts/coconut-dkg/src/dealers/storage.rs index 3204a0c2f6..3f0ecc18ac 100644 --- a/contracts/coconut-dkg/src/dealers/storage.rs +++ b/contracts/coconut-dkg/src/dealers/storage.rs @@ -1,50 +1,95 @@ // Copyright 2022 - Nym Technologies SA // 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 = Item::new("node_index_counter"); -// TODO: pub(crate) const DEALERS_INDICES: Map = 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 for DealersIndex<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = 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 { + 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 { + 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 { + 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 { + 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 { diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index 01507b3dce..fe851df5b6 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -1,34 +1,22 @@ // Copyright 2022 - Nym Technologies SA // 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 { + 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; diff --git a/contracts/coconut-dkg/src/dealings/storage.rs b/contracts/coconut-dkg/src/dealings/storage.rs index 187d84bc6e..bfa92722a4 100644 --- a/contracts/coconut-dkg/src/dealings/storage.rs +++ b/contracts/coconut-dkg/src/dealings/storage.rs @@ -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::{ diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index e64c4d575c..467ee46eba 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -1,11 +1,11 @@ // Copyright 2022-2024 - Nym Technologies SA // 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, resharing: bool, ) -> Result { - 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 { - 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)?; diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs index b4dc6cd310..9a748385bb 100644 --- a/contracts/coconut-dkg/src/epoch_state/queries.rs +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -1,10 +1,10 @@ // Copyright 2022 - Nym Technologies SA // 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 { 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, ContractError> { - Ok(INITIAL_REPLACEMENT_DATA.may_load(storage)?) -} - #[cfg(test)] pub(crate) mod test { use super::*; diff --git a/contracts/coconut-dkg/src/epoch_state/storage.rs b/contracts/coconut-dkg/src/epoch_state/storage.rs index 36d3b7bd91..56c80fac2d 100644 --- a/contracts/coconut-dkg/src/epoch_state/storage.rs +++ b/contracts/coconut-dkg/src/epoch_state/storage.rs @@ -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 = Item::new("threshold"); - -#[deprecated] -pub const INITIAL_REPLACEMENT_DATA: Item = - Item::new("initial_replacement_data"); 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 index f3384e84e6..715a6e5656 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs @@ -1,17 +1,11 @@ // 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::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 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, _>>()?; - // 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)] diff --git a/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs b/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs index 4b5a087dda..abcb78a21e 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs @@ -1,14 +1,10 @@ // Copyright 2022-2024 - Nym Technologies SA // 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::>()?; - - 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::>()?; + // + // 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, -) -> Result { - 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 { - 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 { - 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 { - 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, diff --git a/contracts/coconut-dkg/src/epoch_state/utils.rs b/contracts/coconut-dkg/src/epoch_state/utils.rs index be69318e49..3e712c431d 100644 --- a/contracts/coconut-dkg/src/epoch_state/utils.rs +++ b/contracts/coconut-dkg/src/epoch_state/utils.rs @@ -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, 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 { - 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 { - 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, diff --git a/contracts/coconut-dkg/src/error.rs b/contracts/coconut-dkg/src/error.rs index 743f3b80df..f7952deedc 100644 --- a/contracts/coconut-dkg/src/error.rs +++ b/contracts/coconut-dkg/src/error.rs @@ -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, diff --git a/contracts/coconut-dkg/src/verification_key_shares/storage.rs b/contracts/coconut-dkg/src/verification_key_shares/storage.rs index 0951069e95..a2c626af5b 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/storage.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/storage.rs @@ -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, 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, 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::>()?) -} diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index 583d8c5533..5c683a7690 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -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()