From bd216453ccfdbd9d0b78594f65582d5aca618dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 10 May 2022 15:05:21 +0100 Subject: [PATCH] Querying contract for the current(and past) set of dkg dealers --- Cargo.lock | 1 + .../validator-client/src/client.rs | 70 +++++++++++- .../src/nymd/traits/dkg_client.rs | 38 +++++++ .../coconut-dkg-contract/Cargo.toml | 1 + .../coconut-dkg-contract/src/dealer.rs | 107 ++++++++++++++++++ .../coconut-dkg-contract/src/lib.rs | 1 + .../coconut-dkg-contract/src/msg.rs | 8 ++ .../coconut-dkg-contract/src/types.rs | 80 +------------ contracts/Cargo.lock | 1 + contracts/coconut-dkg/src/dealers/queries.rs | 49 ++++++++ contracts/coconut-dkg/src/dealers/storage.rs | 9 +- .../coconut-dkg/src/dealers/transactions.rs | 1 + contracts/coconut-dkg/src/lib.rs | 7 ++ validator-api/src/nymd_client.rs | 18 ++- 14 files changed, 306 insertions(+), 85 deletions(-) create mode 100644 common/cosmwasm-smart-contracts/coconut-dkg-contract/src/dealer.rs diff --git a/Cargo.lock b/Cargo.lock index 8cf82bec7d..5a5a454063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -626,6 +626,7 @@ dependencies = [ name = "coconut-dkg-common" version = "0.1.0" dependencies = [ + "cosmwasm-std", "schemars", "serde", ] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 66aeb6b28a..8b6243c416 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -21,6 +21,9 @@ use crate::nymd::{ error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient, }; +use crate::nymd::traits::DkgClient; +use crate::nymd::SigningCosmWasmClient; +use coconut_dkg_common::types::DealerDetails; #[cfg(feature = "nymd-client")] use mixnet_contract_common::{ mixnode::DelegationEvent, ContractStateParams, Delegation, IdentityKey, Interval, @@ -49,6 +52,8 @@ pub struct Config { gateway_page_limit: Option, mixnode_delegations_page_limit: Option, rewarded_set_page_limit: Option, + #[cfg(feature = "dkg")] + dealers_page_limit: Option, } #[cfg(feature = "nymd-client")] @@ -74,6 +79,8 @@ impl Config { gateway_page_limit: None, mixnode_delegations_page_limit: None, rewarded_set_page_limit: None, + #[cfg(feature = "dkg")] + dealers_page_limit: None, } } @@ -121,6 +128,8 @@ pub struct Client { gateway_page_limit: Option, mixnode_delegations_page_limit: Option, rewarded_set_page_limit: Option, + #[cfg(feature = "dkg")] + dealers_page_limit: Option, // ideally they would have been read-only, but unfortunately rust doesn't have such features pub validator_api: validator_api::Client, @@ -155,7 +164,8 @@ impl Client { mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, - rewarded_set_page_limit: None, + rewarded_set_page_limit: config.rewarded_set_page_limit, + dealers_page_limit: config.dealers_page_limit, validator_api: validator_api_client, nymd: nymd_client, }) @@ -216,6 +226,7 @@ impl Client { gateway_page_limit: config.gateway_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, rewarded_set_page_limit: config.rewarded_set_page_limit, + dealers_page_limit: config.dealers_page_limit, validator_api: validator_api_client, nymd: nymd_client, }) @@ -745,3 +756,60 @@ impl ApiClient { Ok(self.validator_api.get_coconut_verification_key().await?) } } + +// dkg-related impl block +#[cfg(feature = "dkg")] +impl Client +where + C: SigningCosmWasmClient + Send + Sync, +{ + pub async fn get_all_nymd_current_dealers( + &self, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut dealers = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_current_dealers_paged(start_after.take(), self.mixnode_page_limit) + .await?; + dealers.append(&mut paged_response.dealers); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res.into_string()) + } else { + break; + } + } + + Ok(dealers) + } + + pub async fn get_all_nymd_past_dealers( + &self, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut dealers = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_past_dealers_paged(start_after.take(), self.mixnode_page_limit) + .await?; + dealers.append(&mut paged_response.dealers); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res.into_string()) + } else { + break; + } + } + + Ok(dealers) + } +} diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_client.rs b/common/client-libs/validator-client/src/nymd/traits/dkg_client.rs index 8680029f5e..a878ea3ee5 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/dkg_client.rs @@ -5,6 +5,7 @@ use crate::nymd::cosmwasm_client::types::ExecuteResult; use crate::nymd::error::NymdError; use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient}; use async_trait::async_trait; +use coconut_dkg_common::dealer::PagedDealerResponse; use coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg; use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; use coconut_dkg_common::types::Epoch; @@ -12,6 +13,17 @@ use coconut_dkg_common::types::Epoch; #[async_trait] pub trait DkgClient { async fn get_current_dkg_epoch(&self) -> Result; + async fn get_current_dealers_paged( + &self, + start_after: Option, + page_limit: Option, + ) -> Result; + async fn get_past_dealers_paged( + &self, + start_after: Option, + page_limit: Option, + ) -> Result; + async fn submit_dealing_commitment( &self, epoch_id: u32, @@ -32,6 +44,32 @@ where .query_contract_smart(self.coconut_dkg_contract_address()?, &request) .await } + async fn get_current_dealers_paged( + &self, + start_after: Option, + page_limit: Option, + ) -> Result { + let request = DkgQueryMsg::GetCurrentDealers { + start_after, + limit: page_limit, + }; + self.client + .query_contract_smart(self.coconut_dkg_contract_address()?, &request) + .await + } + async fn get_past_dealers_paged( + &self, + start_after: Option, + page_limit: Option, + ) -> Result { + let request = DkgQueryMsg::GetPastDealers { + start_after, + limit: page_limit, + }; + self.client + .query_contract_smart(self.coconut_dkg_contract_address()?, &request) + .await + } async fn submit_dealing_commitment( &self, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg-contract/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-dkg-contract/Cargo.toml index ac75f1164b..992efa69cb 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-dkg-contract/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +cosmwasm-std = "1.0.0-beta8" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/dealer.rs b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/dealer.rs new file mode 100644 index 0000000000..943644342a --- /dev/null +++ b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/dealer.rs @@ -0,0 +1,107 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::types::{BlockHeight, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, NodeIndex}; +use cosmwasm_std::Addr; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct DealerDetails { + pub address: Addr, + pub joined_at: BlockHeight, + pub left_at: Option, + pub blacklisting: Option, + pub ed25519_public_key: EncodedEd25519PublicKey, + pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof, + pub assigned_index: NodeIndex, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct Blacklisting { + pub reason: BlacklistingReason, + pub height: BlockHeight, + pub expiration: Option, +} + +impl Blacklisting { + pub fn has_expired(&self, current_block: BlockHeight) -> bool { + self.expiration + .map(|expiration| expiration <= current_block) + .unwrap_or_default() + } +} + +impl Display for Blacklisting { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if let Some(expiration) = self.expiration { + write!( + f, + "blacklisted at block height {}. reason given: {}. Expires at: {}", + self.height, self.height, expiration + ) + } else { + write!( + f, + "blacklisted at block height {}. reason given: {}", + self.height, self.height + ) + } + } +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum BlacklistingReason { + InactiveForConsecutiveEpochs, + MalformedBTEPublicKey, + InvalidBTEPublicKey, + MalformedEd25519PublicKey, + Ed25519PossessionVerificationFailure, +} + +impl Display for BlacklistingReason { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + BlacklistingReason::InactiveForConsecutiveEpochs => { + write!(f, "has been inactive for multiple consecutive epochs") + } + BlacklistingReason::MalformedBTEPublicKey => { + write!(f, "provided malformed BTE Public Key") + } + BlacklistingReason::InvalidBTEPublicKey => write!(f, "provided invalid BTE Public Key"), + BlacklistingReason::MalformedEd25519PublicKey => { + write!(f, "provided malformed ed25519 Public Key") + } + BlacklistingReason::Ed25519PossessionVerificationFailure => { + write!( + f, + "failed to verify possession of provided ed25519 Public Key" + ) + } + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct PagedDealerResponse { + pub dealers: Vec, + pub per_page: usize, + pub start_next_after: Option, +} + +impl PagedDealerResponse { + pub fn new( + dealers: Vec, + per_page: usize, + start_next_after: Option, + ) -> Self { + PagedDealerResponse { + dealers, + per_page, + start_next_after, + } + } +} diff --git a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/lib.rs b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/lib.rs index cf978b6d14..bca6634c61 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/lib.rs @@ -1,5 +1,6 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub mod dealer; pub mod msg; pub mod types; diff --git a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/msg.rs index 273a721bd8..c25862abd9 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/msg.rs @@ -32,6 +32,14 @@ pub enum ExecuteMsg { #[serde(rename_all = "snake_case")] pub enum QueryMsg { GetCurrentEpoch {}, + GetCurrentDealers { + limit: Option, + start_after: Option, + }, + GetPastDealers { + limit: Option, + start_after: Option, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/types.rs index caf9ad6507..633a41935c 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/types.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use serde::{Deserialize, Serialize}; -use std::fmt::{Display, Formatter}; +pub use crate::dealer::{Blacklisting, BlacklistingReason, DealerDetails}; pub type BlockHeight = u64; pub type EncodedEd25519PublicKey = String; pub type EncodedEd25519PublicKeyRef<'a> = &'a str; @@ -11,84 +11,6 @@ pub type EncodedBTEPublicKeyWithProof = String; pub type NodeIndex = u64; pub type EpochId = u32; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct DealerDetails { - pub joined_at: BlockHeight, - pub left_at: Option, - pub blacklisting: Option, - pub ed25519_public_key: EncodedEd25519PublicKey, - pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof, - pub assigned_index: NodeIndex, -} - -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct Blacklisting { - pub reason: BlacklistingReason, - pub height: BlockHeight, - pub expiration: Option, -} - -impl Blacklisting { - pub fn has_expired(&self, current_block: BlockHeight) -> bool { - self.expiration - .map(|expiration| expiration <= current_block) - .unwrap_or_default() - } -} - -impl Display for Blacklisting { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if let Some(expiration) = self.expiration { - write!( - f, - "blacklisted at block height {}. reason given: {}. Expires at: {}", - self.height, self.height, expiration - ) - } else { - write!( - f, - "blacklisted at block height {}. reason given: {}", - self.height, self.height - ) - } - } -} - -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum BlacklistingReason { - InactiveForConsecutiveEpochs, - MalformedBTEPublicKey, - InvalidBTEPublicKey, - MalformedEd25519PublicKey, - Ed25519PossessionVerificationFailure, -} - -impl Display for BlacklistingReason { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - BlacklistingReason::InactiveForConsecutiveEpochs => { - write!(f, "has been inactive for multiple consecutive epochs") - } - BlacklistingReason::MalformedBTEPublicKey => { - write!(f, "provided malformed BTE Public Key") - } - BlacklistingReason::InvalidBTEPublicKey => write!(f, "provided invalid BTE Public Key"), - BlacklistingReason::MalformedEd25519PublicKey => { - write!(f, "provided malformed ed25519 Public Key") - } - BlacklistingReason::Ed25519PossessionVerificationFailure => { - write!( - f, - "failed to verify possession of provided ed25519 Public Key" - ) - } - } - } -} - #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] #[serde(rename_all = "snake_case")] pub struct Epoch { diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 385acb562c..81d920c06d 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -241,6 +241,7 @@ dependencies = [ name = "coconut-dkg-common" version = "0.1.0" dependencies = [ + "cosmwasm-std", "schemars", "serde", ] diff --git a/contracts/coconut-dkg/src/dealers/queries.rs b/contracts/coconut-dkg/src/dealers/queries.rs index 87d5c392c8..e32cc38af8 100644 --- a/contracts/coconut-dkg/src/dealers/queries.rs +++ b/contracts/coconut-dkg/src/dealers/queries.rs @@ -1,2 +1,51 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + +use crate::dealers::storage; +use crate::dealers::storage::DealersMap; +use coconut_dkg_common::dealer::PagedDealerResponse; +use cosmwasm_std::{Deps, Order, StdResult}; +use cw_storage_plus::Bound; + +fn query_dealers( + deps: Deps<'_>, + start_after: Option, + limit: Option, + underlying_map: DealersMap<'_>, +) -> 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_current_dealers_paged( + deps: Deps<'_>, + start_after: Option, + limit: Option, +) -> StdResult { + query_dealers(deps, start_after, limit, storage::current_dealers()) +} + +pub fn query_past_dealers_paged( + deps: Deps<'_>, + start_after: Option, + limit: Option, +) -> StdResult { + query_dealers(deps, start_after, limit, storage::past_dealers()) +} diff --git a/contracts/coconut-dkg/src/dealers/storage.rs b/contracts/coconut-dkg/src/dealers/storage.rs index f4b6f3f0fc..088e989041 100644 --- a/contracts/coconut-dkg/src/dealers/storage.rs +++ b/contracts/coconut-dkg/src/dealers/storage.rs @@ -11,9 +11,14 @@ 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 BLACKLISTED_DEALERS: Map<'_, &'_ Addr, Blacklisting> = Map::new("bld"); pub(crate) const NODE_INDEX_COUNTER: Item = Item::new("node_index_counter"); +pub(crate) type DealersMap<'a> = IndexedMap<'a, &'a Addr, DealerDetails, DealersIndex<'a>>; + pub(crate) struct DealersIndex<'a> { pub(crate) node_index: UniqueIndex<'a, NodeIndex, DealerDetails>, } @@ -25,14 +30,14 @@ impl<'a> IndexList for DealersIndex<'a> { } } -pub(crate) fn current_dealers<'a>() -> IndexedMap<'a, &'a Addr, DealerDetails, DealersIndex<'a>> { +pub(crate) fn current_dealers<'a>() -> DealersMap<'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 past_dealers<'a>() -> IndexedMap<'a, &'a Addr, DealerDetails, DealersIndex<'a>> { +pub(crate) fn past_dealers<'a>() -> DealersMap<'a> { let indexes = DealersIndex { node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE), }; diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index b1315345da..f75b098dcb 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -154,6 +154,7 @@ pub fn try_add_dealer( // save the dealer into the storage let dealer_details = DealerDetails { + address: info.sender.clone(), joined_at: env.block.height, left_at: None, blacklisting: None, diff --git a/contracts/coconut-dkg/src/lib.rs b/contracts/coconut-dkg/src/lib.rs index bd5e2d94f5..15dd70e2ad 100644 --- a/contracts/coconut-dkg/src/lib.rs +++ b/contracts/coconut-dkg/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::dealers::queries::{query_current_dealers_paged, query_past_dealers_paged}; use crate::epoch::queries::query_current_epoch; use crate::error::ContractError; use coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; @@ -81,6 +82,12 @@ pub fn execute( pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { let response = match msg { QueryMsg::GetCurrentEpoch {} => to_binary(&query_current_epoch(deps.storage)?)?, + QueryMsg::GetCurrentDealers { limit, start_after } => { + to_binary(&query_current_dealers_paged(deps, start_after, limit)?)? + } + QueryMsg::GetPastDealers { limit, start_after } => { + to_binary(&query_past_dealers_paged(deps, start_after, limit)?)? + } }; Ok(response) diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 4054078d7d..8d26f4658c 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -5,7 +5,7 @@ use crate::config::Config; use crate::rewarded_set_updater::error::RewardingError; #[cfg(feature = "coconut")] use async_trait::async_trait; -use coconut_dkg_common::types::Epoch as DkgEpoch; +use coconut_dkg_common::types::{DealerDetails, Epoch as DkgEpoch}; use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT}; use mixnet_contract_common::Interval; use mixnet_contract_common::{ @@ -432,11 +432,23 @@ impl Client where C: SigningCosmWasmClient + Sync + Send, { - async fn get_dkg_epoch(&self) -> Result { + pub(crate) async fn get_dkg_epoch(&self) -> Result { self.0.read().await.nymd.get_current_dkg_epoch().await } - async fn submit_dealing_commitment( + pub(crate) async fn get_current_dealers( + &self, + ) -> Result, ValidatorClientError> { + self.0.read().await.get_all_nymd_current_dealers().await + } + + pub(crate) async fn get_past_dealers( + &self, + ) -> Result, ValidatorClientError> { + self.0.read().await.get_all_nymd_past_dealers().await + } + + pub(crate) async fn submit_dealing_commitment( &self, epoch_id: u32, dealing_digest: [u8; 32],