Compare commits

...

26 Commits

Author SHA1 Message Date
Jędrzej Stuczyński 2a021b46ac fixed the return type of the query 2024-01-17 14:10:34 +00:00
Jędrzej Stuczyński c43cb657c6 added a query msg for the data 2024-01-17 14:10:34 +00:00
Jędrzej Stuczyński 6f66b377e2 added cw2 interface to dkg contract 2024-01-17 14:10:34 +00:00
Jędrzej Stuczyński 5ea67a9376 missing test fix 2024-01-17 14:10:08 +00:00
Jędrzej Stuczyński f566dffc5b fixed tests 2024-01-17 14:10:08 +00:00
Jędrzej Stuczyński 05f8beedad api support: submit ed25519 public key alongside the bte public key 2024-01-17 14:10:07 +00:00
Jędrzej Stuczyński 2fff051e28 submit ed25519 public key alongside the bte public key 2024-01-17 14:10:07 +00:00
Jędrzej Stuczyński 44bd70c546 reusing already generated dealings 2024-01-17 14:07:03 +00:00
Jędrzej Stuczyński 702354d127 client support 2024-01-17 14:07:02 +00:00
Jędrzej Stuczyński 56b1010d16 schema 2024-01-17 14:05:53 +00:00
Jędrzej Stuczyński 0632517f5d contract query for dealing status 2024-01-17 14:05:53 +00:00
Jędrzej Stuczyński bfcc5e9b41 fixed dealings query arguments 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński 337aacd442 more clippy 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński da5b7302b5 updated dkg schema 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński 7a53e86b40 clippy 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński 654dd07d19 removed old debug code 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński ef0765face ephemera contract fix 2024-01-17 13:59:05 +00:00
Jędrzej Stuczyński 3685b4681c fixes 2024-01-17 13:59:05 +00:00
Jędrzej Stuczyński 47e2af2caa reintroducing bug in deterministic_filter_dealers to make tests pass
yes, it's as bad as it sounds
2024-01-17 13:59:03 +00:00
Jędrzej Stuczyński 5be555d79f ability to query for dkg contract state 2024-01-17 13:58:26 +00:00
Jędrzej Stuczyński 8cc2b3167e client support 2024-01-17 13:56:33 +00:00
Jędrzej Stuczyński 4d95955961 renaming 2024-01-17 13:50:23 +00:00
Jędrzej Stuczyński e36ae4091f removed todos from commented tests 2024-01-17 13:50:23 +00:00
Jędrzej Stuczyński b566147f2f storage and query tests 2024-01-17 13:50:22 +00:00
Jędrzej Stuczyński 12242bb3c6 updated dealings queries 2024-01-17 13:50:22 +00:00
Jędrzej Stuczyński 0a0b0e80f4 storing dealings in new map 2024-01-17 13:50:21 +00:00
59 changed files with 2225 additions and 578 deletions
Generated
+2
View File
@@ -6309,6 +6309,8 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw2",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]
@@ -8,9 +8,15 @@ use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_coconut_dkg_common::{
dealer::{ContractDealing, DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse},
dealer::{
DealerDetailsResponse, DealingResponse, DealingStatusResponse, PagedDealerResponse,
PagedDealingsResponse,
},
msg::QueryMsg as DkgQueryMsg,
types::{DealerDetails, Epoch, EpochId, InitialReplacementData},
types::{
DealerDetails, DealingIndex, Epoch, EpochId, InitialReplacementData,
PartialContractDealing, State,
},
verification_key::{ContractVKShare, PagedVKSharesResponse},
};
use serde::Deserialize;
@@ -22,10 +28,16 @@ pub trait DkgQueryClient {
where
for<'a> T: Deserialize<'a>;
async fn get_state(&self) -> Result<State, NyxdError> {
let request = DkgQueryMsg::GetState {};
self.query_dkg_contract(request).await
}
async fn get_current_epoch(&self) -> Result<Epoch, NyxdError> {
let request = DkgQueryMsg::GetCurrentEpochState {};
self.query_dkg_contract(request).await
}
async fn get_current_epoch_threshold(&self) -> Result<Option<u64>, NyxdError> {
let request = DkgQueryMsg::GetCurrentEpochThreshold {};
self.query_dkg_contract(request).await
@@ -64,14 +76,46 @@ pub trait DkgQueryClient {
self.query_dkg_contract(request).await
}
async fn get_dealings_paged(
async fn get_dealing_status(
&self,
idx: u64,
start_after: Option<String>,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> Result<DealingStatusResponse, NyxdError> {
let request = DkgQueryMsg::GetDealingStatus {
epoch_id,
dealer,
dealing_index,
};
self.query_dkg_contract(request).await
}
async fn get_dealing(
&self,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> Result<DealingResponse, NyxdError> {
let request = DkgQueryMsg::GetDealing {
epoch_id,
dealer,
dealing_index,
};
self.query_dkg_contract(request).await
}
async fn get_dealer_dealings_paged(
&self,
epoch_id: EpochId,
dealer: &str,
start_after: Option<DealingIndex>,
limit: Option<u32>,
) -> Result<PagedDealingsResponse, NyxdError> {
let request = DkgQueryMsg::GetDealing {
idx,
let request = DkgQueryMsg::GetDealings {
epoch_id,
dealer: dealer.to_string(),
limit,
start_after,
};
@@ -91,6 +135,11 @@ pub trait DkgQueryClient {
};
self.query_dkg_contract(request).await
}
async fn get_contract_cw2_version(&self) -> Result<cw2::ContractVersion, NyxdError> {
self.query_dkg_contract(DkgQueryMsg::GetCW2ContractVersion {})
.await
}
}
// extension trait to the query client to deal with the paged queries
@@ -106,8 +155,12 @@ pub trait PagedDkgQueryClient: DkgQueryClient {
collect_paged!(self, get_past_dealers_paged, dealers)
}
async fn get_all_epoch_dealings(&self, idx: u64) -> Result<Vec<ContractDealing>, NyxdError> {
collect_paged!(self, get_dealings_paged, dealings, idx)
async fn get_all_dealer_dealings(
&self,
epoch_id: EpochId,
dealer: &str,
) -> Result<Vec<PartialContractDealing>, NyxdError> {
collect_paged!(self, get_dealer_dealings_paged, dealings, epoch_id, dealer)
}
async fn get_all_verification_key_shares(
@@ -151,6 +204,7 @@ mod tests {
msg: DkgQueryMsg,
) {
match msg {
DkgQueryMsg::GetState {} => client.get_state().ignore(),
DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(),
DkgQueryMsg::GetCurrentEpochThreshold {} => {
client.get_current_epoch_threshold().ignore()
@@ -165,11 +219,26 @@ mod tests {
DkgQueryMsg::GetPastDealers { limit, start_after } => {
client.get_past_dealers_paged(start_after, limit).ignore()
}
DkgQueryMsg::GetDealingStatus {
epoch_id,
dealer,
dealing_index,
} => client
.get_dealing_status(epoch_id, dealer, dealing_index)
.ignore(),
DkgQueryMsg::GetDealing {
idx,
epoch_id,
dealer,
dealing_index,
} => client.get_dealing(epoch_id, dealer, dealing_index).ignore(),
DkgQueryMsg::GetDealings {
epoch_id,
dealer,
limit,
start_after,
} => client.get_dealings_paged(idx, start_after, limit).ignore(),
} => client
.get_dealer_dealings_paged(epoch_id, &dealer, limit, start_after)
.ignore(),
DkgQueryMsg::GetVerificationKeys {
epoch_id,
limit,
@@ -177,6 +246,7 @@ mod tests {
} => client
.get_vk_shares_paged(epoch_id, start_after, limit)
.ignore(),
DkgQueryMsg::GetCW2ContractVersion {} => client.get_contract_cw2_version().ignore(),
};
}
}
@@ -10,9 +10,9 @@ use async_trait::async_trait;
use cosmrs::AccountId;
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
use nym_coconut_dkg_common::types::EncodedBTEPublicKeyWithProof;
use nym_coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, PartialContractDealing};
use nym_coconut_dkg_common::verification_key::VerificationKeyShare;
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_contracts_common::IdentityKey;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
@@ -42,12 +42,14 @@ pub trait DkgSigningClient {
async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
identity_key: IdentityKey,
announce_address: String,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::RegisterDealer {
bte_key_with_proof: bte_key,
identity_key,
announce_address,
resharing,
};
@@ -58,14 +60,11 @@ pub trait DkgSigningClient {
async fn submit_dealing_bytes(
&self,
dealing_bytes: ContractSafeBytes,
dealing: PartialContractDealing,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::CommitDealing {
dealing_bytes,
resharing,
};
let req = DkgExecuteMsg::CommitDealing { dealing, resharing };
self.execute_dkg_contract(fee, req, "dealing commitment".to_string(), vec![])
.await
@@ -148,16 +147,20 @@ mod tests {
match msg {
DkgExecuteMsg::RegisterDealer {
bte_key_with_proof,
identity_key,
announce_address,
resharing,
} => client
.register_dealer(bte_key_with_proof, announce_address, resharing, None)
.register_dealer(
bte_key_with_proof,
identity_key,
announce_address,
resharing,
None,
)
.ignore(),
DkgExecuteMsg::CommitDealing {
dealing_bytes,
resharing,
} => client
.submit_dealing_bytes(dealing_bytes, resharing, None)
DkgExecuteMsg::CommitDealing { dealing, resharing } => client
.submit_dealing_bytes(dealing, resharing, None)
.ignore(),
DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client
.submit_verification_key_share(share, resharing, None)
@@ -6,7 +6,7 @@ use log::{debug, info};
use std::str::FromStr;
use nym_coconut_dkg_common::msg::InstantiateMsg;
use nym_coconut_dkg_common::types::TimeConfiguration;
use nym_coconut_dkg_common::types::{TimeConfiguration, DEFAULT_DEALINGS};
use nym_validator_client::nyxd::AccountId;
#[derive(Debug, Parser)]
@@ -93,6 +93,7 @@ pub async fn generate(args: Args) {
multisig_addr: multisig_addr.to_string(),
time_configuration: Some(time_configuration),
mix_denom,
key_size: DEFAULT_DEALINGS as u32,
};
debug!("instantiate_msg: {:?}", instantiate_msg);
@@ -10,6 +10,8 @@ license.workspace = true
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
cw-utils = { workspace = true }
cw2 = { workspace = true }
cw4 = { workspace = true }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common" }
nym-multisig-contract-common = { path = "../multisig-contract" }
@@ -1,7 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// 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;
@@ -9,6 +12,7 @@ use cosmwasm_std::Addr;
pub struct DealerDetails {
pub address: Addr,
pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
pub ed25519_identity: String,
pub announce_address: String,
pub assigned_index: NodeIndex,
}
@@ -66,35 +70,50 @@ impl PagedDealerResponse {
}
#[cw_serde]
pub struct ContractDealing {
pub dealing: ContractSafeBytes,
pub struct DealingResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub dealing: Option<ContractDealing>,
}
impl ContractDealing {
pub fn new(dealing: ContractSafeBytes, dealer: Addr) -> Self {
ContractDealing { dealing, dealer }
}
#[cw_serde]
pub struct DealingStatusResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub dealing_submitted: bool,
}
#[cw_serde]
pub struct PagedDealingsResponse {
pub dealings: Vec<ContractDealing>,
pub per_page: usize,
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealings: Vec<PartialContractDealing>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<Addr>,
pub start_next_after: Option<DealingIndex>,
}
impl PagedDealingsResponse {
pub fn new(
dealings: Vec<ContractDealing>,
per_page: usize,
start_next_after: Option<Addr>,
epoch_id: EpochId,
dealer: Addr,
dealings: Vec<PartialContractDealing>,
start_next_after: Option<DealingIndex>,
) -> Self {
PagedDealingsResponse {
epoch_id,
dealer,
dealings,
per_page,
start_next_after,
}
}
@@ -1,17 +1,23 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{ContractSafeBytes, 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},
types::{Epoch, InitialReplacementData},
dealer::{
DealerDetailsResponse, DealingResponse, DealingStatusResponse, PagedDealerResponse,
PagedDealingsResponse,
},
types::{Epoch, InitialReplacementData, State},
verification_key::PagedVKSharesResponse,
};
use contracts_common::IdentityKey;
#[cfg(feature = "schema")]
use cosmwasm_schema::QueryResponses;
@@ -21,18 +27,22 @@ pub struct InstantiateMsg {
pub multisig_addr: String,
pub time_configuration: Option<TimeConfiguration>,
pub mix_denom: String,
/// Specifies the number of elements in the derived keys
pub key_size: u32,
}
#[cw_serde]
pub enum ExecuteMsg {
RegisterDealer {
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
identity_key: IdentityKey,
announce_address: String,
resharing: bool,
},
CommitDealing {
dealing_bytes: ContractSafeBytes,
dealing: PartialContractDealing,
resharing: bool,
},
@@ -55,6 +65,9 @@ pub enum ExecuteMsg {
#[cw_serde]
#[cfg_attr(feature = "schema", derive(QueryResponses))]
pub enum QueryMsg {
#[cfg_attr(feature = "schema", returns(State))]
GetState {},
#[cfg_attr(feature = "schema", returns(Epoch))]
GetCurrentEpochState {},
@@ -79,11 +92,26 @@ pub enum QueryMsg {
start_after: Option<String>,
},
#[cfg_attr(feature = "schema", returns(PagedDealingsResponse))]
#[cfg_attr(feature = "schema", returns(DealingStatusResponse))]
GetDealingStatus {
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
},
#[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<u32>,
start_after: Option<String>,
start_after: Option<DealingIndex>,
},
#[cfg_attr(feature = "schema", returns(PagedVKSharesResponse))]
@@ -92,6 +120,11 @@ pub enum QueryMsg {
limit: Option<u32>,
start_after: Option<String>,
},
/// Gets the stored contract version information that's required by the CW2 spec interface for migrations.
#[serde(rename = "get_cw2_contract_version")]
#[cfg_attr(feature = "schema", returns(cw2::ContractVersion))]
GetCW2ContractVersion {},
}
#[cw_serde]
@@ -8,14 +8,38 @@ use std::str::FromStr;
pub use crate::dealer::{DealerDetails, PagedDealerResponse};
pub use contracts_common::dealings::ContractSafeBytes;
pub use cosmwasm_std::{Addr, Coin, Timestamp};
pub use cw4::Cw4Contract;
pub type EncodedBTEPublicKeyWithProof = String;
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;
pub const DEFAULT_DEALINGS: usize = 2 + 2 + 1;
#[cw_serde]
pub struct PartialContractDealing {
pub index: DealingIndex,
pub data: ContractDealing,
}
impl PartialContractDealing {
pub fn new(index: DealingIndex, data: ContractDealing) -> Self {
PartialContractDealing { index, data }
}
}
impl From<(DealingIndex, ContractDealing)> for PartialContractDealing {
fn from(value: (DealingIndex, ContractDealing)) -> Self {
PartialContractDealing {
index: value.0,
data: value.1,
}
}
}
#[cw_serde]
pub struct InitialReplacementData {
@@ -73,6 +97,16 @@ impl Default for TimeConfiguration {
}
}
#[cw_serde]
pub struct State {
pub mix_denom: String,
pub multisig_addr: Addr,
pub group_addr: Cw4Contract,
/// Specifies the number of elements in the derived keys
pub key_size: u32,
}
#[cw_serde]
#[derive(Copy, Default)]
pub struct Epoch {
+2 -2
View File
@@ -14,8 +14,8 @@ use std::collections::HashMap;
use std::ops::Neg;
use zeroize::Zeroize;
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct Ciphertexts {
pub rr: [G1Projective; NUM_CHUNKS],
pub ss: [G1Projective; NUM_CHUNKS],
+2 -2
View File
@@ -67,8 +67,8 @@ impl<'a> Instance<'a> {
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct ProofOfChunking {
y0: G1Projective,
bb: Vec<G1Projective>,
+2 -2
View File
@@ -76,8 +76,8 @@ impl<'a> Instance<'a> {
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct ProofOfSecretSharing {
ff: G1Projective,
aa: G2Projective,
+1 -1
View File
@@ -82,7 +82,7 @@ impl RecoveredVerificationKeys {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct Dealing {
pub public_coefficients: PublicCoefficients,
+6 -3
View File
@@ -1220,12 +1220,13 @@ dependencies = [
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw2",
"cw4",
"cw4-group",
"lazy_static",
"nym-coconut-dkg-common",
"nym-group-contract-common",
"rusty-fork",
"semver",
"serde",
"thiserror",
]
@@ -1237,6 +1238,8 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw2",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]
@@ -1879,9 +1882,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.17"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
+1
View File
@@ -48,5 +48,6 @@ cw3 = "=1.1.0"
cw3-fixed-multisig = "=1.1.0"
cw4 = "=1.1.0"
cw20 = "=1.1.0"
semver = "1.0.21"
thiserror = "1.0.48"
+2 -1
View File
@@ -20,15 +20,16 @@ cosmwasm-std = { workspace = true }
cosmwasm-storage = { workspace = true }
cw-storage-plus = { workspace = true }
cw-controllers = { workspace = true }
cw2 = { workspace = true }
cw4 = { workspace = true }
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
semver = { workspace = true, default-features = false }
thiserror = { workspace = true }
[dev-dependencies]
cw-multi-test = { workspace = true }
cw4-group = { path = "../multisig/cw4-group" }
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
lazy_static = "1.4"
rusty-fork = "0.3"
[features]
+333 -40
View File
@@ -8,6 +8,7 @@
"type": "object",
"required": [
"group_addr",
"key_size",
"mix_denom",
"multisig_addr"
],
@@ -15,6 +16,12 @@
"group_addr": {
"type": "string"
},
"key_size": {
"description": "Specifies the number of elements in the derived keys",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_denom": {
"type": "string"
},
@@ -95,6 +102,7 @@
"required": [
"announce_address",
"bte_key_with_proof",
"identity_key",
"resharing"
],
"properties": {
@@ -104,6 +112,9 @@
"bte_key_with_proof": {
"type": "string"
},
"identity_key": {
"type": "string"
},
"resharing": {
"type": "boolean"
}
@@ -122,12 +133,12 @@
"commit_dealing": {
"type": "object",
"required": [
"dealing_bytes",
"dealing",
"resharing"
],
"properties": {
"dealing_bytes": {
"$ref": "#/definitions/ContractSafeBytes"
"dealing": {
"$ref": "#/definitions/PartialContractDealing"
},
"resharing": {
"type": "boolean"
@@ -227,6 +238,24 @@
"format": "uint8",
"minimum": 0.0
}
},
"PartialContractDealing": {
"type": "object",
"required": [
"data",
"index"
],
"properties": {
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
},
@@ -234,6 +263,19 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"oneOf": [
{
"type": "object",
"required": [
"get_state"
],
"properties": {
"get_state": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -352,6 +394,39 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealing_status"
],
"properties": {
"get_dealing_status": {
"type": "object",
"required": [
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -361,10 +436,47 @@
"get_dealing": {
"type": "object",
"required": [
"idx"
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"idx": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealings"
],
"properties": {
"get_dealings": {
"type": "object",
"required": [
"dealer",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
@@ -379,9 +491,11 @@
},
"start_after": {
"type": [
"string",
"integer",
"null"
]
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
@@ -425,6 +539,20 @@
}
},
"additionalProperties": false
},
{
"description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.",
"type": "object",
"required": [
"get_cw2_contract_version"
],
"properties": {
"get_cw2_contract_version": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
}
]
},
@@ -436,6 +564,26 @@
},
"sudo": null,
"responses": {
"get_c_w2_contract_version": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ContractVersion",
"type": "object",
"required": [
"contract",
"version"
],
"properties": {
"contract": {
"description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing",
"type": "string"
},
"version": {
"description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)",
"type": "string"
}
},
"additionalProperties": false
},
"get_current_dealers": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealerResponse",
@@ -480,7 +628,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -496,6 +645,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -744,7 +896,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -760,6 +913,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -776,34 +932,36 @@
},
"get_dealing": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealingsResponse",
"title": "DealingResponse",
"type": "object",
"required": [
"dealings",
"per_page"
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealings": {
"type": "array",
"items": {
"$ref": "#/definitions/ContractDealing"
}
"dealer": {
"$ref": "#/definitions/Addr"
},
"per_page": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"dealing": {
"anyOf": [
{
"$ref": "#/definitions/Addr"
"$ref": "#/definitions/ContractSafeBytes"
},
{
"type": "null"
}
]
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
@@ -812,21 +970,91 @@
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ContractDealing": {
"type": "object",
"required": [
"dealer",
"dealing"
"ContractSafeBytes": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
}
},
"get_dealing_status": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingStatusResponse",
"type": "object",
"required": [
"dealer",
"dealing_index",
"dealing_submitted",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"dealing_submitted": {
"type": "boolean"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
}
}
},
"get_dealings": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealingsResponse",
"type": "object",
"required": [
"dealer",
"dealings",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealings": {
"type": "array",
"items": {
"$ref": "#/definitions/PartialContractDealing"
}
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"type": [
"integer",
"null"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing": {
"$ref": "#/definitions/ContractSafeBytes"
}
},
"additionalProperties": false
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ContractSafeBytes": {
"type": "array",
@@ -835,6 +1063,24 @@
"format": "uint8",
"minimum": 0.0
}
},
"PartialContractDealing": {
"type": "object",
"required": [
"data",
"index"
],
"properties": {
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
},
@@ -921,7 +1167,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -937,12 +1184,58 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
}
}
},
"get_state": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "State",
"type": "object",
"required": [
"group_addr",
"key_size",
"mix_denom",
"multisig_addr"
],
"properties": {
"group_addr": {
"$ref": "#/definitions/Cw4Contract"
},
"key_size": {
"description": "Specifies the number of elements in the derived keys",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_denom": {
"type": "string"
},
"multisig_addr": {
"$ref": "#/definitions/Addr"
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Cw4Contract": {
"description": "Cw4Contract is a wrapper around Addr that provides a lot of helpers for working with cw4 contracts\n\nIf you wish to persist this, convert to Cw4CanonicalContract via .canonical()",
"allOf": [
{
"$ref": "#/definitions/Addr"
}
]
}
}
},
"get_verification_keys": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedVKSharesResponse",
+25 -3
View File
@@ -13,6 +13,7 @@
"required": [
"announce_address",
"bte_key_with_proof",
"identity_key",
"resharing"
],
"properties": {
@@ -22,6 +23,9 @@
"bte_key_with_proof": {
"type": "string"
},
"identity_key": {
"type": "string"
},
"resharing": {
"type": "boolean"
}
@@ -40,12 +44,12 @@
"commit_dealing": {
"type": "object",
"required": [
"dealing_bytes",
"dealing",
"resharing"
],
"properties": {
"dealing_bytes": {
"$ref": "#/definitions/ContractSafeBytes"
"dealing": {
"$ref": "#/definitions/PartialContractDealing"
},
"resharing": {
"type": "boolean"
@@ -145,6 +149,24 @@
"format": "uint8",
"minimum": 0.0
}
},
"PartialContractDealing": {
"type": "object",
"required": [
"data",
"index"
],
"properties": {
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
}
@@ -4,6 +4,7 @@
"type": "object",
"required": [
"group_addr",
"key_size",
"mix_denom",
"multisig_addr"
],
@@ -11,6 +12,12 @@
"group_addr": {
"type": "string"
},
"key_size": {
"description": "Specifies the number of elements in the derived keys",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_denom": {
"type": "string"
},
+103 -4
View File
@@ -2,6 +2,19 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"oneOf": [
{
"type": "object",
"required": [
"get_state"
],
"properties": {
"get_state": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -120,6 +133,39 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealing_status"
],
"properties": {
"get_dealing_status": {
"type": "object",
"required": [
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -129,10 +175,47 @@
"get_dealing": {
"type": "object",
"required": [
"idx"
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"idx": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealings"
],
"properties": {
"get_dealings": {
"type": "object",
"required": [
"dealer",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
@@ -147,9 +230,11 @@
},
"start_after": {
"type": [
"string",
"integer",
"null"
]
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
@@ -193,6 +278,20 @@
}
},
"additionalProperties": false
},
{
"description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.",
"type": "object",
"required": [
"get_cw2_contract_version"
],
"properties": {
"get_cw2_contract_version": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
}
]
}
@@ -0,0 +1,20 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ContractVersion",
"type": "object",
"required": [
"contract",
"version"
],
"properties": {
"contract": {
"description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing",
"type": "string"
},
"version": {
"description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)",
"type": "string"
}
},
"additionalProperties": false
}
@@ -42,7 +42,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -58,6 +59,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -32,7 +32,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -48,6 +49,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -1,33 +1,35 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealingsResponse",
"title": "DealingResponse",
"type": "object",
"required": [
"dealings",
"per_page"
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealings": {
"type": "array",
"items": {
"$ref": "#/definitions/ContractDealing"
}
"dealer": {
"$ref": "#/definitions/Addr"
},
"per_page": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"dealing": {
"anyOf": [
{
"$ref": "#/definitions/Addr"
"$ref": "#/definitions/ContractSafeBytes"
},
{
"type": "null"
}
]
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
@@ -36,22 +38,6 @@
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ContractDealing": {
"type": "object",
"required": [
"dealer",
"dealing"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing": {
"$ref": "#/definitions/ContractSafeBytes"
}
},
"additionalProperties": false
},
"ContractSafeBytes": {
"type": "array",
"items": {
@@ -0,0 +1,36 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingStatusResponse",
"type": "object",
"required": [
"dealer",
"dealing_index",
"dealing_submitted",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"dealing_submitted": {
"type": "boolean"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
}
}
}
@@ -0,0 +1,68 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealingsResponse",
"type": "object",
"required": [
"dealer",
"dealings",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealings": {
"type": "array",
"items": {
"$ref": "#/definitions/PartialContractDealing"
}
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ContractSafeBytes": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
},
"PartialContractDealing": {
"type": "object",
"required": [
"data",
"index"
],
"properties": {
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
}
@@ -42,7 +42,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -58,6 +59,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "State",
"type": "object",
"required": [
"group_addr",
"key_size",
"mix_denom",
"multisig_addr"
],
"properties": {
"group_addr": {
"$ref": "#/definitions/Cw4Contract"
},
"key_size": {
"description": "Specifies the number of elements in the derived keys",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_denom": {
"type": "string"
},
"multisig_addr": {
"$ref": "#/definitions/Addr"
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Cw4Contract": {
"description": "Cw4Contract is a wrapper around Addr that provides a lot of helpers for working with cw4 contracts\n\nIf you wish to persist this, convert to Cw4CanonicalContract via .canonical()",
"allOf": [
{
"$ref": "#/definitions/Addr"
}
]
}
}
}
+77 -13
View File
@@ -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_dealing_status, 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,
@@ -13,7 +13,8 @@ use crate::epoch_state::queries::{
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::{advance_epoch_state, try_surpassed_threshold};
use crate::error::ContractError;
use crate::state::{State, MULTISIG, STATE};
use crate::state::queries::query_state;
use crate::state::storage::{MULTISIG, STATE};
use crate::verification_key_shares::queries::query_vk_shares_paged;
use crate::verification_key_shares::transactions::try_commit_verification_key_share;
use crate::verification_key_shares::transactions::try_verify_verification_key_share;
@@ -22,7 +23,11 @@ use cosmwasm_std::{
};
use cw4::Cw4Contract;
use nym_coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use nym_coconut_dkg_common::types::{Epoch, EpochState};
use nym_coconut_dkg_common::types::{Epoch, EpochState, State};
use semver::Version;
const CONTRACT_NAME: &str = "crate:nym-coconut-dkg";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
/// Instantiate the contract.
///
@@ -39,7 +44,7 @@ pub fn instantiate(
let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?;
MULTISIG.set(deps.branch(), Some(multisig_addr.clone()))?;
let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| {
let group_addr = Cw4Contract::new(deps.api.addr_validate(&msg.group_addr).map_err(|_| {
ContractError::InvalidGroup {
addr: msg.group_addr.clone(),
}
@@ -49,6 +54,7 @@ pub fn instantiate(
group_addr,
multisig_addr,
mix_denom: msg.mix_denom,
key_size: msg.key_size,
};
STATE.save(deps.storage, &state)?;
@@ -62,6 +68,8 @@ pub fn instantiate(
),
)?;
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Ok(Response::default())
}
@@ -76,13 +84,20 @@ pub fn execute(
match msg {
ExecuteMsg::RegisterDealer {
bte_key_with_proof,
identity_key,
announce_address,
resharing,
} => try_add_dealer(deps, info, bte_key_with_proof, announce_address, resharing),
ExecuteMsg::CommitDealing {
dealing_bytes,
} => try_add_dealer(
deps,
info,
bte_key_with_proof,
identity_key,
announce_address,
resharing,
} => try_commit_dealings(deps, info, dealing_bytes, resharing),
),
ExecuteMsg::CommitDealing { dealing, resharing } => {
try_commit_dealings(deps, info, dealing, resharing)
}
ExecuteMsg::CommitVerificationKeyShare { share, resharing } => {
try_commit_verification_key_share(deps, env, info, share, resharing)
}
@@ -97,6 +112,7 @@ pub fn execute(
#[entry_point]
pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let response = match msg {
QueryMsg::GetState {} => to_binary(&query_state(deps.storage)?)?,
QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch(deps.storage)?)?,
QueryMsg::GetCurrentEpochThreshold {} => {
to_binary(&query_current_epoch_threshold(deps.storage)?)?
@@ -111,24 +127,67 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
QueryMsg::GetPastDealers { limit, start_after } => {
to_binary(&query_past_dealers_paged(deps, start_after, limit)?)?
}
QueryMsg::GetDealingStatus {
epoch_id,
dealer,
dealing_index,
} => to_binary(&query_dealing_status(
deps,
epoch_id,
dealer,
dealing_index,
)?)?,
QueryMsg::GetDealing {
idx,
epoch_id,
dealer,
dealing_index,
} => 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,
start_after,
limit,
)?)?,
QueryMsg::GetVerificationKeys {
epoch_id,
limit,
start_after,
} => to_binary(&query_vk_shares_paged(deps, epoch_id, start_after, limit)?)?,
QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?)?,
};
Ok(response)
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
fn parse_semver(raw: &str) -> Result<Version, ContractError> {
raw.parse()
.map_err(|error: semver::Error| ContractError::SemVerFailure {
value: CONTRACT_VERSION.to_string(),
error_message: error.to_string(),
})
}
// Note: don't remove this particular bit of code as we have to ALWAYS check whether we have to
// update the stored version
let build_version: Version = parse_semver(CONTRACT_VERSION)?;
let stored_version: Version = parse_semver(&cw2::get_contract_version(deps.storage)?.version)?;
if stored_version < build_version {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
// If state structure changed in any contract version in the way migration is needed, it
// should occur here, for example anything from `crate::queued_migrations::`
}
Ok(Response::new())
}
#[cfg(test)]
@@ -141,7 +200,7 @@ mod tests {
use cw4::Member;
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
use nym_coconut_dkg_common::msg::ExecuteMsg::RegisterDealer;
use nym_coconut_dkg_common::types::NodeIndex;
use nym_coconut_dkg_common::types::{NodeIndex, DEFAULT_DEALINGS};
use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg;
fn instantiate_with_group(app: &mut App, members: &[Addr]) -> Addr {
@@ -178,6 +237,7 @@ mod tests {
multisig_addr: MULTISIG_CONTRACT.to_string(),
time_configuration: None,
mix_denom: TEST_MIX_DENOM.to_string(),
key_size: DEFAULT_DEALINGS as u32,
};
app.instantiate_contract(
coconut_dkg_code_id,
@@ -213,6 +273,7 @@ mod tests {
multisig_addr: "multisig_addr".to_string(),
time_configuration: None,
mix_denom: "nym".to_string(),
key_size: 5,
};
let info = mock_info("creator", &[]);
@@ -242,6 +303,7 @@ mod tests {
coconut_dkg_contract_addr.clone(),
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
identity_key: "identity".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
@@ -256,6 +318,7 @@ mod tests {
coconut_dkg_contract_addr.clone(),
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
identity_key: "identity".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
@@ -272,6 +335,7 @@ mod tests {
coconut_dkg_contract_addr,
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
identity_key: "identity".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
@@ -5,7 +5,7 @@ use crate::dealers::storage as dealers_storage;
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::STATE;
use crate::state::storage::STATE;
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
use nym_coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState};
@@ -38,6 +38,7 @@ pub fn try_add_dealer(
mut deps: DepsMut<'_>,
info: MessageInfo,
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
identity_key: String,
announce_address: String,
resharing: bool,
) -> Result<Response, ContractError> {
@@ -65,6 +66,7 @@ pub fn try_add_dealer(
let dealer_details = DealerDetails {
address: info.sender.clone(),
bte_public_key_with_proof: bte_key_with_proof,
ed25519_identity: identity_key,
announce_address,
assigned_index: node_index,
};
@@ -141,6 +143,7 @@ pub(crate) mod tests {
let mut env = mock_env();
let info = mock_info(owner.as_str(), &[]);
let bte_key_with_proof = String::from("bte_key_with_proof");
let identity = String::from("identity");
let announce_address = String::from("localhost:8000");
env.block.time = env
@@ -155,6 +158,7 @@ pub(crate) mod tests {
deps.as_mut(),
info,
bte_key_with_proof,
identity,
announce_address,
false,
)
+253 -142
View File
@@ -2,44 +2,74 @@
// 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, DealingStatusResponse, PagedDealingsResponse,
};
use nym_coconut_dkg_common::types::{DealingIndex, EpochId};
// this does almost the same as query_dealing but doesn't return the actual dealing to make it easier on the validator
// so it wouldn't need to deal with the deserialization
pub fn query_dealing_status(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> StdResult<DealingStatusResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let dealing_submitted = StoredDealing::exists(deps.storage, epoch_id, &dealer, dealing_index);
Ok(DealingStatusResponse {
epoch_id,
dealer,
dealing_index,
dealing_submitted,
})
}
pub fn query_dealing(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> StdResult<DealingResponse> {
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<String>,
epoch_id: EpochId,
dealer: String,
start_after: Option<DealingIndex>,
limit: Option<u32>,
) -> StdResult<PagedDealingsResponse> {
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::<StdResult<Vec<_>>>()?;
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,
))
}
@@ -48,148 +78,229 @@ pub fn query_dealings_paged(
pub(crate) mod tests {
use super::*;
use crate::dealings::storage::{DEALINGS_PAGE_DEFAULT_LIMIT, DEALINGS_PAGE_MAX_LIMIT};
use crate::support::tests::fixtures::dealing_bytes_fixture;
use crate::support::tests::fixtures::{dealing_bytes_fixture, partial_dealing_fixture};
use crate::support::tests::helpers::init_contract;
use cosmwasm_std::{Addr, DepsMut};
use nym_coconut_dkg_common::types::PartialContractDealing;
fn fill_dealings(deps: DepsMut<'_>, size: usize) {
for n in 0..size {
let dealing_share = dealing_bytes_fixture();
let sender = Addr::unchecked(format!("owner{}", n));
(0..TOTAL_DEALINGS).for_each(|idx| {
DEALINGS_BYTES[idx]
.save(deps.storage, &sender, &dealing_share)
.unwrap();
});
fn fill_dealings(deps: DepsMut<'_>, epoch: EpochId, dealers: usize, key_size: u32) {
for i in 0..dealers {
let dealer = Addr::unchecked(format!("dealer{i}"));
for dealing_index in 0..key_size {
StoredDealing::save(
deps.storage,
epoch,
&dealer,
PartialContractDealing {
index: dealing_index,
data: dealing_bytes_fixture(),
},
)
}
}
}
#[test]
fn empty_on_bad_idx() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 1000);
for idx in TOTAL_DEALINGS as u64..100 * TOTAL_DEALINGS as u64 {
let page1 = query_dealings_paged(deps.as_ref(), idx, None, None).unwrap();
assert_eq!(0, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_empty_on_init() {
let deps = init_contract();
for idx in 0..TOTAL_DEALINGS as u64 {
let response = query_dealings_paged(deps.as_ref(), idx, None, Option::from(2)).unwrap();
assert_eq!(0, response.dealings.len());
}
}
#[test]
fn dealings_paged_retrieval_obeys_limits() {
let mut deps = init_contract();
let limit = 2;
fill_dealings(deps.as_mut(), 1000);
for idx in 0..TOTAL_DEALINGS as u64 {
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_paged_retrieval_has_default_limit() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 1000);
for idx in 0..TOTAL_DEALINGS as u64 {
// query without explicitly setting a limit
let page1 = query_dealings_paged(deps.as_ref(), idx, None, None).unwrap();
assert_eq!(DEALINGS_PAGE_DEFAULT_LIMIT, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_paged_retrieval_has_max_limit() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 1000);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * DEALINGS_PAGE_MAX_LIMIT;
for idx in 0..TOTAL_DEALINGS as u64 {
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
let expected_limit = DEALINGS_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_pagination_works() {
fn test_query_dealing() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 1);
let bad_address = "FOOMP".to_string();
assert!(query_dealing(deps.as_ref(), 0, bad_address, 0).is_err());
let per_page = 2;
let empty = query_dealing(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
assert_eq!(empty.epoch_id, 0);
assert_eq!(empty.dealing_index, 0);
assert_eq!(empty.dealer, Addr::unchecked("foo"));
assert!(empty.dealing.is_none());
for idx in 0..TOTAL_DEALINGS as u64 {
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap();
// insert the dealing
let dealing = partial_dealing_fixture();
StoredDealing::save(
deps.as_mut().storage,
0,
&Addr::unchecked("foo"),
dealing.clone(),
);
// page should have 1 result on it
assert_eq!(1, page1.dealings.len());
let retrieved = query_dealing(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
assert_eq!(retrieved.epoch_id, 0);
assert_eq!(retrieved.dealing_index, dealing.index);
assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
assert_eq!(retrieved.dealing.unwrap(), dealing.data);
}
#[test]
fn test_query_dealing_status() {
let mut deps = init_contract();
let bad_address = "FOOMP".to_string();
assert!(query_dealing_status(deps.as_ref(), 0, bad_address, 0).is_err());
let empty = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
assert_eq!(empty.epoch_id, 0);
assert_eq!(empty.dealing_index, 0);
assert_eq!(empty.dealer, Addr::unchecked("foo"));
assert!(!empty.dealing_submitted);
// insert the dealing
let dealing = partial_dealing_fixture();
StoredDealing::save(
deps.as_mut().storage,
0,
&Addr::unchecked("foo"),
dealing.clone(),
);
let retrieved = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
assert_eq!(retrieved.epoch_id, 0);
assert_eq!(retrieved.dealing_index, dealing.index);
assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
assert!(retrieved.dealing_submitted)
}
#[cfg(test)]
mod query_dealings {
use super::*;
use nym_coconut_dkg_common::types::DEFAULT_DEALINGS;
#[test]
fn dealings_empty_on_init() {
let deps = init_contract();
let all_dealings = StoredDealing::unchecked_all_entries(&deps.storage);
assert!(all_dealings.is_empty())
}
// save another
fill_dealings(deps.as_mut(), 2);
#[test]
fn dealings_paged_retrieval_obeys_limits() {
let mut deps = init_contract();
let limit = 2;
fill_dealings(deps.as_mut(), 0, 10, DEFAULT_DEALINGS as u32);
for idx in 0..TOTAL_DEALINGS as u64 {
// page1 should have 2 results on it
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.dealings.len());
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
let page1 =
query_dealings_paged(deps.as_ref(), 0, dealer, None, Option::from(limit))
.unwrap();
assert_eq!(limit, page1.dealings.len() as u32);
}
}
fill_dealings(deps.as_mut(), 3);
#[test]
fn dealings_paged_retrieval_has_default_limit() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 0, 10, DEFAULT_DEALINGS as u32);
for idx in 0..TOTAL_DEALINGS as u64 {
// page1 still has 2 results
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.dealings.len());
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
// query without explicitly setting a limit
let page1 = query_dealings_paged(deps.as_ref(), 0, dealer, None, None).unwrap();
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_dealings_paged(
deps.as_ref(),
idx,
Option::from(start_after.to_string()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.dealings.len());
assert_eq!(DEALINGS_PAGE_DEFAULT_LIMIT, page1.dealings.len() as u32);
}
}
fill_dealings(deps.as_mut(), 4);
#[test]
fn dealings_paged_retrieval_has_max_limit() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 0, 10, DEFAULT_DEALINGS as u32);
for idx in 0..TOTAL_DEALINGS as u64 {
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap();
let start_after = page1.start_next_after.unwrap();
let page2 = query_dealings_paged(
deps.as_ref(),
idx,
Option::from(start_after.to_string()),
Option::from(per_page),
)
.unwrap();
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * DEALINGS_PAGE_MAX_LIMIT;
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
let page1 =
query_dealings_paged(deps.as_ref(), 0, dealer, None, Option::from(crazy_limit))
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.dealings.len());
// we default to a decent sized upper bound instead
let expected_limit = DEALINGS_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_pagination_works() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 0, 10, 1);
let per_page = 2;
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
let page1 =
query_dealings_paged(deps.as_ref(), 0, dealer, None, Option::from(per_page))
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.dealings.len());
}
// save another
fill_dealings(deps.as_mut(), 1, 10, 2);
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
// page1 should have 2 results on it
let page1 =
query_dealings_paged(deps.as_ref(), 1, dealer, None, Option::from(per_page))
.unwrap();
assert_eq!(2, page1.dealings.len());
}
fill_dealings(deps.as_mut(), 3, 10, 3);
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
// page1 still has 2 results
let page1 = query_dealings_paged(
deps.as_ref(),
3,
dealer.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.dealings.len());
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_dealings_paged(
deps.as_ref(),
3,
dealer,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.dealings.len());
}
fill_dealings(deps.as_mut(), 4, 10, 4);
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
let page1 = query_dealings_paged(
deps.as_ref(),
4,
dealer.clone(),
None,
Option::from(per_page),
)
.unwrap();
let start_after = page1.start_next_after.unwrap();
let page2 = query_dealings_paged(
deps.as_ref(),
4,
dealer,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.dealings.len());
}
}
}
}
+284 -24
View File
@@ -1,33 +1,293 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use cw_storage_plus::Map;
use nym_coconut_dkg_common::types::{ContractSafeBytes, TOTAL_DEALINGS};
use cosmwasm_std::{Addr, Order, Record, StdResult, Storage};
use cw_storage_plus::{Bound, Key, KeyDeserialize, Path, Prefix, Prefixer, PrimaryKey};
use nym_coconut_dkg_common::types::{
ContractDealing, ContractSafeBytes, DealingIndex, EpochId, PartialContractDealing,
};
pub(crate) const DEALINGS_PAGE_MAX_LIMIT: u32 = 2;
pub(crate) const DEALINGS_PAGE_DEFAULT_LIMIT: u32 = 1;
type DealingKey<'a> = &'a Addr;
type Dealer<'a> = &'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.
// 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 we can't use the `Map` type,
// thus make sure you always use the below methods for using the storage!
// 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"),
];
pub(crate) struct StoredDealing;
impl StoredDealing {
const NAMESPACE: &'static [u8] = b"dealing";
fn deserialize_dealing_record(kv: Record) -> StdResult<(DealingIndex, ContractDealing)> {
let (k, v) = kv;
let index = <DealingIndex as KeyDeserialize>::from_vec(k)?;
let data = ContractSafeBytes(v);
Ok((index, data))
}
fn storage_key(
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> Path<Vec<u8>> {
// 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::<Vec<_>>(),
)
}
fn prefix(prefix: (EpochId, Dealer)) -> Prefix<DealingIndex, ContractSafeBytes, DealingIndex> {
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
|_, _, kv| Self::deserialize_dealing_record(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<ContractDealing> {
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<Bound<DealingIndex>>,
) -> impl Iterator<Item = StdResult<PartialContractDealing>> + 'a {
Self::prefix(prefix)
.range(storage, start, None, Order::Ascending)
.map(|maybe_record| maybe_record.map(Into::into))
}
// iterate over all values, only to be used in tests due to the amount of data being returned
#[cfg(test)]
pub(crate) fn unchecked_all_entries(
storage: &dyn Storage,
) -> Vec<((EpochId, Addr, DealingIndex), ContractDealing)> {
type StorageKey<'a> = (EpochId, Dealer<'a>, DealingIndex);
let empty_prefix: Prefix<StorageKey, ContractDealing, StorageKey> =
Prefix::with_deserialization_functions(
Self::NAMESPACE,
&[],
&[],
|_, _, kv| StorageKey::from_vec(kv.0).map(|kt| (kt, ContractSafeBytes(kv.1))),
|_, _, _| unimplemented!(),
);
empty_prefix
.range(storage, None, None, Order::Ascending)
.collect::<StdResult<_>>()
.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::helpers::init_contract;
use std::collections::HashMap;
fn dealing_data(
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> ContractDealing {
ContractSafeBytes(
format!("{epoch_id},{dealer},{dealing_index}")
.as_bytes()
.to_vec(),
)
}
#[test]
fn saving_dealing() {
let mut deps = init_contract();
// make sure to check all combinations of epoch id, dealer address and dealing index to ensure nothing overlaps
let epochs = [54, 423, 754];
let dealers = [
Addr::unchecked("dealer1"),
Addr::unchecked("dealer2"),
Addr::unchecked("dealer3"),
Addr::unchecked("dealer4"),
Addr::unchecked("dealer5"),
];
let dealing_indices = [0, 1, 2, 3, 4, 5, 6, 7];
for epoch_id in &epochs {
for dealer in &dealers {
for dealing_index in &dealing_indices {
assert!(!StoredDealing::exists(
&deps.storage,
*epoch_id,
dealer,
*dealing_index
));
StoredDealing::save(
deps.as_mut().storage,
*epoch_id,
dealer,
PartialContractDealing {
index: *dealing_index,
data: dealing_data(*epoch_id, dealer, *dealing_index),
},
)
}
}
}
let all: HashMap<_, _> = StoredDealing::unchecked_all_entries(&deps.storage)
.into_iter()
.collect();
assert_eq!(
all.len(),
epochs.len() * dealers.len() * dealing_indices.len()
);
for epoch_id in &epochs {
for dealer in &dealers {
for dealing_index in &dealing_indices {
assert!(StoredDealing::exists(
&deps.storage,
*epoch_id,
dealer,
*dealing_index
));
let content =
StoredDealing::read(&deps.storage, *epoch_id, dealer, *dealing_index)
.unwrap();
let expected = dealing_data(*epoch_id, dealer, *dealing_index);
assert_eq!(expected, content);
assert_eq!(
&expected,
all.get(&(*epoch_id, dealer.clone(), *dealing_index))
.unwrap()
);
}
}
}
}
#[test]
fn iterating_over_dealings() {
let mut deps = init_contract();
let epochs = [54, 423, 754];
let dealers = [
Addr::unchecked("dealer1"),
Addr::unchecked("dealer2"),
Addr::unchecked("dealer3"),
Addr::unchecked("dealer4"),
Addr::unchecked("dealer5"),
];
let dealing_indices = [0, 1, 2, 3, 4, 5, 6, 7];
for epoch_id in &epochs {
for dealer in &dealers {
for dealing_index in &dealing_indices {
StoredDealing::save(
deps.as_mut().storage,
*epoch_id,
dealer,
PartialContractDealing {
index: *dealing_index,
data: dealing_data(*epoch_id, dealer, *dealing_index),
},
)
}
}
}
// remember, we're not testing the iterator implementation
// nothing under epoch 0
let dealings =
StoredDealing::prefix_range(&deps.storage, (0, &dealers[0]), None).collect::<Vec<_>>();
assert!(dealings.is_empty());
// nothing for dealer "foo"
let foo = Addr::unchecked("foo");
let dealings =
StoredDealing::prefix_range(&deps.storage, (epochs[0], &foo), None).collect::<Vec<_>>();
assert!(dealings.is_empty());
let all = StoredDealing::prefix_range(&deps.storage, (epochs[0], &dealers[0]), None)
.collect::<Vec<_>>();
assert_eq!(all.len(), dealing_indices.len());
for (i, dealing) in all.iter().enumerate() {
let expected = dealing_data(epochs[0], &dealers[0], dealing_indices[i]);
assert_eq!(expected, dealing.as_ref().unwrap().data);
assert_eq!(dealing_indices[i], dealing.as_ref().unwrap().index);
}
// for sanity sake, check another dealer with different epoch
let all_other = StoredDealing::prefix_range(&deps.storage, (epochs[2], &dealers[3]), None)
.collect::<Vec<_>>();
assert_eq!(all_other.len(), dealing_indices.len());
for (i, dealing) in all_other.iter().enumerate() {
let expected = dealing_data(epochs[2], &dealers[3], dealing_indices[i]);
assert_eq!(expected, dealing.as_ref().unwrap().data);
assert_eq!(dealing_indices[i], dealing.as_ref().unwrap().index);
}
let without_first = StoredDealing::prefix_range(
&deps.storage,
(epochs[0], &dealers[0]),
Some(Bound::exclusive(dealing_indices[0])),
)
.collect::<Vec<_>>();
assert_eq!(&all[1..], without_first);
let mid = StoredDealing::prefix_range(
&deps.storage,
(epochs[0], &dealers[0]),
Some(Bound::inclusive(dealing_indices[3])),
)
.collect::<Vec<_>>();
assert_eq!(&all[3..], mid);
}
}
@@ -2,17 +2,18 @@
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage as dealers_storage;
use crate::dealings::storage::DEALINGS_BYTES;
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
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;
use crate::state::storage::STATE;
use cosmwasm_std::{DepsMut, MessageInfo, Response};
use nym_coconut_dkg_common::types::{ContractSafeBytes, EpochState};
use nym_coconut_dkg_common::types::{EpochState, PartialContractDealing};
pub fn try_commit_dealings(
deps: DepsMut<'_>,
info: MessageInfo,
dealing_bytes: ContractSafeBytes,
dealing: PartialContractDealing,
resharing: bool,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::DealingExchange { resharing })?;
@@ -32,18 +33,32 @@ pub fn try_commit_dealings(
return Err(ContractError::NotAnInitialDealer);
}
// check if this dealer has already committed to all dealings
// (we don't want to allow overwriting anything)
for dealings in DEALINGS_BYTES {
if !dealings.has(deps.storage, &info.sender) {
dealings.save(deps.storage, &info.sender, &dealing_bytes)?;
return Ok(Response::default());
}
let state = STATE.load(deps.storage)?;
let epoch = CURRENT_EPOCH.load(deps.storage)?;
// check if the index is in range without doing expensive storage reads
// note: dealing indexing starts from 0
if dealing.index >= state.key_size {
return Err(ContractError::DealingOutOfRange {
epoch_id: epoch.epoch_id,
dealer: info.sender,
index: dealing.index,
key_size: state.key_size,
});
}
Err(ContractError::AlreadyCommitted {
commitment: String::from("dealing"),
})
// check if this dealer has already committed this particular dealing
if StoredDealing::exists(deps.storage, epoch.epoch_id, &info.sender, dealing.index) {
return Err(ContractError::DealingAlreadyCommitted {
epoch_id: epoch.epoch_id,
dealer: info.sender,
index: dealing.index,
});
}
StoredDealing::save(deps.storage, epoch.epoch_id, &info.sender, dealing);
Ok(Response::new())
}
#[cfg(test)]
@@ -51,13 +66,15 @@ pub(crate) mod tests {
use super::*;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::advance_epoch_state;
use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture};
use crate::support::tests::fixtures::{dealer_details_fixture, partial_dealing_fixture};
use crate::support::tests::helpers;
use crate::support::tests::helpers::add_fixture_dealer;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
use nym_coconut_dkg_common::types::{
ContractSafeBytes, InitialReplacementData, TimeConfiguration, DEFAULT_DEALINGS,
};
#[test]
fn invalid_commit_dealing() {
@@ -65,10 +82,10 @@ pub(crate) mod tests {
let owner = Addr::unchecked("owner1");
let mut env = mock_env();
let info = mock_info(owner.as_str(), &[]);
let dealing_bytes = dealing_bytes_fixture();
let dealing = partial_dealing_fixture();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
.unwrap_err();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
@@ -84,13 +101,14 @@ pub(crate) mod tests {
add_fixture_dealer(deps.as_mut());
advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
.unwrap_err();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
assert_eq!(ret, ContractError::NotADealer);
let dealer_details = DealerDetails {
address: owner.clone(),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: String::new(),
assigned_index: 1,
};
@@ -114,8 +132,8 @@ pub(crate) mod tests {
},
)
.unwrap();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true)
.unwrap_err();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), true).unwrap_err();
assert_eq!(ret, ContractError::NotAnInitialDealer);
INITIAL_REPLACEMENT_DATA
@@ -125,18 +143,60 @@ pub(crate) mod tests {
})
.unwrap();
for dealings in DEALINGS_BYTES {
assert!(!dealings.has(deps.as_mut().storage, &owner));
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true);
assert!(ret.is_ok());
assert!(dealings.has(deps.as_mut().storage, &owner));
}
let ret = try_commit_dealings(deps.as_mut(), info, dealing_bytes, true).unwrap_err();
// back to 'normal' mode
CURRENT_EPOCH
.update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
epoch.state = EpochState::DealingExchange { resharing: false };
Ok(epoch)
})
.unwrap();
// dealing out of range
let ret = try_commit_dealings(
deps.as_mut(),
info.clone(),
PartialContractDealing {
index: 42,
data: ContractSafeBytes(vec![1, 2, 3]),
},
false,
)
.unwrap_err();
assert_eq!(
ret,
ContractError::AlreadyCommitted {
commitment: String::from("dealing"),
ContractError::DealingOutOfRange {
epoch_id: 0,
dealer: info.sender.clone(),
index: 42,
key_size: DEFAULT_DEALINGS as u32,
}
);
// 'good' dealing
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false);
assert!(ret.is_ok());
// duplicate dealing
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
assert_eq!(
ret,
ContractError::DealingAlreadyCommitted {
epoch_id: 0,
dealer: info.sender.clone(),
index: 0,
}
);
// same index, but next epoch
CURRENT_EPOCH
.update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
epoch.epoch_id += 1;
Ok(epoch)
})
.unwrap();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false);
assert!(ret.is_ok());
}
}
@@ -2,16 +2,15 @@
// 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;
use crate::state::STATE;
use crate::state::storage::STATE;
use crate::verification_key_shares::storage::verified_dealers;
use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage};
use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
fn reset_dkg_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
THRESHOLD.remove(storage);
let dealers: Vec<_> = current_dealers()
.keys(storage, None, None, Order::Ascending)
@@ -19,15 +18,6 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
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)?;
}
@@ -123,6 +113,8 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
} 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,
@@ -152,7 +144,7 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
EpochState::PublicKeySubmission { resharing: true }
};
reset_epoch_state(deps.storage)?;
reset_dkg_state(deps.storage)?;
Epoch::new(
state,
current_epoch.epoch_id + 1,
@@ -174,7 +166,7 @@ pub(crate) fn try_surpassed_threshold(
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_epoch_state(deps.storage)?;
reset_dkg_state(deps.storage)?;
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
Ok(Epoch::new(
EpochState::default(),
@@ -198,9 +190,7 @@ pub(crate) mod tests {
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::Addr;
use cw4::Member;
use nym_coconut_dkg_common::types::{
ContractSafeBytes, DealerDetails, EpochState, TimeConfiguration,
};
use nym_coconut_dkg_common::types::{DealerDetails, EpochState, TimeConfiguration};
use rusty_fork::rusty_fork_test;
// Because of the global variable handling group, we need individual process for each test
@@ -788,27 +778,12 @@ pub(crate) mod tests {
current_dealers()
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
for dealings in DEALINGS_BYTES {
dealings
.save(
deps.as_mut().storage,
&details.address,
&ContractSafeBytes(vec![1, 2, 3]),
)
.unwrap();
}
}
reset_epoch_state(deps.as_mut().storage).unwrap();
reset_dkg_state(deps.as_mut().storage).unwrap();
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
for details in all_details {
for dealings in DEALINGS_BYTES {
assert!(dealings
.may_load(&deps.storage, &details.address)
.unwrap()
.is_none());
}
assert!(current_dealers()
.may_load(deps.as_mut().storage, &details.address)
.unwrap()
@@ -838,6 +813,7 @@ pub(crate) mod tests {
&DealerDetails {
address: address.clone(),
bte_public_key_with_proof: "bte_public_key_with_proof".to_string(),
ed25519_identity: "identity".to_string(),
announce_address: "127.0.0.1".to_string(),
assigned_index: i,
},
+27 -1
View File
@@ -1,8 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::StdError;
use cosmwasm_std::{Addr, StdError};
use cw_controllers::AdminError;
use nym_coconut_dkg_common::types::{DealingIndex, EpochId};
use thiserror::Error;
/// Custom errors for contract failure conditions.
@@ -43,9 +44,34 @@ pub enum ContractError {
#[error("This sender is not a dealer for the current resharing epoch")]
NotAnInitialDealer,
#[error(
"Dealer {dealer} has already committed dealing for epoch {epoch_id} with index {index}"
)]
DealingAlreadyCommitted {
epoch_id: EpochId,
dealer: Addr,
index: DealingIndex,
},
#[error(
"Dealer {dealer} has attempted to commit dealing for epoch {epoch_id} with index {index} while the key size is set to {key_size}"
)]
DealingOutOfRange {
epoch_id: EpochId,
dealer: Addr,
index: DealingIndex,
key_size: u32,
},
#[error("This dealer has already committed {commitment}")]
AlreadyCommitted { commitment: String },
#[error("No verification key committed for owner {owner}")]
NoCommitForOwner { owner: String },
#[error("failed to parse {value} into a valid SemVer version: {error_message}")]
SemVerFailure {
value: String,
error_message: String,
},
}
+3 -17
View File
@@ -1,19 +1,5 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use cw4::Cw4Contract;
use cw_controllers::Admin;
use cw_storage_plus::Item;
use serde::{Deserialize, Serialize};
// unique items
pub const STATE: Item<State> = Item::new("state");
pub const MULTISIG: Admin = Admin::new("multisig");
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct State {
pub mix_denom: String,
pub multisig_addr: Addr,
pub group_addr: Cw4Contract,
}
pub mod queries;
pub mod storage;
@@ -0,0 +1,10 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::state::storage::STATE;
use cosmwasm_std::{StdResult, Storage};
use nym_coconut_dkg_common::types::State;
pub(crate) fn query_state(storage: &dyn Storage) -> StdResult<State> {
STATE.load(storage)
}
@@ -0,0 +1,10 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cw_controllers::Admin;
use cw_storage_plus::Item;
use nym_coconut_dkg_common::types::State;
// unique items
pub const STATE: Item<State> = Item::new("state");
pub const MULTISIG: Admin = Admin::new("multisig");
@@ -3,7 +3,7 @@
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::ContractSafeBytes;
use nym_coconut_dkg_common::types::{ContractSafeBytes, PartialContractDealing};
use nym_coconut_dkg_common::verification_key::ContractVKShare;
pub const TEST_MIX_DENOM: &str = "unym";
@@ -20,13 +20,21 @@ pub fn vk_share_fixture(owner: &str, index: u64) -> ContractVKShare {
}
pub fn dealing_bytes_fixture() -> ContractSafeBytes {
ContractSafeBytes(vec![])
ContractSafeBytes(vec![1, 2, 3])
}
pub fn partial_dealing_fixture() -> PartialContractDealing {
PartialContractDealing {
index: 0,
data: ContractSafeBytes(vec![1, 2, 3]),
}
}
pub fn dealer_details_fixture(assigned_index: u64) -> DealerDetails {
DealerDetails {
address: Addr::unchecked(format!("owner{}", assigned_index)),
bte_public_key_with_proof: "".to_string(),
ed25519_identity: "".to_string(),
announce_address: "".to_string(),
assigned_index,
}
@@ -9,9 +9,8 @@ use cosmwasm_std::{
QuerierResult, SystemResult, WasmQuery,
};
use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse};
use lazy_static::lazy_static;
use nym_coconut_dkg_common::msg::InstantiateMsg;
use nym_coconut_dkg_common::types::DealerDetails;
use nym_coconut_dkg_common::types::{DealerDetails, DEFAULT_DEALINGS};
use std::sync::Mutex;
use super::fixtures::TEST_MIX_DENOM;
@@ -20,9 +19,7 @@ pub const ADMIN_ADDRESS: &str = "admin address";
pub const GROUP_CONTRACT: &str = "group contract address";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
lazy_static! {
pub static ref GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(vec![]);
}
pub(crate) static GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(Vec::new());
pub fn add_fixture_dealer(deps: DepsMut<'_>) {
let owner = Addr::unchecked("owner");
@@ -33,6 +30,7 @@ pub fn add_fixture_dealer(deps: DepsMut<'_>) {
&DealerDetails {
address: owner.clone(),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: String::new(),
assigned_index: 100,
},
@@ -87,6 +85,7 @@ pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>
multisig_addr: String::from(MULTISIG_CONTRACT),
time_configuration: None,
mix_denom: TEST_MIX_DENOM.to_string(),
key_size: DEFAULT_DEALINGS as u32,
};
let env = mock_env();
let info = mock_info(ADMIN_ADDRESS, &[]);
@@ -6,7 +6,7 @@ use crate::dealers::storage as dealers_storage;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::{MULTISIG, STATE};
use crate::state::storage::{MULTISIG, STATE};
use crate::verification_key_shares::storage::vk_shares;
use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response};
use nym_coconut_dkg_common::types::EpochState;
@@ -122,6 +122,7 @@ mod tests {
let dealer_details = DealerDetails {
address: dealer.clone(),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: announce_address.clone(),
assigned_index: 1,
};
@@ -193,6 +194,7 @@ mod tests {
let dealer_details = DealerDetails {
address: dealer.clone(),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: String::new(),
assigned_index: 1,
};
@@ -300,6 +302,7 @@ mod tests {
let dealer_details = DealerDetails {
address: owner.clone(),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: String::new(),
assigned_index: 1,
};
@@ -75,6 +75,7 @@ fn dkg_proposal() {
multisig_addr: multisig_contract_addr.to_string(),
time_configuration: None,
mix_denom: TEST_COIN_DENOM.to_string(),
key_size: 5,
};
let coconut_dkg_contract_addr = app
.instantiate_contract(
@@ -104,6 +105,7 @@ fn dkg_proposal() {
coconut_dkg_contract_addr.clone(),
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
identity_key: "identity".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
+1 -1
View File
@@ -41,7 +41,7 @@ bs58 = "0.4.0"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { workspace = true }
time = { version = "0.3", features = ["macros"] }
semver = { version = "1.0.16", default-features = false }
semver = { workspace = true, default-features = false }
[dev-dependencies]
rand_chacha = "0.2"
+1 -1
View File
@@ -20,7 +20,7 @@ cw-utils = { workspace = true }
cw2 = { workspace = true }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
nym-name-service-common = { path = "../../common/cosmwasm-smart-contracts/name-service" }
semver = { version = "1.0.16", default-features = false }
semver = { workspace = true, default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
thiserror = { workspace = true }
@@ -20,7 +20,7 @@ cw-utils = { workspace = true }
cw2 = { workspace = true }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
nym-service-provider-directory-common = { path = "../../common/cosmwasm-smart-contracts/service-provider-directory" }
semver = { version = "1.0.16", default-features = false }
semver = { workspace = true, default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
thiserror = { workspace = true }
+1 -1
View File
@@ -36,7 +36,7 @@ cw-storage-plus = { workspace = true, features = ["iterator"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror ={ workspace = true }
semver = { version = "1.0.16", default-features = false }
semver = { workspace = true, default-features = false }
[dev-dependencies]
rand_chacha = "0.3.1"
+19 -5
View File
@@ -5,12 +5,13 @@ use crate::coconut::error::Result;
use cw3::ProposalResponse;
use cw4::MemberResponse;
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use nym_coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse, DealingStatusResponse};
use nym_coconut_dkg_common::types::{
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData,
DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData,
PartialContractDealing, State,
};
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_contracts_common::IdentityKey;
use nym_dkg::Threshold;
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
use nym_validator_client::nyxd::{AccountId, Fee, Hash, TxResponse};
@@ -25,13 +26,25 @@ pub trait Client {
&self,
blinded_serial_number: String,
) -> Result<SpendCredentialResponse>;
async fn contract_state(&self) -> Result<State>;
async fn get_current_epoch(&self) -> Result<Epoch>;
async fn group_member(&self, addr: String) -> Result<MemberResponse>;
async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>>;
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>>;
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse>;
async fn get_dealing_status(
&self,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> Result<DealingStatusResponse>;
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>>;
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>>;
async fn get_dealings(
&self,
epoch_id: EpochId,
dealer: &str,
) -> Result<Vec<PartialContractDealing>>;
async fn get_verification_key_shares(&self, epoch_id: EpochId) -> Result<Vec<ContractVKShare>>;
async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option<Fee>)
-> Result<()>;
@@ -40,12 +53,13 @@ pub trait Client {
async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
identity_key: IdentityKey,
announce_address: String,
resharing: bool,
) -> Result<ExecuteResult>;
async fn submit_dealing(
&self,
dealing_bytes: ContractSafeBytes,
dealing: PartialContractDealing,
resharing: bool,
) -> Result<ExecuteResult>;
async fn submit_verification_key_share(
+57 -11
View File
@@ -5,22 +5,25 @@ use crate::coconut::client::Client;
use crate::coconut::error::CoconutError;
use cw3::ProposalResponse;
use cw4::MemberResponse;
use nym_coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse};
use nym_coconut_dkg_common::types::{
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, NodeIndex,
DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, NodeIndex,
PartialContractDealing, State as ContractState,
};
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_contracts_common::IdentityKey;
use nym_dkg::Threshold;
use nym_validator_client::nyxd::cosmwasm_client::logs::{find_attribute, NODE_INDEX};
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
use nym_validator_client::nyxd::AccountId;
use std::time::Duration;
pub(crate) struct DkgClient {
inner: Box<dyn Client + Send + Sync>,
}
impl DkgClient {
// FIXME:
// Some queries simply don't work the first time
// Until we determine why that is, retry the query a few more times
const RETRIES: usize = 3;
@@ -44,11 +47,24 @@ impl DkgClient {
if ret.is_ok() {
return ret;
}
tokio::time::sleep(Duration::from_millis(200)).await;
ret = self.inner.get_current_epoch().await;
}
ret
}
pub(crate) async fn get_contract_state(&self) -> Result<ContractState, CoconutError> {
let mut ret = self.inner.contract_state().await;
for _ in 0..Self::RETRIES {
if ret.is_ok() {
return ret;
}
tokio::time::sleep(Duration::from_millis(200)).await;
ret = self.inner.contract_state().await;
}
ret
}
pub(crate) async fn group_member(&self) -> Result<MemberResponse, CoconutError> {
self.inner
.group_member(self.get_address().await.to_string())
@@ -77,16 +93,44 @@ impl DkgClient {
self.inner.get_current_dealers().await
}
pub(crate) async fn get_dealings(
pub(crate) async fn get_dealing_status(
&self,
idx: usize,
) -> Result<Vec<ContractDealing>, CoconutError> {
let mut ret = self.inner.get_dealings(idx).await;
epoch_id: EpochId,
dealing_index: DealingIndex,
) -> Result<bool, CoconutError> {
let address = self.inner.address().await.to_string();
let mut ret = self
.inner
.get_dealing_status(epoch_id, address.clone(), dealing_index)
.await
.map(|r| r.dealing_submitted);
for _ in 0..Self::RETRIES {
if ret.is_ok() {
return ret;
}
ret = self.inner.get_dealings(idx).await;
tokio::time::sleep(Duration::from_millis(200)).await;
ret = self
.inner
.get_dealing_status(epoch_id, address.clone(), dealing_index)
.await
.map(|r| r.dealing_submitted);
}
ret
}
pub(crate) async fn get_dealings(
&self,
epoch_id: EpochId,
dealer: String,
) -> Result<Vec<PartialContractDealing>, CoconutError> {
let mut ret = self.inner.get_dealings(epoch_id, &dealer).await;
for _ in 0..Self::RETRIES {
if ret.is_ok() {
return ret;
}
tokio::time::sleep(Duration::from_millis(200)).await;
ret = self.inner.get_dealings(epoch_id, &dealer).await;
}
ret
}
@@ -109,12 +153,13 @@ impl DkgClient {
pub(crate) async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
identity_key: IdentityKey,
announce_address: String,
resharing: bool,
) -> Result<NodeIndex, CoconutError> {
let res = self
.inner
.register_dealer(bte_key, announce_address, resharing)
.register_dealer(bte_key, identity_key, announce_address, resharing)
.await?;
let node_index = find_attribute(&res.logs, "wasm", NODE_INDEX)
.ok_or(CoconutError::NodeIndexRecoveryError {
@@ -131,10 +176,10 @@ impl DkgClient {
pub(crate) async fn submit_dealing(
&self,
dealing_bytes: ContractSafeBytes,
dealing: PartialContractDealing,
resharing: bool,
) -> Result<(), CoconutError> {
self.inner.submit_dealing(dealing_bytes, resharing).await?;
self.inner.submit_dealing(dealing, resharing).await?;
Ok(())
}
@@ -151,6 +196,7 @@ impl DkgClient {
if let Ok(res) = ret {
return Ok(res);
}
tokio::time::sleep(Duration::from_millis(200)).await;
ret = self
.inner
.submit_verification_key_share(share.clone(), resharing)
+7 -1
View File
@@ -15,6 +15,7 @@ use crate::nyxd;
use crate::support::config;
use anyhow::{bail, Result};
use nym_coconut_dkg_common::types::EpochState;
use nym_crypto::asymmetric::identity;
use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
use nym_task::{TaskClient, TaskManager};
use rand::rngs::OsRng;
@@ -51,6 +52,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
config: &config::CoconutSigner,
nyxd_client: nyxd::Client,
coconut_keypair: CoconutKeyPair,
identity_key: identity::PublicKey,
rng: R,
) -> Result<Self> {
let Some(announce_address) = &config.announce_address else {
@@ -82,6 +84,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
persistent_state,
announce_address.clone(),
dkg_keypair,
identity_key,
coconut_keypair,
),
rng,
@@ -140,6 +143,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
verification_key_submission(
&self.dkg_client,
&mut self.state,
epoch.epoch_id,
&keypair_path,
resharing,
)
@@ -207,6 +211,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
config: &config::CoconutSigner,
nyxd_client: nyxd::Client,
coconut_keypair: CoconutKeyPair,
identity_key: identity::PublicKey,
rng: R,
shutdown: &TaskManager,
) -> Result<()>
@@ -214,7 +219,8 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
R: Sync + Send + 'static,
{
let shutdown_listener = shutdown.subscribe();
let dkg_controller = DkgController::new(config, nyxd_client, coconut_keypair, rng).await?;
let dkg_controller =
DkgController::new(config, nyxd_client, coconut_keypair, identity_key, rng).await?;
tokio::spawn(async move { dkg_controller.run(shutdown_listener).await });
Ok(())
}
+89 -29
View File
@@ -1,13 +1,12 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::dkg;
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::state::{ConsistentState, State};
use crate::coconut::error::CoconutError;
use log::debug;
use nym_coconut_dkg_common::types::TOTAL_DEALINGS;
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_dkg::bte::setup;
use nym_coconut_dkg_common::types::{ContractDealing, PartialContractDealing};
use nym_dkg::Dealing;
use rand::RngCore;
use std::collections::VecDeque;
@@ -24,6 +23,11 @@ pub(crate) async fn dealing_exchange(
return Ok(());
}
let contract_state = dkg_client.get_contract_state().await?;
let expected_key_size = contract_state.key_size;
let epoch_id = dkg_client.get_current_epoch().await?.epoch_id;
let dealers = dkg_client.get_current_dealers().await?;
let threshold = dkg_client.get_current_epoch_threshold().await?;
let initial_dealers = dkg_client
@@ -44,7 +48,7 @@ pub(crate) async fn dealing_exchange(
// Double check that we are in resharing mode
if resharing {
let sk = keypair.secret_key();
if sk.size() + 1 != TOTAL_DEALINGS {
if sk.size() + 1 != expected_key_size as usize {
return Err(CoconutError::CorruptedCoconutKeyPair);
}
@@ -64,23 +68,54 @@ pub(crate) async fn dealing_exchange(
};
let mut prior_resharing_secrets = VecDeque::from(prior_resharing_secrets);
if !resharing || initial_dealers.iter().any(|d| *d == own_address) {
let params = setup();
for _ in 0..TOTAL_DEALINGS {
let params = dkg::params();
for dealing_index in 0..expected_key_size {
debug!(
"dealing {dealing_index} ({} out of {expected_key_size})",
dealing_index + 1
);
// see if we have already submitted this one (we might have crashed)
if dkg_client
.get_dealing_status(epoch_id, dealing_index)
.await?
{
warn!("we have already submitted dealing {dealing_index} before - we probably crashed!");
continue;
}
// see if we have already generated, but not submitted this one before (we might have crashed or validator might have had a problem)
let contract_dealing = if let Some(prior_dealing) =
state.get_dealing(epoch_id, dealing_index)
{
warn!("we have already generated dealing {dealing_index} before, but failed to submit it");
PartialContractDealing::new(dealing_index, ContractDealing::from(prior_dealing))
} else {
// generate fresh dealing
let (dealing, _) = Dealing::create(
rng.clone(),
params,
dealer_index,
state.threshold()?,
&receivers,
prior_resharing_secrets.pop_front(),
);
let contract_dealing =
PartialContractDealing::new(dealing_index, ContractDealing::from(&dealing));
state.store_dealing(epoch_id, dealing_index, dealing);
contract_dealing
};
debug!(
"Submitting dealing for indexes {:?} with resharing: {}",
receivers.keys().collect::<Vec<_>>(),
prior_resharing_secrets.front().is_some()
);
let (dealing, _) = Dealing::create(
rng.clone(),
&params,
dealer_index,
state.threshold()?,
&receivers,
prior_resharing_secrets.pop_front(),
);
dkg_client
.submit_dealing(ContractSafeBytes::from(&dealing), resharing)
.submit_dealing(contract_dealing, resharing)
.await?;
}
} else {
@@ -104,10 +139,12 @@ pub(crate) mod tests {
use nym_coconut::{ttp_keygen, Parameters};
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::InitialReplacementData;
use nym_crypto::asymmetric::identity;
use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
use nym_dkg::bte::{Params, PublicKeyWithProof};
use nym_validator_client::nyxd::AccountId;
use rand::rngs::OsRng;
use rand_07::thread_rng;
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
@@ -128,6 +165,8 @@ pub(crate) mod tests {
let mut keypairs = vec![];
for (idx, addr) in TEST_VALIDATORS_ADDRESS.iter().enumerate() {
let keypair = DkgKeyPair::new(params, OsRng);
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let bte_public_key_with_proof =
bs58::encode(&keypair.public_key().to_bytes()).into_string();
keypairs.push(keypair);
@@ -137,6 +176,7 @@ pub(crate) mod tests {
DealerDetails {
address: Addr::unchecked(*addr),
bte_public_key_with_proof,
ed25519_identity: identity_keypair.public_key().to_base58_string(),
announce_address: format!("localhost:80{}", idx),
assigned_index: (idx + 1) as u64,
},
@@ -160,16 +200,20 @@ pub(crate) mod tests {
.with_dealings(&dealings_db)
.with_threshold(&threshold_db),
);
let params = setup();
let params = dkg::params();
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
DkgKeyPair::new(params, OsRng),
*identity_keypair.public_key(),
KeyPair::new(),
);
state.set_node_index(Some(self_index));
let keypairs = insert_dealers(&params, &dealer_details_db);
let keypairs = insert_dealers(params, &dealer_details_db);
let contract_state = dkg_client.get_contract_state().await.unwrap();
dealing_exchange(&dkg_client, &mut state, OsRng, false)
.await
@@ -187,10 +231,12 @@ pub(crate) mod tests {
let dealings = dealings_db
.read()
.unwrap()
.get(&0)
.unwrap()
.get(TEST_VALIDATORS_ADDRESS[0])
.unwrap()
.clone();
assert_eq!(dealings.len(), TOTAL_DEALINGS);
assert_eq!(dealings.len(), contract_state.key_size as usize);
dealing_exchange(&dkg_client, &mut state, OsRng, false)
.await
@@ -198,6 +244,8 @@ pub(crate) mod tests {
let new_dealings = dealings_db
.read()
.unwrap()
.get(&0)
.unwrap()
.get(TEST_VALIDATORS_ADDRESS[0])
.unwrap()
.clone();
@@ -217,16 +265,18 @@ pub(crate) mod tests {
.with_dealings(&dealings_db)
.with_threshold(&threshold_db),
);
let params = setup();
let params = dkg::params();
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
DkgKeyPair::new(params, OsRng),
*identity_keypair.public_key(),
KeyPair::new(),
);
state.set_node_index(Some(self_index));
insert_dealers(&params, &dealer_details_db);
insert_dealers(params, &dealer_details_db);
dealer_details_db
.write()
@@ -285,20 +335,24 @@ pub(crate) mod tests {
.with_threshold(&threshold_db)
.with_initial_dealers_db(&initial_dealers_db),
);
let params = setup();
let contract_state = dkg_client.get_contract_state().await.unwrap();
let params = dkg::params();
let mut keys = ttp_keygen(&Parameters::new(4).unwrap(), 3, 4).unwrap();
let coconut_keypair = KeyPair::new();
coconut_keypair.set(Some(keys.pop().unwrap())).await;
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
DkgKeyPair::new(params, OsRng),
*identity_keypair.public_key(),
coconut_keypair.clone(),
);
state.set_node_index(Some(self_index));
let keypairs = insert_dealers(&params, &dealer_details_db);
let keypairs = insert_dealers(params, &dealer_details_db);
dealing_exchange(&dkg_client, &mut state, OsRng, true)
.await
@@ -313,14 +367,18 @@ pub(crate) mod tests {
);
assert_eq!(state.threshold().unwrap(), 3);
assert_eq!(state.receiver_index().unwrap(), 1);
let addr = dkg_client.get_address().await;
assert!(dealings_db.read().unwrap().get(addr.as_ref()).is_none());
// let addr = dkg_client.get_address().await;
// no dealings submitted for the first (zeroth) epoch
assert!(dealings_db.read().unwrap().get(&0).is_none());
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
DkgKeyPair::new(params, OsRng),
*identity_keypair.public_key(),
coconut_keypair,
);
state.set_node_index(Some(self_index));
@@ -340,9 +398,11 @@ pub(crate) mod tests {
let dealings = dealings_db
.read()
.unwrap()
.get(&0)
.unwrap()
.get(TEST_VALIDATORS_ADDRESS[0])
.unwrap()
.clone();
assert_eq!(dealings.len(), TOTAL_DEALINGS);
assert_eq!(dealings.len(), contract_state.key_size as usize);
}
}
+7
View File
@@ -1,6 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use std::sync::OnceLock;
pub(crate) fn params() -> &'static nym_dkg::bte::Params {
static PARAMS: OnceLock<nym_dkg::bte::Params> = OnceLock::new();
PARAMS.get_or_init(nym_dkg::bte::setup)
}
pub(crate) mod client;
pub(crate) mod complaints;
pub(crate) mod controller;
+16 -2
View File
@@ -38,7 +38,12 @@ pub(crate) async fn public_key_submission(
// If it was a dealer in a previous epoch, re-register it for this epoch
debug!("Registering for the current DKG round, with keys from a previous epoch");
dkg_client
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
.register_dealer(
bte_key,
state.identity_key().to_base58_string(),
state.announce_address().to_string(),
resharing,
)
.await?;
}
details.assigned_index
@@ -46,7 +51,12 @@ pub(crate) async fn public_key_submission(
debug!("Registering for the first time to be a dealer");
// First time registration
dkg_client
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
.register_dealer(
bte_key,
state.identity_key().to_base58_string(),
state.announce_address().to_string(),
resharing,
)
.await?
};
state.set_node_index(Some(index));
@@ -61,9 +71,11 @@ pub(crate) mod tests {
use crate::coconut::dkg::state::PersistentState;
use crate::coconut::tests::DummyClient;
use crate::coconut::KeyPair;
use nym_crypto::asymmetric::identity;
use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
use nym_validator_client::nyxd::AccountId;
use rand::rngs::OsRng;
use rand_07::thread_rng;
use std::path::PathBuf;
use std::str::FromStr;
use url::Url;
@@ -76,11 +88,13 @@ pub(crate) mod tests {
let dkg_client = DkgClient::new(DummyClient::new(
AccountId::from_str(TEST_VALIDATOR_ADDRESS).unwrap(),
));
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&nym_dkg::bte::setup(), OsRng),
*identity_keypair.public_key(),
KeyPair::new(),
);
+78 -6
View File
@@ -7,12 +7,13 @@ use crate::coconut::keypair::KeyPair as CoconutKeyPair;
use cosmwasm_std::Addr;
use log::debug;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::EpochState;
use nym_coconut_dkg_common::types::{DealingIndex, EpochId, EpochState};
use nym_crypto::asymmetric::identity;
use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof};
use nym_dkg::{NodeIndex, RecoveredVerificationKeys, Threshold};
use nym_dkg::{Dealing, NodeIndex, RecoveredVerificationKeys, Threshold};
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use url::Url;
@@ -159,10 +160,54 @@ where
.collect()
}
mod generated_dealings {
use nym_coconut_dkg_common::types::{DealingIndex, EpochId};
use nym_dkg::Dealing;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
type Helper = HashMap<EpochId, HashMap<DealingIndex, Vec<u8>>>;
pub fn serialize<S: Serializer>(
dealings: &HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut helper = HashMap::new();
for (epoch, dealings) in dealings {
let mut inner = HashMap::new();
for (dealing_index, dealing) in dealings {
inner.insert(*dealing_index, dealing.to_bytes());
}
helper.insert(*epoch, inner);
}
helper.serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<HashMap<EpochId, HashMap<DealingIndex, Dealing>>, D::Error> {
let helper = <Helper>::deserialize(deserializer)?;
let mut epoch_dealings = HashMap::with_capacity(helper.len());
for (epoch, dealings) in helper {
let mut inner = HashMap::with_capacity(dealings.len());
for (dealing_index, raw_dealing) in dealings {
let dealing =
Dealing::try_from_bytes(&raw_dealing).map_err(serde::de::Error::custom)?;
inner.insert(dealing_index, dealing);
}
epoch_dealings.insert(epoch, inner);
}
Ok(epoch_dealings)
}
}
#[derive(Default, Deserialize, Serialize)]
pub(crate) struct PersistentState {
node_index: Option<NodeIndex>,
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
#[serde(with = "generated_dealings")]
generated_dealings: HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
receiver_index: Option<usize>,
threshold: Option<Threshold>,
#[serde(serialize_with = "vks_serialize")]
@@ -179,6 +224,7 @@ impl From<&State> for PersistentState {
PersistentState {
node_index: s.node_index,
dealers: s.dealers.clone(),
generated_dealings: s.generated_dealings.clone(),
receiver_index: s.receiver_index,
threshold: s.threshold,
recovered_vks: s.recovered_vks.clone(),
@@ -204,10 +250,12 @@ impl PersistentState {
pub(crate) struct State {
persistent_state_path: PathBuf,
announce_address: Url,
identity_key: identity::PublicKey,
dkg_keypair: DkgKeyPair,
coconut_keypair: CoconutKeyPair,
node_index: Option<NodeIndex>,
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
generated_dealings: HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
receiver_index: Option<usize>,
threshold: Option<Threshold>,
recovered_vks: Vec<RecoveredVerificationKeys>,
@@ -223,15 +271,18 @@ impl State {
persistent_state: PersistentState,
announce_address: Url,
dkg_keypair: DkgKeyPair,
identity_key: identity::PublicKey,
coconut_keypair: CoconutKeyPair,
) -> Self {
State {
persistent_state_path,
announce_address,
identity_key,
dkg_keypair,
coconut_keypair,
node_index: persistent_state.node_index,
dealers: persistent_state.dealers,
generated_dealings: persistent_state.generated_dealings,
receiver_index: persistent_state.receiver_index,
threshold: persistent_state.threshold,
recovered_vks: persistent_state.recovered_vks,
@@ -265,6 +316,10 @@ impl State {
&self.announce_address
}
pub fn identity_key(&self) -> identity::PublicKey {
self.identity_key
}
pub fn dkg_keypair(&self) -> &DkgKeyPair {
&self.dkg_keypair
}
@@ -277,6 +332,24 @@ impl State {
self.coconut_keypair.take().await
}
pub fn get_dealing(&self, epoch_id: EpochId, dealing_index: DealingIndex) -> Option<&Dealing> {
self.generated_dealings
.get(&epoch_id)
.and_then(|epoch_dealings| epoch_dealings.get(&dealing_index))
}
pub fn store_dealing(
&mut self,
epoch_id: EpochId,
dealing_index: DealingIndex,
dealing: Dealing,
) {
self.generated_dealings
.entry(epoch_id)
.or_default()
.insert(dealing_index, dealing);
}
#[cfg(test)]
pub async fn coconut_keypair(
&self,
@@ -304,6 +377,7 @@ impl State {
.collect()
}
// FIXME: BUG: if we remove dealers, we won't be able to verify shares of other parties...
pub fn current_dealers_by_idx(&self) -> BTreeMap<NodeIndex, PublicKey> {
self.dealers
.iter()
@@ -364,8 +438,7 @@ impl State {
.find(|(addr, _)| *addr == dealer_addr)
{
debug!(
"Dealer {} misbehaved: {:?}. It will be marked locally as bad dealer and ignored",
dealer_addr, reason
"Dealer {dealer_addr} misbehaved: {reason:?}. It will be marked locally as bad dealer and ignored",
);
*value = Err(reason);
}
@@ -395,7 +468,6 @@ impl State {
self.was_in_progress = true;
}
#[cfg(test)]
pub fn all_dealers(&self) -> &BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>> {
&self.dealers
}
+163 -77
View File
@@ -1,6 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::dkg;
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::complaints::ComplaintReason;
use crate::coconut::dkg::state::{ConsistentState, State};
@@ -13,24 +14,30 @@ use log::debug;
use nym_coconut::tests::helpers::transpose_matrix;
use nym_coconut::{check_vk_pairing, Base58, KeyPair, SecretKey, VerificationKey};
use nym_coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID;
use nym_coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS};
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
use nym_coconut_dkg_common::verification_key::owner_from_cosmos_msgs;
use nym_coconut_interface::KeyPair as CoconutKeyPair;
use nym_dkg::bte::{decrypt_share, setup};
use nym_dkg::bte::decrypt_share;
use nym_dkg::error::DkgError;
use nym_dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold};
use nym_pemstore::KeyPairPath;
use nym_validator_client::nyxd::cosmwasm_client::logs::find_attribute;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
// Filter the dealers based on what dealing they posted (or not) in the contract
// TODO: change the return type to make sure that:
// - each entry has the same number of dealings
// - dealer data is not duplicated
// - each dealer has submitted all or nothing
async fn deterministic_filter_dealers(
dkg_client: &DkgClient,
state: &mut State,
epoch_id: EpochId,
threshold: Threshold,
resharing: bool,
) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> {
let mut dealings_maps = vec![];
let mut dealings_maps = Vec::new();
let initial_dealers_by_addr = state.current_dealers_by_addr();
let initial_receivers = state.current_dealers_by_idx();
let initial_resharing_dealers = if resharing {
@@ -43,36 +50,46 @@ async fn deterministic_filter_dealers(
vec![]
};
let params = setup();
let params = dkg::params();
for idx in 0..TOTAL_DEALINGS {
let dealings = dkg_client.get_dealings(idx).await?;
// note: this is a temporary solution to replicate the behaviour of the old code so that I wouldn't need to
// fix the filtering in this PR, because the old code is quite buggy and misses few edge cases
let mut raw_dealings = HashMap::new();
for dealer in state.all_dealers().keys() {
let dealer_dealings = dkg_client
.get_dealings(epoch_id, dealer.to_string())
.await?;
for dealing in dealer_dealings {
let old_contract_dealing = raw_dealings.entry(dealing.index).or_insert(Vec::new());
old_contract_dealing.push((dealer.clone(), dealing.data))
}
}
// this is a temporary thing to reintroduce the bug to make sure tests still pass : )
// i will fix it properly in next PR
for dealing_index in 0..5 {
let dealings = raw_dealings.remove(&dealing_index).unwrap_or_default();
let dealings_map =
BTreeMap::from_iter(dealings.into_iter().filter_map(|contract_dealing| {
match Dealing::try_from(&contract_dealing.dealing) {
BTreeMap::from_iter(dealings.into_iter().filter_map(|(dealer, dealing)| {
match Dealing::try_from(&dealing) {
Ok(dealing) => {
if dealing
.verify(&params, threshold, &initial_receivers, None)
.verify(params, threshold, &initial_receivers, None)
.is_err()
{
state.mark_bad_dealer(
&contract_dealing.dealer,
&dealer,
ComplaintReason::DealingVerificationError,
);
None
} else if let Some(idx) =
initial_dealers_by_addr.get(&contract_dealing.dealer)
{
Some((*idx, (contract_dealing.dealer, dealing)))
} else {
None
initial_dealers_by_addr
.get(&dealer)
.map(|idx| (*idx, (dealer, dealing)))
}
}
Err(_) => {
state.mark_bad_dealer(
&contract_dealing.dealer,
ComplaintReason::MalformedDealing,
);
state.mark_bad_dealer(&dealer, ComplaintReason::MalformedDealing);
None
}
}
@@ -80,6 +97,40 @@ async fn deterministic_filter_dealers(
dealings_maps.push(dealings_map);
}
//
//
// for dealer in initial_dealers_by_addr.keys() {
// let Some(dealer_index) = initial_dealers_by_addr.get(dealer) else {
// warn!("could not obtain dealer index of {dealer}");
// continue;
// };
//
// let dealer_dealings = dkg_client
// .get_dealings(epoch_id, dealer.to_string())
// .await?;
//
// for contract_dealing in dealer_dealings {
// match Dealing::try_from(&contract_dealing.data) {
// // FIXME: bug: this doesn't check resharing
// Ok(dealing) => {
// if let Err(err) = dealing.verify(params, threshold, &initial_receivers, None) {
// println!("dealing verification failure from {dealer}: {err}");
// state.mark_bad_dealer(dealer, ComplaintReason::DealingVerificationError);
// } else {
// let entry = dealings_maps
// .entry(contract_dealing.index)
// .or_insert(BTreeMap::new());
// entry.insert(*dealer_index, (dealer.clone(), dealing));
// }
// }
// Err(err) => {
// warn!("malformed dealing from {dealer}: {err}");
// state.mark_bad_dealer(dealer, ComplaintReason::MalformedDealing);
// }
// }
// }
// }
for (addr, _) in initial_dealers_by_addr.iter() {
// in resharing mode, we don't commit dealings from dealers outside the initial set
if !resharing || initial_resharing_dealers.contains(addr) {
@@ -155,6 +206,7 @@ fn derive_partial_keypair(
pub(crate) async fn verification_key_submission(
dkg_client: &DkgClient,
state: &mut State,
epoch_id: EpochId,
keypair_path: &KeyPairPath,
resharing: bool,
) -> Result<(), CoconutError> {
@@ -165,7 +217,7 @@ pub(crate) async fn verification_key_submission(
let threshold = state.threshold()?;
let dealings_maps =
deterministic_filter_dealers(dkg_client, state, threshold, resharing).await?;
deterministic_filter_dealers(dkg_client, state, epoch_id, threshold, resharing).await?;
debug!(
"Filtered dealers to {:?}",
dealings_maps[0].keys().collect::<Vec<_>>()
@@ -307,13 +359,14 @@ pub(crate) mod tests {
use crate::coconut::KeyPair;
use nym_coconut::aggregate_verification_keys;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::InitialReplacementData;
use nym_coconut_dkg_common::types::{EpochId, InitialReplacementData, PartialContractDealing};
use nym_coconut_dkg_common::verification_key::ContractVKShare;
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_crypto::asymmetric::identity;
use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
use nym_validator_client::nyxd::AccountId;
use rand::rngs::OsRng;
use rand::Rng;
use rand_07::thread_rng;
use std::collections::HashMap;
use std::env::temp_dir;
use std::path::PathBuf;
@@ -323,7 +376,9 @@ pub(crate) mod tests {
struct MockContractDb {
dealer_details_db: Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
dealings_db: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
// it's a really bad practice, but I'm not going to be changing it now...
#[allow(clippy::type_complexity)]
dealings_db: Arc<RwLock<HashMap<EpochId, HashMap<String, Vec<PartialContractDealing>>>>>,
proposal_db: Arc<RwLock<HashMap<u64, ProposalResponse>>>,
verification_share_db: Arc<RwLock<HashMap<String, ContractVKShare>>>,
threshold_db: Arc<RwLock<Option<Threshold>>>,
@@ -351,8 +406,9 @@ pub(crate) mod tests {
];
async fn prepare_clients_and_states(db: &MockContractDb) -> Vec<(DkgClient, State)> {
let params = setup();
let params = dkg::params();
let mut clients_and_states = vec![];
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
for addr in TEST_VALIDATORS_ADDRESS {
let dkg_client = DkgClient::new(
@@ -364,12 +420,13 @@ pub(crate) mod tests {
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
);
let keypair = DkgKeyPair::new(&params, OsRng);
let keypair = DkgKeyPair::new(params, OsRng);
let state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
keypair,
*identity_keypair.public_key(),
KeyPair::new(),
);
clients_and_states.push((dkg_client, state));
@@ -403,7 +460,7 @@ pub(crate) mod tests {
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, false)
verification_key_submission(dkg_client, state, 0, &keypair_path, false)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
@@ -441,11 +498,13 @@ pub(crate) mod tests {
async fn check_dealers_filter_all_good() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap();
for (dkg_client, state) in clients_and_states.iter_mut() {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
assert_eq!(filtered.len(), contract_state.key_size as usize);
for mapping in filtered.iter() {
assert_eq!(mapping.len(), 4);
}
@@ -457,23 +516,27 @@ pub(crate) mod tests {
async fn check_dealers_filter_one_bad_dealing() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap();
// corrupt just one dealing
db.dealings_db
.write()
.unwrap()
.entry(TEST_VALIDATORS_ADDRESS[0].to_string())
.and_modify(|dealings| {
let mut last = dealings.pop().unwrap();
last.0.pop();
dealings.push(last);
.entry(0)
.and_modify(|epoch_dealings| {
let validator_dealings = epoch_dealings
.entry(TEST_VALIDATORS_ADDRESS[0].to_string())
.or_default();
let mut last = validator_dealings.pop().unwrap();
last.data.0.pop();
validator_dealings.push(last);
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
assert_eq!(filtered.len(), contract_state.key_size as usize);
let corrupted_status = state
.all_dealers()
.get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0]))
@@ -489,6 +552,7 @@ pub(crate) mod tests {
async fn check_dealers_resharing_filter_one_missing_dealing() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states(&db).await;
let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap();
// add all but the first dealing
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
@@ -502,10 +566,10 @@ pub(crate) mod tests {
initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])],
initial_height: 1,
});
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, true)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
assert_eq!(filtered.len(), contract_state.key_size as usize);
let corrupted_status = state
.all_dealers()
.get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0]))
@@ -522,6 +586,7 @@ pub(crate) mod tests {
async fn check_dealers_resharing_filter_one_noninitial_missing_dealing() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states(&db).await;
let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap();
// add all but the first dealing
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
@@ -535,10 +600,10 @@ pub(crate) mod tests {
initial_dealers: vec![],
initial_height: 1,
});
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, true)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
assert_eq!(filtered.len(), contract_state.key_size as usize);
assert!(state
.all_dealers()
.get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0]))
@@ -553,23 +618,27 @@ pub(crate) mod tests {
async fn check_dealers_filter_all_bad_dealings() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap();
// corrupt all dealings of one address
db.dealings_db
.write()
.unwrap()
.entry(TEST_VALIDATORS_ADDRESS[0].to_string())
.and_modify(|dealings| {
dealings.iter_mut().for_each(|dealing| {
dealing.0.pop();
.entry(0)
.and_modify(|epoch_dealings| {
let validator_dealings = epoch_dealings
.entry(TEST_VALIDATORS_ADDRESS[0].to_string())
.or_default();
validator_dealings.iter_mut().for_each(|dealing| {
dealing.data.0.pop();
});
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
assert_eq!(filtered.len(), contract_state.key_size as usize);
for mapping in filtered.iter() {
assert_eq!(mapping.len(), 3);
}
@@ -588,28 +657,32 @@ pub(crate) mod tests {
async fn check_dealers_filter_malformed_dealing() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap();
// corrupt just one dealing
db.dealings_db
.write()
.unwrap()
.entry(TEST_VALIDATORS_ADDRESS[0].to_string())
.and_modify(|dealings| {
let mut last = dealings.pop().unwrap();
last.0.pop();
dealings.push(last);
.entry(0)
.and_modify(|epoch_dealings| {
let validator_dealings = epoch_dealings
.get_mut(TEST_VALIDATORS_ADDRESS[0])
.expect("no dealing");
let mut last = validator_dealings.pop().unwrap();
last.data.0.pop();
validator_dealings.push(last);
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
deterministic_filter_dealers(dkg_client, state, 2, false)
deterministic_filter_dealers(dkg_client, state, 0, 2, false)
.await
.unwrap();
// second filter will leave behind the bad dealer and surface why it was left out
// in the first place
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
assert_eq!(filtered.len(), contract_state.key_size as usize);
let corrupted_status = state
.all_dealers()
.get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0]))
@@ -625,33 +698,37 @@ pub(crate) mod tests {
async fn check_dealers_filter_dealing_verification_error() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
let contract_state = clients_and_states[0].0.get_contract_state().await.unwrap();
// corrupt just one dealing
db.dealings_db
.write()
.unwrap()
.entry(TEST_VALIDATORS_ADDRESS[0].to_string())
.and_modify(|dealings| {
let mut last = dealings.pop().unwrap();
let value = last.0.pop().unwrap();
.entry(0)
.and_modify(|epoch_dealings| {
let validator_dealings = epoch_dealings
.entry(TEST_VALIDATORS_ADDRESS[0].to_string())
.or_default();
let mut last = validator_dealings.pop().unwrap();
let value = last.data.0.pop().unwrap();
if value == 42 {
last.0.push(43);
last.data.0.push(43);
} else {
last.0.push(42);
last.data.0.push(42);
}
dealings.push(last);
validator_dealings.push(last);
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
deterministic_filter_dealers(dkg_client, state, 2, false)
deterministic_filter_dealers(dkg_client, state, 0, 2, false)
.await
.unwrap();
// second filter will leave behind the bad dealer and surface why it was left out
// in the first place
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false)
.await
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
assert_eq!(filtered.len(), contract_state.key_size as usize);
let corrupted_status = state
.all_dealers()
.get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0]))
@@ -668,7 +745,7 @@ pub(crate) mod tests {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
for (dkg_client, state) in clients_and_states.iter_mut() {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false)
.await
.unwrap();
assert!(derive_partial_keypair(state, 2, filtered).is_ok());
@@ -685,15 +762,18 @@ pub(crate) mod tests {
db.dealings_db
.write()
.unwrap()
.entry(TEST_VALIDATORS_ADDRESS[0].to_string())
.and_modify(|dealings| {
let mut last = dealings.pop().unwrap();
last.0.pop();
dealings.push(last);
.entry(0)
.and_modify(|epoch_dealings| {
let validator_dealings = epoch_dealings
.entry(TEST_VALIDATORS_ADDRESS[0].to_string())
.or_default();
let mut last = validator_dealings.pop().unwrap();
last.data.0.pop();
validator_dealings.push(last);
});
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
let filtered = deterministic_filter_dealers(dkg_client, state, 0, 2, false)
.await
.unwrap();
assert!(derive_partial_keypair(state, 2, filtered).is_ok());
@@ -863,12 +943,14 @@ pub(crate) mod tests {
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
);
let keypair = DkgKeyPair::new(&setup(), OsRng);
let keypair = DkgKeyPair::new(dkg::params(), OsRng);
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
keypair,
*identity_keypair.public_key(),
KeyPair::new(),
);
@@ -906,7 +988,7 @@ pub(crate) mod tests {
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, true)
verification_key_submission(dkg_client, state, 0, &keypair_path, true)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
@@ -967,12 +1049,14 @@ pub(crate) mod tests {
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
);
let keypair = DkgKeyPair::new(&setup(), OsRng);
let keypair = DkgKeyPair::new(dkg::params(), OsRng);
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
keypair,
*identity_keypair.public_key(),
KeyPair::new(),
);
let new_dkg_client2 = DkgClient::new(
@@ -986,12 +1070,14 @@ pub(crate) mod tests {
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
);
let keypair = DkgKeyPair::new(&setup(), OsRng);
let keypair = DkgKeyPair::new(dkg::params(), OsRng);
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let state2 = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
keypair,
*identity_keypair.public_key(),
KeyPair::new(),
);
@@ -1022,7 +1108,7 @@ pub(crate) mod tests {
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, false)
verification_key_submission(dkg_client, state, 0, &keypair_path, false)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
@@ -1098,7 +1184,7 @@ pub(crate) mod tests {
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, true)
verification_key_submission(dkg_client, state, 0, &keypair_path, true)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
+5
View File
@@ -124,6 +124,11 @@ pub enum CoconutError {
// I guess we should make this one a bit more detailed
#[error("the provided query arguments were invalid")]
InvalidQueryArguments,
#[error("insufficient number of dealings provided to derive the key")]
InsufficientDealings {
// TODO: details
},
}
impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError {
+57 -23
View File
@@ -8,7 +8,7 @@ use crate::support::storage::NymApiStorage;
use async_trait::async_trait;
use cosmwasm_std::{coin, to_binary, Addr, CosmosMsg, Decimal, WasmMsg};
use cw3::ProposalResponse;
use cw4::MemberResponse;
use cw4::{Cw4Contract, MemberResponse};
use nym_api_requests::coconut::models::{IssuedCredentialBody, IssuedCredentialResponse};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
@@ -23,16 +23,17 @@ use nym_coconut_bandwidth_contract_common::spend_credential::{
SpendCredential, SpendCredentialResponse,
};
use nym_coconut_dkg_common::dealer::{
ContractDealing, DealerDetails, DealerDetailsResponse, DealerType,
DealerDetails, DealerDetailsResponse, DealerType, DealingStatusResponse,
};
use nym_coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX};
use nym_coconut_dkg_common::types::{
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, TOTAL_DEALINGS,
DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData,
PartialContractDealing, State as ContractState,
};
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use nym_coconut_interface::{hash_to_scalar, Credential, VerificationKey};
use nym_config::defaults::VOUCHER_INFO;
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_contracts_common::IdentityKey;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_crypto::asymmetric::{encryption, identity};
use nym_dkg::Threshold;
@@ -67,9 +68,12 @@ pub(crate) struct DummyClient {
spent_credential_db: Arc<RwLock<HashMap<String, SpendCredentialResponse>>>,
epoch: Arc<RwLock<Epoch>>,
contract_state: Arc<RwLock<ContractState>>,
dealer_details: Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
threshold: Arc<RwLock<Option<Threshold>>>,
dealings: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
// it's a really bad practice, but I'm not going to be changing it now...
#[allow(clippy::type_complexity)]
dealings: Arc<RwLock<HashMap<EpochId, HashMap<String, Vec<PartialContractDealing>>>>>,
verification_share: Arc<RwLock<HashMap<String, ContractVKShare>>>,
group_db: Arc<RwLock<HashMap<String, MemberResponse>>>,
initial_dealers_db: Arc<RwLock<Option<InitialReplacementData>>>,
@@ -83,6 +87,12 @@ impl DummyClient {
proposal_db: Arc::new(RwLock::new(HashMap::new())),
spent_credential_db: Arc::new(RwLock::new(HashMap::new())),
epoch: Arc::new(RwLock::new(Epoch::default())),
contract_state: Arc::new(RwLock::new(ContractState {
mix_denom: TEST_COIN_DENOM.to_string(),
multisig_addr: Addr::unchecked("dummy address"),
group_addr: Cw4Contract::new(Addr::unchecked("dummy cw4")),
key_size: 5,
})),
dealer_details: Arc::new(RwLock::new(HashMap::new())),
threshold: Arc::new(RwLock::new(None)),
dealings: Arc::new(RwLock::new(HashMap::new())),
@@ -131,9 +141,11 @@ impl DummyClient {
self
}
// it's a really bad practice, but I'm not going to be changing it now...
#[allow(clippy::type_complexity)]
pub fn with_dealings(
mut self,
dealings: &Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
dealings: &Arc<RwLock<HashMap<EpochId, HashMap<String, Vec<PartialContractDealing>>>>>,
) -> Self {
self.dealings = Arc::clone(dealings);
self
@@ -203,6 +215,10 @@ impl super::client::Client for DummyClient {
})
}
async fn contract_state(&self) -> Result<ContractState> {
Ok(self.contract_state.read().unwrap().clone())
}
async fn get_current_epoch(&self) -> Result<Epoch> {
Ok(*self.epoch.read().unwrap())
}
@@ -248,6 +264,21 @@ impl super::client::Client for DummyClient {
})
}
async fn get_dealing_status(
&self,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> crate::coconut::error::Result<DealingStatusResponse> {
let dealings = self.get_dealings(epoch_id, &dealer).await?;
Ok(DealingStatusResponse {
epoch_id,
dealer: Addr::unchecked(dealer),
dealing_index,
dealing_submitted: dealings.get(dealing_index as usize).is_some(),
})
}
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>> {
Ok(self
.dealer_details
@@ -259,17 +290,21 @@ impl super::client::Client for DummyClient {
.collect())
}
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>> {
async fn get_dealings(
&self,
epoch_id: EpochId,
dealer: &str,
) -> Result<Vec<PartialContractDealing>> {
Ok(self
.dealings
.read()
.unwrap()
.iter()
.map(|(dealer, dealings)| ContractDealing {
dealing: dealings.get(idx).unwrap().clone(),
dealer: Addr::unchecked(dealer),
})
.collect())
.get(&epoch_id)
.cloned()
.unwrap_or_default()
.get(dealer)
.cloned()
.unwrap_or_default())
}
async fn get_verification_key_shares(
@@ -322,6 +357,7 @@ impl super::client::Client for DummyClient {
async fn register_dealer(
&self,
bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
identity_key: IdentityKey,
announce_address: String,
_resharing: bool,
) -> Result<ExecuteResult> {
@@ -345,6 +381,7 @@ impl super::client::Client for DummyClient {
DealerDetails {
address: Addr::unchecked(self.validator_address.to_string()),
bte_public_key_with_proof,
ed25519_identity: identity_key,
announce_address,
assigned_index,
},
@@ -367,19 +404,16 @@ impl super::client::Client for DummyClient {
async fn submit_dealing(
&self,
dealing_bytes: ContractSafeBytes,
dealing: PartialContractDealing,
_resharing: bool,
) -> Result<ExecuteResult> {
self.dealings
.write()
.unwrap()
let current_epoch = self.epoch.read().unwrap().epoch_id;
let mut guard = self.dealings.write().unwrap();
let epoch_dealings = guard.entry(current_epoch).or_default();
let existing_dealings = epoch_dealings
.entry(self.validator_address.to_string())
.and_modify(|v| {
if v.len() < TOTAL_DEALINGS {
v.push(dealing_bytes.clone())
}
})
.or_insert_with(|| vec![dealing_bytes]);
.or_default();
existing_dealings.push(dealing);
Ok(ExecuteResult {
logs: vec![],
+2
View File
@@ -70,6 +70,7 @@ async fn start_nym_api_tasks(config: Config) -> anyhow::Result<ShutdownHandles>
let coconut_keypair = coconut::keypair::KeyPair::new();
let identity_keypair = config.base.storage_paths.load_identity()?;
let identity_public_key = *identity_keypair.public_key();
// let's build our rocket!
let rocket = http::setup_rocket(
@@ -137,6 +138,7 @@ async fn start_nym_api_tasks(config: Config) -> anyhow::Result<ShutdownHandles>
&config.coconut_signer,
nyxd_client.clone(),
coconut_keypair,
identity_public_key,
OsRng,
&shutdown,
)
+32 -9
View File
@@ -9,15 +9,17 @@ use async_trait::async_trait;
use cw3::ProposalResponse;
use cw4::MemberResponse;
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use nym_coconut_dkg_common::dealer::DealingStatusResponse;
use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use nym_coconut_dkg_common::types::InitialReplacementData;
use nym_coconut_dkg_common::types::{
DealingIndex, InitialReplacementData, PartialContractDealing, State,
};
use nym_coconut_dkg_common::{
dealer::{ContractDealing, DealerDetails, DealerDetailsResponse},
dealer::{DealerDetails, DealerDetailsResponse},
types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId},
verification_key::{ContractVKShare, VerificationKeyShare},
};
use nym_config::defaults::{ChainDetails, NymNetworkDetails};
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_ephemera_common::msg::QueryMsg as EphemeraQueryMsg;
use nym_ephemera_common::types::JsonPeerInfo;
use nym_mixnet_contract_common::families::FamilyHead;
@@ -374,6 +376,10 @@ impl crate::coconut::client::Client for Client {
))
}
async fn contract_state(&self) -> crate::coconut::error::Result<State> {
Ok(nyxd_query!(self, get_state().await?))
}
async fn get_current_epoch(&self) -> crate::coconut::error::Result<Epoch> {
Ok(nyxd_query!(self, get_current_epoch().await?))
}
@@ -401,15 +407,31 @@ impl crate::coconut::client::Client for Client {
Ok(nyxd_query!(self, get_dealer_details(self_address).await?))
}
async fn get_dealing_status(
&self,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> crate::coconut::error::Result<DealingStatusResponse> {
Ok(nyxd_query!(
self,
get_dealing_status(epoch_id, dealer, dealing_index).await?
))
}
async fn get_current_dealers(&self) -> crate::coconut::error::Result<Vec<DealerDetails>> {
Ok(nyxd_query!(self, get_all_current_dealers().await?))
}
async fn get_dealings(
&self,
idx: usize,
) -> crate::coconut::error::Result<Vec<ContractDealing>> {
Ok(nyxd_query!(self, get_all_epoch_dealings(idx as u64).await?))
epoch_id: EpochId,
dealer: &str,
) -> crate::coconut::error::Result<Vec<PartialContractDealing>> {
Ok(nyxd_query!(
self,
get_all_dealer_dealings(epoch_id, dealer).await?
))
}
async fn get_verification_key_shares(
@@ -445,23 +467,24 @@ impl crate::coconut::client::Client for Client {
async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
identity_key: IdentityKey,
announce_address: String,
resharing: bool,
) -> Result<ExecuteResult, CoconutError> {
Ok(nyxd_signing!(
self,
register_dealer(bte_key, announce_address, resharing, None).await?
register_dealer(bte_key, announce_address, identity_key, resharing, None).await?
))
}
async fn submit_dealing(
&self,
dealing_bytes: ContractSafeBytes,
dealing: PartialContractDealing,
resharing: bool,
) -> Result<ExecuteResult, CoconutError> {
Ok(nyxd_signing!(
self,
submit_dealing_bytes(dealing_bytes, resharing, None).await?
submit_dealing_bytes(dealing, resharing, None).await?
))
}
+2
View File
@@ -3814,6 +3814,8 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw2",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]
+2
View File
@@ -3220,6 +3220,8 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw2",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]