Compare commits

...

15 Commits

Author SHA1 Message Date
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
43 changed files with 1542 additions and 543 deletions
Generated
+1
View File
@@ -6309,6 +6309,7 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]
@@ -8,9 +8,12 @@ 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, 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,6 +25,11 @@ 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
@@ -64,14 +72,31 @@ pub trait DkgQueryClient {
self.query_dkg_contract(request).await
}
async fn get_dealings_paged(
async fn get_dealing(
&self,
idx: u64,
start_after: Option<String>,
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,
};
@@ -106,8 +131,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 +180,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()
@@ -166,10 +196,18 @@ mod tests {
client.get_past_dealers_paged(start_after, limit).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,
@@ -10,9 +10,8 @@ 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;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
@@ -58,14 +57,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
@@ -153,11 +149,8 @@ mod tests {
} => client
.register_dealer(bte_key_with_proof, 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,7 @@ license.workspace = true
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
cw-utils = { 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;
@@ -66,35 +69,39 @@ impl PagedDealerResponse {
}
#[cw_serde]
pub struct ContractDealing {
pub dealing: ContractSafeBytes,
pub dealer: Addr,
}
pub struct DealingResponse {
pub epoch_id: EpochId,
impl ContractDealing {
pub fn new(dealing: ContractSafeBytes, dealer: Addr) -> Self {
ContractDealing { dealing, dealer }
}
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub dealing: Option<ContractDealing>,
}
#[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,15 +1,17 @@
// 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, PagedDealerResponse, PagedDealingsResponse},
types::{Epoch, InitialReplacementData, State},
verification_key::PagedVKSharesResponse,
};
#[cfg(feature = "schema")]
@@ -21,6 +23,9 @@ 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]
@@ -32,7 +37,7 @@ pub enum ExecuteMsg {
},
CommitDealing {
dealing_bytes: ContractSafeBytes,
dealing: PartialContractDealing,
resharing: bool,
},
@@ -55,6 +60,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 +87,19 @@ pub enum QueryMsg {
start_after: Option<String>,
},
#[cfg_attr(feature = "schema", returns(PagedDealingsResponse))]
#[cfg_attr(feature = "schema", returns(DealingResponse))]
GetDealing {
idx: u64,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
},
#[cfg_attr(feature = "schema", returns(PagedDealingsResponse))]
GetDealings {
epoch_id: EpochId,
dealer: String,
limit: Option<u32>,
start_after: Option<String>,
start_after: Option<DealingIndex>,
},
#[cfg_attr(feature = "schema", returns(PagedVKSharesResponse))]
@@ -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 {
+1 -1
View File
@@ -1222,7 +1222,6 @@ dependencies = [
"cw-storage-plus",
"cw4",
"cw4-group",
"lazy_static",
"nym-coconut-dkg-common",
"nym-group-contract-common",
"rusty-fork",
@@ -1237,6 +1236,7 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]
-1
View File
@@ -28,7 +28,6 @@ thiserror = { workspace = true }
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]
+211 -37
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"
},
@@ -122,12 +129,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 +234,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 +259,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": [
@@ -361,10 +399,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 +454,11 @@
},
"start_after": {
"type": [
"string",
"integer",
"null"
]
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
@@ -776,34 +853,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 +891,55 @@
"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_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 +948,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
}
}
},
@@ -943,6 +1074,49 @@
}
}
},
"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",
+21 -3
View File
@@ -40,12 +40,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 +145,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"
},
+56 -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": [
@@ -129,10 +142,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 +197,11 @@
},
"start_after": {
"type": [
"string",
"integer",
"null"
]
],
"format": "uint32",
"minimum": 0.0
}
},
"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,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
}
}
}
@@ -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"
}
]
}
}
}
+27 -11
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_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,7 @@ 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};
/// Instantiate the contract.
///
@@ -39,7 +40,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 +50,7 @@ pub fn instantiate(
group_addr,
multisig_addr,
mix_denom: msg.mix_denom,
key_size: msg.key_size,
};
STATE.save(deps.storage, &state)?;
@@ -79,10 +81,9 @@ pub fn execute(
announce_address,
resharing,
} => try_add_dealer(deps, info, bte_key_with_proof, announce_address, resharing),
ExecuteMsg::CommitDealing {
dealing_bytes,
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 +98,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)?)?
@@ -112,10 +114,22 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
to_binary(&query_past_dealers_paged(deps, start_after, limit)?)?
}
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,
@@ -141,7 +155,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 +192,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 +228,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", &[]);
@@ -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};
+203 -142
View File
@@ -2,44 +2,53 @@
// SPDX-License-Identifier: Apache-2.0
use crate::dealings::storage;
use crate::dealings::storage::DEALINGS_BYTES;
use cosmwasm_std::{Deps, Order, StdResult};
use crate::dealings::storage::StoredDealing;
use cosmwasm_std::{Deps, StdResult};
use cw_storage_plus::Bound;
use nym_coconut_dkg_common::dealer::{ContractDealing, PagedDealingsResponse};
use nym_coconut_dkg_common::types::TOTAL_DEALINGS;
use nym_coconut_dkg_common::dealer::{DealingResponse, PagedDealingsResponse};
use nym_coconut_dkg_common::types::{DealingIndex, EpochId};
pub fn query_dealing(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> StdResult<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 +57,200 @@ 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);
}
#[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,8 +101,8 @@ 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 {
@@ -114,8 +131,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 +142,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()
+21 -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,6 +44,25 @@ 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 },
+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,7 +20,14 @@ 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 {
@@ -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");
@@ -87,6 +84,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;
@@ -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(
+11 -5
View File
@@ -5,12 +5,12 @@ 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};
use nym_coconut_dkg_common::types::{
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData,
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, PartialContractDealing,
State,
};
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_dkg::Threshold;
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
use nym_validator_client::nyxd::{AccountId, Fee, Hash, TxResponse};
@@ -25,13 +25,19 @@ 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_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<()>;
@@ -45,7 +51,7 @@ pub trait Client {
) -> Result<ExecuteResult>;
async fn submit_dealing(
&self,
dealing_bytes: ContractSafeBytes,
dealing: PartialContractDealing,
resharing: bool,
) -> Result<ExecuteResult>;
async fn submit_verification_key_share(
+26 -8
View File
@@ -5,22 +5,24 @@ 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,
PartialContractDealing, State as ContractState,
};
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use nym_contracts_common::dealings::ContractSafeBytes;
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 +46,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())
@@ -79,14 +94,16 @@ impl DkgClient {
pub(crate) async fn get_dealings(
&self,
idx: usize,
) -> Result<Vec<ContractDealing>, CoconutError> {
let mut ret = self.inner.get_dealings(idx).await;
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;
}
ret = self.inner.get_dealings(idx).await;
tokio::time::sleep(Duration::from_millis(200)).await;
ret = self.inner.get_dealings(epoch_id, &dealer).await;
}
ret
}
@@ -131,10 +148,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 +168,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)
+1
View File
@@ -140,6 +140,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
verification_key_submission(
&self.dkg_client,
&mut self.state,
epoch.epoch_id,
&keypair_path,
resharing,
)
+45 -22
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,9 @@ 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 dealers = dkg_client.get_current_dealers().await?;
let threshold = dkg_client.get_current_epoch_threshold().await?;
let initial_dealers = dkg_client
@@ -44,7 +46,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,8 +66,13 @@ 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
);
debug!(
"Submitting dealing for indexes {:?} with resharing: {}",
receivers.keys().collect::<Vec<_>>(),
@@ -73,14 +80,18 @@ pub(crate) async fn dealing_exchange(
);
let (dealing, _) = Dealing::create(
rng.clone(),
&params,
params,
dealer_index,
state.threshold()?,
&receivers,
prior_resharing_secrets.pop_front(),
);
let contract_dealing =
PartialContractDealing::new(dealing_index, ContractDealing::from(&dealing));
dkg_client
.submit_dealing(ContractSafeBytes::from(&dealing), resharing)
.submit_dealing(contract_dealing, resharing)
.await?;
}
} else {
@@ -160,16 +171,18 @@ pub(crate) mod tests {
.with_dealings(&dealings_db)
.with_threshold(&threshold_db),
);
let params = setup();
let params = dkg::params();
let mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
DkgKeyPair::new(params, OsRng),
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 +200,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 +213,8 @@ pub(crate) mod tests {
let new_dealings = dealings_db
.read()
.unwrap()
.get(&0)
.unwrap()
.get(TEST_VALIDATORS_ADDRESS[0])
.unwrap()
.clone();
@@ -217,16 +234,16 @@ pub(crate) mod tests {
.with_dealings(&dealings_db)
.with_threshold(&threshold_db),
);
let params = setup();
let params = dkg::params();
let mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
DkgKeyPair::new(params, OsRng),
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,7 +302,9 @@ 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;
@@ -294,11 +313,11 @@ pub(crate) mod tests {
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
DkgKeyPair::new(params, OsRng),
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 +332,16 @@ 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 mut state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
DkgKeyPair::new(&params, OsRng),
DkgKeyPair::new(params, OsRng),
coconut_keypair,
);
state.set_node_index(Some(self_index));
@@ -340,9 +361,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;
+2 -3
View File
@@ -304,6 +304,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 +365,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 +395,6 @@ impl State {
self.was_in_progress = true;
}
#[cfg(test)]
pub fn all_dealers(&self) -> &BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>> {
&self.dealers
}
+153 -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,9 +359,8 @@ 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_dkg::bte::keys::KeyPair as DkgKeyPair;
use nym_validator_client::nyxd::AccountId;
use rand::rngs::OsRng;
@@ -323,7 +374,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,7 +404,7 @@ 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![];
for addr in TEST_VALIDATORS_ADDRESS {
@@ -364,7 +417,7 @@ 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(),
@@ -403,7 +456,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 +494,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 +512,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 +548,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 +562,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 +582,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 +596,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 +614,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 +653,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 +694,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 +741,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 +758,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,7 +939,7 @@ 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 state = State::new(
PathBuf::default(),
PersistentState::default(),
@@ -906,7 +982,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,7 +1043,7 @@ 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 state = State::new(
PathBuf::default(),
PersistentState::default(),
@@ -986,7 +1062,7 @@ 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 state2 = State::new(
PathBuf::default(),
PersistentState::default(),
@@ -1022,7 +1098,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 +1174,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 {
+39 -25
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,
@@ -22,17 +22,15 @@ use nym_coconut_bandwidth_contract_common::events::{
use nym_coconut_bandwidth_contract_common::spend_credential::{
SpendCredential, SpendCredentialResponse,
};
use nym_coconut_dkg_common::dealer::{
ContractDealing, DealerDetails, DealerDetailsResponse, DealerType,
};
use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse, DealerType};
use nym_coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX};
use nym_coconut_dkg_common::types::{
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, TOTAL_DEALINGS,
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_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_crypto::asymmetric::{encryption, identity};
use nym_dkg::Threshold;
@@ -67,9 +65,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 +84,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 +138,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 +212,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())
}
@@ -259,17 +272,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(
@@ -367,19 +384,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![],
+15 -8
View File
@@ -10,14 +10,13 @@ use cw3::ProposalResponse;
use cw4::MemberResponse;
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use nym_coconut_dkg_common::types::InitialReplacementData;
use nym_coconut_dkg_common::types::{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 +373,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?))
}
@@ -407,9 +410,13 @@ impl crate::coconut::client::Client for Client {
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(
@@ -456,12 +463,12 @@ impl crate::coconut::client::Client for Client {
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?
))
}
+1
View File
@@ -3814,6 +3814,7 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]
+1
View File
@@ -3220,6 +3220,7 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]