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 978a585ce4..33ae58bc42 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 @@ -11,6 +11,7 @@ use cosmwasm_std::Addr; use nym_coconut_dkg_common::types::{ChunkIndex, NodeIndex}; use serde::Deserialize; +use nym_coconut_dkg_common::dealer::RegisteredDealerDetails; pub use nym_coconut_dkg_common::{ dealer::{DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse}, dealing::{ @@ -44,6 +45,18 @@ pub trait DkgQueryClient { self.query_dkg_contract(request).await } + async fn get_registered_dealer_details( + &self, + address: &AccountId, + epoch_id: Option, + ) -> Result { + let request = DkgQueryMsg::GetRegisteredDealer { + dealer_address: address.to_string(), + epoch_id, + }; + self.query_dkg_contract(request).await + } + async fn get_dealer_details( &self, address: &AccountId, @@ -235,6 +248,12 @@ mod tests { DkgQueryMsg::GetCurrentEpochThreshold {} => { client.get_current_epoch_threshold().ignore() } + DkgQueryMsg::GetRegisteredDealer { + dealer_address, + epoch_id, + } => client + .get_registered_dealer_details(&dealer_address.parse().unwrap(), epoch_id) + .ignore(), DkgQueryMsg::GetDealerDetails { dealer_address } => client .get_dealer_details(&dealer_address.parse().unwrap()) .ignore(), diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs index 3677d67dcd..2c0b1746eb 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs @@ -35,6 +35,11 @@ impl DealerType { } } +#[cw_serde] +pub struct RegisteredDealerDetails { + pub details: Option, +} + #[cw_serde] pub struct DealerDetailsResponse { pub details: Option, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index b048eb5f7d..02274926e9 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -11,7 +11,10 @@ use cosmwasm_schema::cw_serde; #[cfg(feature = "schema")] use crate::{ - dealer::{DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse}, + dealer::{ + DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse, + RegisteredDealerDetails, + }, dealing::{ DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse, DealingMetadataResponse, DealingStatusResponse, @@ -86,6 +89,12 @@ pub enum QueryMsg { #[cfg_attr(feature = "schema", returns(u64))] GetCurrentEpochThreshold {}, + #[cfg_attr(feature = "schema", returns(RegisteredDealerDetails))] + GetRegisteredDealer { + dealer_address: String, + epoch_id: Option, + }, + #[cfg_attr(feature = "schema", returns(DealerDetailsResponse))] GetDealerDetails { dealer_address: String }, diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index b8c5339ba8..01a3d38303 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -3,6 +3,7 @@ use crate::dealers::queries::{ query_current_dealers_paged, query_dealer_details, query_dealers_indices_paged, + query_registered_dealer_details, }; use crate::dealers::transactions::try_add_dealer; use crate::dealings::queries::{ @@ -130,6 +131,14 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { to_binary(&query_current_epoch_threshold(deps.storage)?)? } + QueryMsg::GetRegisteredDealer { + dealer_address, + epoch_id, + } => to_binary(&query_registered_dealer_details( + deps, + dealer_address, + epoch_id, + )?)?, 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 3bf0aa28f0..d88a739976 100644 --- a/contracts/coconut-dkg/src/dealers/queries.rs +++ b/contracts/coconut-dkg/src/dealers/queries.rs @@ -2,15 +2,34 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage::{ - self, get_dealer_details, get_dealer_index, DEALERS_INDICES, EPOCH_DEALERS_MAP, + self, get_dealer_details, get_dealer_index, get_registration_details, DEALERS_INDICES, + EPOCH_DEALERS_MAP, }; use crate::epoch_state::storage::CURRENT_EPOCH; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; use nym_coconut_dkg_common::dealer::{ DealerDetailsResponse, DealerType, PagedDealerIndexResponse, PagedDealerResponse, + RegisteredDealerDetails, }; -use nym_coconut_dkg_common::types::DealerDetails; +use nym_coconut_dkg_common::types::{DealerDetails, EpochId}; + +pub fn query_registered_dealer_details( + deps: Deps<'_>, + dealer_address: String, + epoch_id: Option, +) -> StdResult { + let addr = deps.api.addr_validate(&dealer_address)?; + + let epoch_id = match epoch_id { + Some(epoch_id) => epoch_id, + None => CURRENT_EPOCH.load(deps.storage)?.epoch_id, + }; + + Ok(RegisteredDealerDetails { + details: get_registration_details(deps.storage, &addr, epoch_id).ok(), + }) +} pub fn query_dealer_details( deps: Deps<'_>, diff --git a/nym-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs index c4ef30ebef..414c89fcf1 100644 --- a/nym-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -5,13 +5,15 @@ use crate::coconut::error::Result; use cw3::{ProposalResponse, VoteResponse}; use cw4::MemberResponse; use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; -use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse}; +use nym_coconut_dkg_common::dealer::{ + DealerDetails, DealerDetailsResponse, RegisteredDealerDetails, +}; use nym_coconut_dkg_common::dealing::{ DealerDealingsStatusResponse, DealingChunkInfo, DealingMetadata, DealingStatusResponse, PartialContractDealing, }; use nym_coconut_dkg_common::types::{ - ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, + ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, PartialContractDealingData, State, }; use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; @@ -47,10 +49,14 @@ pub trait Client { async fn get_current_epoch_threshold(&self) -> Result>; - async fn get_initial_dealers(&self) -> Result>; - async fn get_self_registered_dealer_details(&self) -> Result; + async fn get_registered_dealer_details( + &self, + epoch_id: EpochId, + dealer: String, + ) -> Result; + async fn get_dealer_dealings_status( &self, epoch_id: EpochId, @@ -111,11 +117,7 @@ pub trait Client { resharing: bool, ) -> Result; - async fn submit_dealing_chunk( - &self, - chunk: PartialContractDealing, - resharing: bool, - ) -> Result; + async fn submit_dealing_chunk(&self, chunk: PartialContractDealing) -> Result; async fn submit_verification_key_share( &self, diff --git a/nym-api/src/coconut/dkg/client.rs b/nym-api/src/coconut/dkg/client.rs index 9397d31a11..bad9ce6ea9 100644 --- a/nym-api/src/coconut/dkg/client.rs +++ b/nym-api/src/coconut/dkg/client.rs @@ -10,8 +10,8 @@ use nym_coconut_dkg_common::dealing::{ DealerDealingsStatusResponse, DealingChunkInfo, PartialContractDealing, }; use nym_coconut_dkg_common::types::{ - ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, - NodeIndex, PartialContractDealingData, State as ContractState, + ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, NodeIndex, + PartialContractDealingData, State as ContractState, }; use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use nym_contracts_common::IdentityKey; @@ -62,18 +62,23 @@ impl DkgClient { self.inner.get_current_epoch_threshold().await } - pub(crate) async fn get_initial_dealers( - &self, - ) -> Result, CoconutError> { - self.inner.get_initial_dealers().await - } - pub(crate) async fn get_self_registered_dealer_details( &self, ) -> Result { self.inner.get_self_registered_dealer_details().await } + pub(crate) async fn dealer_in_epoch( + &self, + epoch_id: EpochId, + dealer: String, + ) -> Result { + self.inner + .get_registered_dealer_details(epoch_id, dealer) + .await + .map(|d| d.details.is_some()) + } + pub(crate) async fn get_current_dealers(&self) -> Result, CoconutError> { self.inner.get_current_dealers().await } @@ -190,9 +195,8 @@ impl DkgClient { pub(crate) async fn submit_dealing_chunk( &self, chunk: PartialContractDealing, - resharing: bool, ) -> Result<(), CoconutError> { - self.inner.submit_dealing_chunk(chunk, resharing).await?; + self.inner.submit_dealing_chunk(chunk).await?; Ok(()) } diff --git a/nym-api/src/coconut/dkg/dealing.rs b/nym-api/src/coconut/dkg/dealing.rs index 77a6f3310e..0c5b8132a6 100644 --- a/nym-api/src/coconut/dkg/dealing.rs +++ b/nym-api/src/coconut/dkg/dealing.rs @@ -209,9 +209,7 @@ impl DkgController { .remove(chunk_index) .expect("chunking specification has changed mid-exchange!"); debug!("[dealing {dealing_index}]: resubmitting chunk index {chunk_index}"); - self.dkg_client - .submit_dealing_chunk(chunk, resharing) - .await?; + self.dkg_client.submit_dealing_chunk(chunk).await?; } } Ok(()) @@ -243,26 +241,28 @@ impl DkgController { let human_index = chunk_index + 1; debug!("[dealing {dealing_index}]: submitting chunk index {chunk_index} ({human_index}/{total_chunks})"); - self.dkg_client - .submit_dealing_chunk(chunk, resharing) - .await?; + self.dkg_client.submit_dealing_chunk(chunk).await?; } Ok(()) } /// Check whether this dealer can participate in the resharing - /// by looking into the contract and ensuring it's in the list of initial dealers for this epoch - async fn can_reshare(&self) -> Result { - let Some(initial_data) = self.dkg_client.get_initial_dealers().await? else { - return Ok(false); - }; + /// by looking into the contract and ensuring it's been a dealer in the previous epoch + async fn can_reshare(&self, epoch_id: EpochId) -> Result { + // SAFETY: + // it's impossible for the contract to trigger resharing for the 0th epoch + // otherwise some serious invariants have been broken + #[allow(clippy::expect_used)] + let previous_epoch_id = epoch_id + .checked_sub(1) + .expect("resharing epoch invariant has been broken"); let address = self.dkg_client.get_address().await; - Ok(initial_data - .initial_dealers - .iter() - .any(|d| d.as_str() == address.as_ref())) + Ok(self + .dkg_client + .dealer_in_epoch(previous_epoch_id, address.to_string()) + .await?) } /// Deal with the dealing generation case where the system requests resharing @@ -274,7 +274,7 @@ impl DkgController { old_keypair: KeyPairWithEpoch, ) -> Result<(), DealingGenerationError> { // make sure we're allowed to participate in resharing - if !self.can_reshare().await? { + if !self.can_reshare(epoch_id).await? { // we have to wait for other dealers to give us the dealings (hopefully) warn!("we we have an existing coconut keypair, but we're not allowed to participate in resharing"); return Ok(()); @@ -427,7 +427,7 @@ impl DkgController { // sure, the if statements could be collapsed, but i prefer to explicitly repeat the block for readability if resharing { debug!("resharing + no prior key -> nothing to do"); - if self.can_reshare().await? { + if self.can_reshare(epoch_id).await? { warn!("this dealer was expected to participate in resharing but it doesn't have any prior keys to use"); } } else { @@ -475,7 +475,6 @@ pub(crate) mod tests { }; use crate::coconut::tests::helpers::unchecked_decode_bte_key; use nym_coconut::{ttp_keygen, Parameters}; - use nym_coconut_dkg_common::types::InitialReplacementData; use nym_dkg::bte::PublicKeyWithProof; #[tokio::test] diff --git a/nym-api/src/coconut/dkg/key_derivation.rs b/nym-api/src/coconut/dkg/key_derivation.rs index 15f2a047d2..ca85716d13 100644 --- a/nym-api/src/coconut/dkg/key_derivation.rs +++ b/nym-api/src/coconut/dkg/key_derivation.rs @@ -186,7 +186,6 @@ impl DkgController { epoch_id: EpochId, dealer: &Addr, resharing: bool, - initial_dealers: &[Addr], ) -> Result>, DealerRejectionReason>, KeyDerivationError> { let dealing_statuses = self @@ -208,11 +207,25 @@ impl DkgController { )); } - // we might be in resharing mode and this dealer was not in "initial" set. - // in that case we don't expect any dealings - if resharing && !initial_dealers.contains(dealer) { - return Ok(Ok(HashMap::new())); + // if we're in the resharing mode and this dealer has not been a dealer in the previous epoch, + // we don't expect to have received anything from them + if resharing { + // SAFETY: + // it's impossible for the contract to trigger resharing for the 0th epoch + // otherwise some serious invariants have been broken + #[allow(clippy::expect_used)] + let previous_epoch_id = epoch_id + .checked_sub(1) + .expect("resharing epoch invariant has been broken"); + if !self + .dkg_client + .dealer_in_epoch(previous_epoch_id, dealer.to_string()) + .await? + { + return Ok(Ok(HashMap::new())); + } } + return Ok(Err(DealerRejectionReason::NoDealingsProvided)); } @@ -251,22 +264,13 @@ impl DkgController { let mut valid_dealings: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new(); // given at MOST we'll have like 50 entries here, iterating over entire vector for lookup is fine - let initial_dealers = self - .dkg_client - .get_initial_dealers() - .await? - .map(|i| i.initial_dealers) - .unwrap_or_default(); // for every valid dealer in this epoch, obtain its dealings for (dealer, dealer_index) in self.state.valid_epoch_receivers(epoch_id)? { // note: if we're in resharing mode, the contract itself will forbid submission of dealings from - // parties that were not "initial" dealers, so we don't have to worry about it + // parties that were dealers in the previous epoch, so we don't have to worry about it - let raw_dealings = match self - .get_raw_dealings(epoch_id, &dealer, resharing, &initial_dealers) - .await? - { + let raw_dealings = match self.get_raw_dealings(epoch_id, &dealer, resharing).await? { Ok(dealings) => dealings, Err(rejection) => { self.blacklist_dealer(epoch_id, dealer, rejection)?; diff --git a/nym-api/src/coconut/tests/mod.rs b/nym-api/src/coconut/tests/mod.rs index 5690c7c46e..eb055f3cfa 100644 --- a/nym-api/src/coconut/tests/mod.rs +++ b/nym-api/src/coconut/tests/mod.rs @@ -29,7 +29,7 @@ use nym_coconut_dkg_common::dealing::{ use nym_coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX}; use nym_coconut_dkg_common::types::{ ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, EpochState, - InitialReplacementData, PartialContractDealingData, State as ContractState, + PartialContractDealingData, State as ContractState, }; use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use nym_coconut_interface::VerificationKey; @@ -135,9 +135,9 @@ impl Dealing { pub(crate) struct FakeDkgContractState { pub(crate) address: AccountId, - pub(crate) dealers: HashMap, - pub(crate) past_dealers: HashMap, - pub(crate) initial_dealers: Option, + // pub(crate) dealers: HashMap, + // pub(crate) past_dealers: HashMap, + // pub(crate) initial_dealers: Option, // map of epoch id -> dealer -> dealings pub(crate) dealings: HashMap>>, @@ -651,16 +651,6 @@ impl super::client::Client for DummyClient { Ok(self.state.lock().unwrap().dkg_contract.threshold) } - async fn get_initial_dealers(&self) -> Result> { - Ok(self - .state - .lock() - .unwrap() - .dkg_contract - .initial_dealers - .clone()) - } - async fn get_self_registered_dealer_details(&self) -> Result { let address = self.validator_address.as_ref(); @@ -1081,11 +1071,7 @@ impl super::client::Client for DummyClient { }) } - async fn submit_dealing_chunk( - &self, - chunk: PartialContractDealing, - _resharing: bool, - ) -> Result { + async fn submit_dealing_chunk(&self, chunk: PartialContractDealing) -> Result { let mut guard = self.state.lock().unwrap(); let current_epoch = guard.dkg_contract.epoch.epoch_id; let current_height = guard.block_info.height; diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index d583669fe9..dcd60c1234 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -14,9 +14,7 @@ use nym_coconut_dkg_common::dealing::{ PartialContractDealing, }; use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; -use nym_coconut_dkg_common::types::{ - ChunkIndex, DealingIndex, InitialReplacementData, PartialContractDealingData, State, -}; +use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, PartialContractDealingData, State}; use nym_coconut_dkg_common::{ dealer::{DealerDetails, DealerDetailsResponse}, types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId}, @@ -24,6 +22,7 @@ use nym_coconut_dkg_common::{ }; use nym_config::defaults::{ChainDetails, NymNetworkDetails}; +use nym_coconut_dkg_common::dealer::RegisteredDealerDetails; use nym_mixnet_contract_common::families::FamilyHead; use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::reward_params::RewardingParams; @@ -412,12 +411,6 @@ impl crate::coconut::client::Client for Client { Ok(nyxd_query!(self, get_current_epoch_threshold().await?)) } - async fn get_initial_dealers( - &self, - ) -> crate::coconut::error::Result> { - Ok(nyxd_query!(self, get_initial_dealers().await?)) - } - async fn get_self_registered_dealer_details( &self, ) -> crate::coconut::error::Result { @@ -425,6 +418,21 @@ impl crate::coconut::client::Client for Client { Ok(nyxd_query!(self, get_dealer_details(self_address).await?)) } + async fn get_registered_dealer_details( + &self, + epoch_id: EpochId, + dealer: String, + ) -> crate::coconut::error::Result { + let dealer = dealer + .as_str() + .parse() + .map_err(|_| NyxdError::MalformedAccountAddress(dealer))?; + Ok(nyxd_query!( + self, + get_registered_dealer_details(&dealer, Some(epoch_id)).await? + )) + } + async fn get_dealer_dealings_status( &self, epoch_id: EpochId, @@ -547,11 +555,10 @@ impl crate::coconut::client::Client for Client { async fn submit_dealing_chunk( &self, chunk: PartialContractDealing, - resharing: bool, ) -> Result { Ok(nyxd_signing!( self, - submit_dealing_chunk(chunk, resharing, None).await? + submit_dealing_chunk(chunk, None).await? )) }