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 46712cb81b..0b1e1e50f3 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 @@ -70,7 +70,7 @@ pub trait DkgQueryClient { start_after: Option, limit: Option, ) -> Result { - let request = DkgQueryMsg::GetDealing { + let request = DkgQueryMsg::GetDealings { idx, limit, start_after, @@ -165,7 +165,7 @@ mod tests { DkgQueryMsg::GetPastDealers { limit, start_after } => { client.get_past_dealers_paged(start_after, limit).ignore() } - DkgQueryMsg::GetDealing { + DkgQueryMsg::GetDealings { idx, limit, start_after, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs index 629e6cbf12..3e626a41ff 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs @@ -1,7 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, NodeIndex}; +use crate::types::{ + ContractDealing, DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, NodeIndex, + PartialContractDealing, +}; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; @@ -65,37 +68,40 @@ impl PagedDealerResponse { } } -#[deprecated] #[cw_serde] -pub struct ContractDealing { - pub dealing: ContractSafeBytes, - pub dealer: Addr, -} +pub struct DealingResponse { + pub epoch_id: EpochId, -impl ContractDealing { - pub fn new(dealing: ContractSafeBytes, dealer: Addr) -> Self { - ContractDealing { dealing, dealer } - } + pub dealer: Addr, + + pub dealing_index: DealingIndex, + + pub dealing: Option, } #[cw_serde] pub struct PagedDealingsResponse { - pub dealings: Vec, - pub per_page: usize, + pub epoch_id: EpochId, + + pub dealer: Addr, + + pub dealings: Vec, /// Field indicating paging information for the following queries if the caller wishes to get further entries. - pub start_next_after: Option, + pub start_next_after: Option, } impl PagedDealingsResponse { pub fn new( - dealings: Vec, - per_page: usize, - start_next_after: Option, + epoch_id: EpochId, + dealer: Addr, + dealings: Vec, + start_next_after: Option, ) -> Self { PagedDealingsResponse { + epoch_id, + dealer, dealings, - per_page, start_next_after, } } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index 3b760d69a4..39624313fc 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -1,14 +1,16 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::types::{PartialContractDealing, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration}; +use crate::types::{ + DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, PartialContractDealing, TimeConfiguration, +}; use crate::verification_key::VerificationKeyShare; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; #[cfg(feature = "schema")] use crate::{ - dealer::{DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse}, + dealer::{DealerDetailsResponse, DealingResponse, PagedDealerResponse, PagedDealingsResponse}, types::{Epoch, InitialReplacementData}, verification_key::PagedVKSharesResponse, }; @@ -82,11 +84,19 @@ pub enum QueryMsg { start_after: Option, }, - #[cfg_attr(feature = "schema", returns(PagedDealingsResponse))] + #[cfg_attr(feature = "schema", returns(DealingResponse))] GetDealing { - idx: u64, + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + }, + + #[cfg_attr(feature = "schema", returns(PagedDealingsResponse))] + GetDealings { + epoch_id: EpochId, + dealer: String, limit: Option, - start_after: Option, + start_after: Option, }, #[cfg_attr(feature = "schema", returns(PagedVKSharesResponse))] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index 17d610fd40..0e09978323 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -14,6 +14,7 @@ pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str; pub type NodeIndex = u64; pub type EpochId = u64; pub type DealingIndex = u32; +pub type ContractDealing = ContractSafeBytes; // 2 public attributes, 2 private attributes, 1 fixed for coconut credential pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; @@ -21,7 +22,7 @@ pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; #[cw_serde] pub struct PartialContractDealing { pub index: u32, - pub data: ContractSafeBytes, + pub data: ContractDealing, } #[cw_serde] diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index caf643ab87..e1221bfc33 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -5,7 +5,7 @@ use crate::dealers::queries::{ query_current_dealers_paged, query_dealer_details, query_past_dealers_paged, }; use crate::dealers::transactions::try_add_dealer; -use crate::dealings::queries::query_dealings_paged; +use crate::dealings::queries::{query_dealing, query_dealings_paged}; use crate::dealings::transactions::try_commit_dealings; use crate::epoch_state::queries::{ query_current_epoch, query_current_epoch_threshold, query_initial_dealers, @@ -112,10 +112,22 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result to_binary(&query_dealing(deps, epoch_id, dealer, dealing_index)?)?, + QueryMsg::GetDealings { + epoch_id, + dealer, limit, start_after, - } => to_binary(&query_dealings_paged(deps, idx, start_after, limit)?)?, + } => to_binary(&query_dealings_paged( + deps, + epoch_id, + dealer, + limit, + start_after, + )?)?, QueryMsg::GetVerificationKeys { epoch_id, limit, diff --git a/contracts/coconut-dkg/src/dealings/queries.rs b/contracts/coconut-dkg/src/dealings/queries.rs index 1e35fd2c3d..3f2f0470f7 100644 --- a/contracts/coconut-dkg/src/dealings/queries.rs +++ b/contracts/coconut-dkg/src/dealings/queries.rs @@ -2,44 +2,52 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealings::storage; -use crate::dealings::storage::DEALINGS_BYTES; -use cosmwasm_std::{Deps, Order, StdResult}; +use crate::dealings::storage::StoredDealing; +use cosmwasm_std::{Deps, StdResult}; use cw_storage_plus::Bound; -use nym_coconut_dkg_common::dealer::{ContractDealing, PagedDealingsResponse}; -use nym_coconut_dkg_common::types::TOTAL_DEALINGS; +use nym_coconut_dkg_common::dealer::{DealingResponse, PagedDealingsResponse}; +use nym_coconut_dkg_common::types::{DealingIndex, EpochId}; +pub fn query_dealing( + deps: Deps<'_>, + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, +) -> StdResult { + let dealer = deps.api.addr_validate(&dealer)?; + let dealing = StoredDealing::read(deps.storage, epoch_id, &dealer, dealing_index); + + Ok(DealingResponse { + epoch_id, + dealer, + dealing_index, + dealing, + }) +} pub fn query_dealings_paged( deps: Deps<'_>, - idx: u64, - start_after: Option, + epoch_id: EpochId, + dealer: String, + start_after: Option, limit: Option, ) -> StdResult { let limit = limit .unwrap_or(storage::DEALINGS_PAGE_DEFAULT_LIMIT) - .min(storage::DEALINGS_PAGE_MAX_LIMIT) as usize; + .min(storage::DEALINGS_PAGE_MAX_LIMIT); - let idx = idx as usize; - if idx >= TOTAL_DEALINGS { - return Ok(PagedDealingsResponse::new(vec![], limit, None)); - } + let dealer = deps.api.addr_validate(&dealer)?; + let start = start_after.map(Bound::exclusive); - let addr = start_after - .map(|addr| deps.api.addr_validate(&addr)) - .transpose()?; - - let start = addr.as_ref().map(Bound::exclusive); - - let dealings = DEALINGS_BYTES[idx] - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|res| res.map(|(dealer, dealing)| ContractDealing::new(dealing, dealer))) + let dealings = StoredDealing::prefix_range(deps.storage, (epoch_id, &dealer), start) + .take(limit as usize) .collect::>>()?; - let start_next_after = dealings.last().map(|dealing| dealing.dealer.clone()); + let start_next_after = dealings.last().map(|dealing| dealing.index); Ok(PagedDealingsResponse::new( + epoch_id, + dealer, dealings, - limit, start_next_after, )) } diff --git a/contracts/coconut-dkg/src/dealings/storage.rs b/contracts/coconut-dkg/src/dealings/storage.rs index 521ccd89be..0cc3180679 100644 --- a/contracts/coconut-dkg/src/dealings/storage.rs +++ b/contracts/coconut-dkg/src/dealings/storage.rs @@ -1,11 +1,12 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::error::ContractError; -use cosmwasm_std::{Addr, Storage}; -use cw_storage_plus::Map; +use cosmwasm_std::{Addr, Order, Record, StdResult, Storage}; +use cw_storage_plus::{ + range_with_prefix, Bound, Key, KeyDeserialize, Path, Prefix, Prefixer, PrimaryKey, +}; use nym_coconut_dkg_common::types::{ - ContractSafeBytes, DealingIndex, EpochId, PartialContractDealing, TOTAL_DEALINGS, + ContractDealing, ContractSafeBytes, DealingIndex, EpochId, PartialContractDealing, }; pub(crate) const DEALINGS_PAGE_MAX_LIMIT: u32 = 2; @@ -13,56 +14,102 @@ pub(crate) const DEALINGS_PAGE_DEFAULT_LIMIT: u32 = 1; type Dealer<'a> = &'a Addr; -#[deprecated] -type DealingKey<'a> = &'a Addr; - // dealings are stored in a multilevel map with the following hierarchy: // - epoch-id: // - issuer-address: // - dealing id: // - dealing content -// NOTE: we're storing raw bytes bypassing serialization, so make sure you always use the below methods for using the storage! -pub(crate) const DEALINGS: Map<(EpochId, Dealer, DealingIndex), ContractSafeBytes> = - Map::new("dealing"); +// NOTE: we're storing raw bytes bypassing serialization, so we can't use the `Map` type, +// thus make sure you always use the below methods for using the storage! -pub fn has_committed_dealing( - storage: &dyn Storage, - epoch_id: EpochId, - dealer: &Addr, - dealing_index: DealingIndex, -) -> bool { - DEALINGS.has(storage, (epoch_id, dealer, dealing_index)) +pub(crate) struct StoredDealing; + +impl StoredDealing { + const NAMESPACE: &'static [u8] = b"dealing"; + + fn deserialize_raw_dealing(kv: Record) -> StdResult { + let (k, v) = kv; + let index = ::from_vec(k)?; + let data = ContractSafeBytes(v); + + Ok(PartialContractDealing { index, data }) + } + + fn storage_key( + epoch_id: EpochId, + dealer: Dealer, + dealing_index: DealingIndex, + ) -> Path> { + // just replicate the behaviour from `Map::key` + let key = (epoch_id, dealer, dealing_index); + Path::new( + Self::NAMESPACE, + &key.key().iter().map(Key::as_ref).collect::>(), + ) + } + + fn prefix(prefix: (EpochId, Dealer)) -> Prefix { + Prefix::with_deserialization_functions( + Self::NAMESPACE, + &prefix.prefix(), + &[], + // explicitly panic to make sure we're never attempting to call an unexpected deserializer on our data + |_, _, _| panic!("attempted to call custom de_fn_kv"), + |_, _, _| panic!("attempted to call custom de_fn_v"), + ) + } + + pub(crate) fn exists( + storage: &dyn Storage, + epoch_id: EpochId, + dealer: &Addr, + dealing_index: DealingIndex, + ) -> bool { + StoredDealing::storage_key(epoch_id, dealer, dealing_index).has(storage) + } + + pub(crate) fn save( + storage: &mut dyn Storage, + epoch_id: EpochId, + dealer: Dealer, + dealing: PartialContractDealing, + ) { + // NOTE: we're storing bytes directly here! + let storage_key = StoredDealing::storage_key(epoch_id, dealer, dealing.index); + storage.set(&storage_key, dealing.data.as_slice()); + } + + pub(crate) fn read( + storage: &dyn Storage, + epoch_id: EpochId, + dealer: Dealer, + dealing_index: DealingIndex, + ) -> Option { + let storage_key = StoredDealing::storage_key(epoch_id, dealer, dealing_index); + let raw_dealing = storage.get(&storage_key); + raw_dealing.map(ContractSafeBytes) + } + + pub(crate) fn prefix_range<'a>( + storage: &'a dyn Storage, + prefix: (EpochId, Dealer), + start: Option>, + ) -> Box> + 'a> { + // replicate the behaviour of `Prefix::range_raw` but without the fallible deserialization on the data + // (since we're reading from the storage directly) + // and whilst combining the data on the spot + let prefix = Self::prefix(prefix); + + let mapped = range_with_prefix( + storage, + // note: Prefix's Deref implementation gives back its storage_prefix + &prefix, + start.map(|b| b.to_raw_bound()), + None, + Order::Ascending, + ) + .map(Self::deserialize_raw_dealing); + + Box::new(mapped) + } } - -pub fn save_dealing( - storage: &mut dyn Storage, - epoch_id: EpochId, - dealer: &Addr, - dealing: PartialContractDealing, -) { - // NOTE: we're storing bytes directly here! - let storage_key = DEALINGS.key((epoch_id, dealer, dealing.index)); - storage.set(&storage_key, dealing.data.as_slice()); -} - -// Note to whoever is looking at this implementation and is thinking of using something similar -// for storing small commitments/hashes of data on chain: -// If there's a lot of entries you want to store thinking, "oh, this digest is only 32 bytes, it's not that much", -// the default cosmwasm' serializer will bloat it to around ~100B. So you really don't want to be using -// Buckets/Maps, etc. for that purpose. Instead you want to use `storage` directly (look into the actual implementation of -// `Map` or `Bucket` to see what I mean. Instead of using the `to_vec` method on serde_json_wasm, you'd -// provide your data directly yourself. -// but you must be extremely careful when doing so, as you might end up overwriting some existing data -// if you don't choose your prefixes wisely. -// I didn't have to do it here as I'm storing relatively little data and after just base58-encoding -// my bytes, I was fine with the json overhead. - -// if TOTAL_DEALINGS is modified to anything other then current value (5), this part will also need -// to be modified -pub(crate) const DEALINGS_BYTES: [Map<'_, DealingKey<'_>, ContractSafeBytes>; TOTAL_DEALINGS] = [ - Map::new("dbyt1"), - Map::new("dbyt2"), - Map::new("dbyt3"), - Map::new("dbyt4"), - Map::new("dbyt5"), -]; diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index a68eb2a0b2..0c806ab2d6 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage as dealers_storage; -use crate::dealings::storage::{has_committed_dealing, save_dealing}; +use crate::dealings::storage::StoredDealing; use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; @@ -48,7 +48,7 @@ pub fn try_commit_dealings( } // check if this dealer has already committed this particular dealing - if has_committed_dealing(deps.storage, epoch.epoch_id, &info.sender, dealing.index) { + if StoredDealing::exists(deps.storage, epoch.epoch_id, &info.sender, dealing.index) { return Err(ContractError::DealingAlreadyCommitted { epoch_id: epoch.epoch_id, dealer: info.sender, @@ -56,7 +56,7 @@ pub fn try_commit_dealings( }); } - save_dealing(deps.storage, epoch.epoch_id, &info.sender, dealing); + StoredDealing::save(deps.storage, epoch.epoch_id, &info.sender, dealing); Ok(Response::new()) } diff --git a/contracts/coconut-dkg/src/epoch_state/transactions.rs b/contracts/coconut-dkg/src/epoch_state/transactions.rs index 35f0e75f51..02b7efc2be 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage::{current_dealers, past_dealers}; -use crate::dealings::storage::DEALINGS_BYTES; use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; @@ -17,21 +16,22 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> { .keys(storage, None, None, Order::Ascending) .collect::>()?; - for dealer_addr in dealers { - let details = current_dealers().load(storage, &dealer_addr)?; - for dealings in DEALINGS_BYTES { - let dealing_keys: Vec<_> = dealings - .keys(storage, None, None, Order::Ascending) - .flatten() - .collect(); - for key in dealing_keys { - dealings.remove(storage, &key); - } - } - current_dealers().remove(storage, &dealer_addr)?; - past_dealers().save(storage, &dealer_addr, &details)?; - } - Ok(()) + todo!() + // for dealer_addr in dealers { + // let details = current_dealers().load(storage, &dealer_addr)?; + // for dealings in DEALINGS_BYTES { + // let dealing_keys: Vec<_> = dealings + // .keys(storage, None, None, Order::Ascending) + // .flatten() + // .collect(); + // for key in dealing_keys { + // dealings.remove(storage, &key); + // } + // } + // current_dealers().remove(storage, &dealer_addr)?; + // past_dealers().save(storage, &dealer_addr, &details)?; + // } + // Ok(()) } fn dealers_still_active(