Storing and retrieving epoch dealings commitments

This commit is contained in:
Jędrzej Stuczyński
2022-05-19 17:36:49 +01:00
parent 34b1fc8989
commit dc2a386cd6
17 changed files with 301 additions and 25 deletions
Generated
+1
View File
@@ -733,6 +733,7 @@ name = "contracts-common"
version = "0.1.0"
dependencies = [
"blake3",
"bs58",
"cosmwasm-std",
"digest 0.10.3",
"schemars",
@@ -23,6 +23,8 @@ use crate::nymd::{
#[cfg(feature = "dkg")]
use crate::nymd::{traits::DkgClient, SigningCosmWasmClient};
use coconut_dkg_common::dealer::ContractDealingCommitment;
use coconut_dkg_common::types::EpochId;
#[cfg(feature = "dkg")]
use coconut_dkg_common::types::{BlacklistedDealer, DealerDetails};
#[cfg(feature = "nymd-client")]
@@ -846,4 +848,34 @@ where
Ok(dealers)
}
pub async fn get_all_nymd_epoch_dealings_commitments(
&self,
epoch_id: EpochId,
) -> Result<Vec<ContractDealingCommitment>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut commitments = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_epoch_dealings_commitments_paged(
epoch_id,
start_after.take(),
self.dealers_page_limit,
)
.await?;
commitments.append(&mut paged_response.commitments);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(commitments)
}
}
@@ -5,11 +5,11 @@ use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{cosmwasm_coin_to_cosmos_coin, Fee, NymdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use coconut_dkg_common::dealer::DealerDetailsResponse;
use coconut_dkg_common::dealer::{DealerDetailsResponse, PagedCommitmentsResponse};
use coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use coconut_dkg_common::types::{
BlacklistingResponse, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, Epoch,
BlacklistingResponse, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, Epoch, EpochId,
MinimumDepositResponse, PagedBlacklistingResponse, PagedDealerResponse,
};
use contracts_common::commitment::ContractSafeCommitment;
@@ -41,6 +41,12 @@ pub trait DkgClient {
async fn get_blacklisting(&self, dealer: String) -> Result<BlacklistingResponse, NymdError>;
async fn get_deposit_amount(&self) -> Result<MinimumDepositResponse, NymdError>;
async fn get_epoch_dealings_commitments_paged(
&self,
epoch: EpochId,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedCommitmentsResponse, NymdError>;
async fn register_dealer(
&self,
@@ -138,6 +144,22 @@ where
.await
}
async fn get_epoch_dealings_commitments_paged(
&self,
epoch: EpochId,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedCommitmentsResponse, NymdError> {
let request = DkgQueryMsg::GetEpochDealingsCommitments {
limit: page_limit,
start_after,
epoch,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address()?, &request)
.await
}
async fn register_dealer(
&self,
identity: EncodedEd25519PublicKey,
@@ -1,7 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{BlockHeight, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, NodeIndex};
use crate::types::{
BlockHeight, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, EpochId, NodeIndex,
};
use contracts_common::commitment::ContractSafeCommitment;
use cosmwasm_std::Addr;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
@@ -206,3 +209,43 @@ impl BlacklistingResponse {
.expect("dealer is not blacklisted")
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ContractDealingCommitment {
pub commitment: ContractSafeCommitment,
pub dealer: Addr,
pub epoch_id: EpochId,
}
impl ContractDealingCommitment {
pub fn new(commitment: ContractSafeCommitment, dealer: Addr, epoch_id: EpochId) -> Self {
ContractDealingCommitment {
commitment,
dealer,
epoch_id,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct PagedCommitmentsResponse {
pub commitments: Vec<ContractDealingCommitment>,
pub per_page: usize,
pub start_next_after: Option<Addr>,
}
impl PagedCommitmentsResponse {
pub fn new(
commitments: Vec<ContractDealingCommitment>,
per_page: usize,
start_next_after: Option<Addr>,
) -> Self {
PagedCommitmentsResponse {
commitments,
per_page,
start_next_after,
}
}
}
@@ -1,7 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{BlockHeight, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, Threshold};
use crate::types::{
BlockHeight, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, EpochId, Threshold,
};
use contracts_common::commitment::ContractSafeCommitment;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -57,6 +59,11 @@ pub enum QueryMsg {
dealer: String,
},
GetDepositAmount {},
GetEpochDealingsCommitments {
limit: Option<u32>,
start_after: Option<String>,
epoch: EpochId,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -7,6 +7,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bs58 = "0.4.0"
cosmwasm-std = "1.0.0-beta8"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -4,7 +4,7 @@
#[cfg(feature = "committable_trait")]
pub use digest::{Digest, Output};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
#[cfg(feature = "committable_trait")]
use std::marker::PhantomData;
@@ -16,9 +16,7 @@ pub const MAX_COMMITMENT_SIZE: usize = 128;
// TODO: if we are to use commitments for different types, it might make sense to introduce something like
// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait
#[derive(
Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, JsonSchema,
)]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)]
pub struct ContractSafeCommitment(Vec<u8>);
impl Deref for ContractSafeCommitment {
@@ -45,6 +43,35 @@ impl Display for ContractSafeCommitment {
}
}
// since cosmwasm stores everything with byte representation of stringified json, it's actually more efficient
// to serialize this as a string as opposed to keeping it as vector of bytes.
// for example vec![255,255] would have string representation of "[255,255]" and will be serialized to
// [91, 50, 53, 53, 44, 50, 53, 53, 93]. the equivalent base58 encoded string `"LUv"` will be serialized to
// [34, 76, 85, 118, 34]
//
// the difference between base58 and base64 is rather minimal and I've gone with base58 for consistency sake
impl Serialize for ContractSafeCommitment {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&bs58::encode(&self.0).into_string())
}
}
impl<'de> Deserialize<'de> for ContractSafeCommitment {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
let bytes = bs58::decode(&s)
.into_vec()
.map_err(serde::de::Error::custom)?;
Ok(ContractSafeCommitment(bytes))
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct UnsupportedCommitmentSize;
+1
View File
@@ -277,6 +277,7 @@ checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
name = "contracts-common"
version = "0.1.0"
dependencies = [
"bs58",
"cosmwasm-std",
"schemars",
"serde",
@@ -5,9 +5,8 @@ use crate::constants::{INVALID_ED25519_BLACKLISTING_EXPIRATION, MINIMUM_DEPOSIT}
use crate::dealers::storage as dealers_storage;
use crate::ContractError;
use coconut_dkg_common::types::{
BlacklistingReason, BlockHeight, ContractSafeCommitment, DealerDetails,
EncodedBTEPublicKeyWithProof, EncodedBTEPublicKeyWithProofRef, EncodedEd25519PublicKey,
EncodedEd25519PublicKeyRef,
BlacklistingReason, BlockHeight, DealerDetails, EncodedBTEPublicKeyWithProof,
EncodedBTEPublicKeyWithProofRef, EncodedEd25519PublicKey, EncodedEd25519PublicKeyRef,
};
use config::defaults::STAKE_DENOM;
use cosmwasm_std::{Addr, Coin, Deps, DepsMut, Env, MessageInfo, Response};
@@ -132,6 +131,9 @@ pub fn try_add_dealer(
owner_signature: String,
host: String,
) -> Result<Response, ContractError> {
// TODO: check if we're in correct epoch state
// check whether this sender is eligible to become a dealer
verify_dealer(deps.branch(), env.block.height, &info.sender)?;
@@ -190,13 +192,3 @@ pub fn try_add_dealer(
Ok(Response::new().set_data(node_index.to_be_bytes()))
}
pub fn try_commit_dealing(
mut deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
epoch_id: u32,
commitment: ContractSafeCommitment,
) -> Result<Response, ContractError> {
todo!()
}
@@ -1,2 +1,47 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealings::storage;
use crate::dealings::storage::DEALING_COMMITMENTS;
use coconut_dkg_common::dealer::{ContractDealingCommitment, PagedCommitmentsResponse};
use coconut_dkg_common::types::EpochId;
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
pub fn query_epoch_dealings_commitments_paged(
deps: Deps<'_>,
epoch: EpochId,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedCommitmentsResponse> {
let limit = limit
.unwrap_or(storage::COMMITMENTS_PAGE_DEFAULT_LIMIT)
.min(storage::COMMITMENTS_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 commitments = DEALING_COMMITMENTS
.prefix(epoch)
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| {
res.map(|(dealer, commitment)| {
ContractDealingCommitment::new(commitment, dealer, epoch)
})
})
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = commitments
.last()
.map(|commitment| commitment.dealer.clone());
Ok(PagedCommitmentsResponse::new(
commitments,
limit,
start_next_after,
))
}
@@ -1,2 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::{ContractSafeCommitment, EpochId};
use cosmwasm_std::Addr;
use cw_storage_plus::Map;
pub(crate) const COMMITMENTS_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const COMMITMENTS_PAGE_DEFAULT_LIMIT: u32 = 50;
type CommitmentKey<'a> = (EpochId, &'a Addr);
// 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.
pub(crate) const DEALING_COMMITMENTS: Map<'_, CommitmentKey<'_>, ContractSafeCommitment> =
Map::new("dcmt");
@@ -1,2 +1,47 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage as dealers_storage;
use crate::dealings::storage::DEALING_COMMITMENTS;
use crate::epoch::storage as epoch_storage;
use crate::ContractError;
use coconut_dkg_common::types::ContractSafeCommitment;
use cosmwasm_std::{DepsMut, MessageInfo, Response};
pub fn try_commit_dealing(
deps: DepsMut<'_>,
info: MessageInfo,
epoch_id: u32,
commitment: ContractSafeCommitment,
) -> Result<Response, ContractError> {
let current_epoch = epoch_storage::current_epoch(deps.storage)?;
// TODO: check if we're in correct epoch state (i.e. current_epoch.state)
// ensure the sender is a dealer for the current epoch
if dealers_storage::current_dealers()
.may_load(deps.storage, &info.sender)?
.is_none()
{
return Err(ContractError::NotADealer);
}
// make sure the dealer wants to submit commitment for THIS epoch
if current_epoch.id != epoch_id {
return Err(ContractError::MismatchedEpoch {
current: current_epoch.id,
request_for: epoch_id,
});
}
// check if this dealer has already committed to a dealing
// (we don't want to allow overwriting it as some receivers might already be using the commitment)
let storage_key = (epoch_id, &info.sender);
if DEALING_COMMITMENTS.has(deps.storage, storage_key) {
return Err(ContractError::AlreadyCommitted);
}
DEALING_COMMITMENTS.save(deps.storage, storage_key, &commitment)?;
Ok(Response::new())
}
+13 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::Blacklisting;
use coconut_dkg_common::types::{Blacklisting, EpochId};
use config::defaults::STAKE_DENOM;
use cosmwasm_std::{Addr, StdError, VerificationError};
use thiserror::Error;
@@ -55,4 +55,16 @@ pub enum ContractError {
// explicitly declare it so that when we ultimate do see it, it's gonna be more informative over "normal" panic
#[error("Somehow our validated address {address} is not using correct bech32 encoding")]
InvalidValidatedAddress { address: Addr },
#[error("This sender is not a dealer for the current epoch")]
NotADealer,
#[error("The current epoch does not match the one present in the request. Current: {current}, in request: {request_for}")]
MismatchedEpoch {
current: EpochId,
request_for: EpochId,
},
#[error("This dealer has already commited dealing for this epoch")]
AlreadyCommitted,
}
+13 -1
View File
@@ -6,6 +6,7 @@ use crate::dealers::queries::{
query_blacklisted_dealers_paged, query_blacklisting, query_current_dealers_paged,
query_dealer_details, query_past_dealers_paged,
};
use crate::dealings::queries::query_epoch_dealings_commitments_paged;
use crate::epoch::queries::query_current_epoch;
use crate::error::ContractError;
use coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
@@ -88,7 +89,7 @@ pub fn execute(
ExecuteMsg::CommitDealing {
epoch_id,
commitment,
} => dealers::transactions::try_commit_dealing(deps, env, info, epoch_id, commitment),
} => dealings::transactions::try_commit_dealing(deps, info, epoch_id, commitment),
ExecuteMsg::UnsafeResetAll { init_msg } => reset_contract_state(deps, env, info, init_msg),
}
}
@@ -151,6 +152,16 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
MINIMUM_DEPOSIT.u128(),
STAKE_DENOM,
)))?,
QueryMsg::GetEpochDealingsCommitments {
limit,
epoch,
start_after,
} => to_binary(&query_epoch_dealings_commitments_paged(
deps,
epoch,
start_after,
limit,
)?)?,
};
Ok(response)
@@ -174,6 +185,7 @@ mod tests {
let env = mock_env();
let msg = InstantiateMsg {
public_key_submission_end_height: env.block.height + 123,
system_threshold: None,
};
let info = mock_info("creator", &[]);
@@ -13,6 +13,7 @@ pub mod helpers {
let env = mock_env();
let msg = InstantiateMsg {
public_key_submission_end_height: env.block.height + 123,
system_threshold: None,
};
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env, info, msg).unwrap();
+1
View File
@@ -725,6 +725,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
name = "contracts-common"
version = "0.1.0"
dependencies = [
"bs58",
"cosmwasm-std",
"schemars",
"serde",
+13 -2
View File
@@ -5,10 +5,10 @@ use crate::config::Config;
use crate::rewarded_set_updater::error::RewardingError;
#[cfg(feature = "coconut")]
use async_trait::async_trait;
use coconut_dkg_common::dealer::DealerDetailsResponse;
use coconut_dkg_common::dealer::{ContractDealingCommitment, DealerDetailsResponse};
use coconut_dkg_common::types::{
BlacklistedDealer, BlacklistingResponse, Coin, DealerDetails, EncodedBTEPublicKeyWithProof,
EncodedEd25519PublicKey, Epoch as DkgEpoch,
EncodedEd25519PublicKey, Epoch as DkgEpoch, EpochId,
};
use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT};
use contracts_common::commitment::ContractSafeCommitment;
@@ -504,6 +504,17 @@ where
.map(|res| res.amount)
}
pub(crate) async fn get_epoch_dealings_commitments(
&self,
epoch_id: EpochId,
) -> Result<Vec<ContractDealingCommitment>, ValidatorClientError> {
self.0
.read()
.await
.get_all_nymd_epoch_dealings_commitments(epoch_id)
.await
}
pub(crate) async fn register_dealer(
&self,
identity: EncodedEd25519PublicKey,