dealing metadata storage logic

This commit is contained in:
Jędrzej Stuczyński
2024-01-31 15:46:26 +00:00
parent e7bc50fc4a
commit a7feeaa660
9 changed files with 1010 additions and 660 deletions
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::types::{
ContractDealing, DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, NodeIndex,
PartialContractDealing,
DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, NodeIndex, PartialContractDealing,
PartialContractDealingData,
};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
@@ -77,7 +77,7 @@ pub struct DealingResponse {
pub dealing_index: DealingIndex,
pub dealing: Option<ContractDealing>,
pub dealing: Option<PartialContractDealingData>,
}
#[cw_serde]
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_schema::cw_serde;
use std::collections::BTreeMap;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
@@ -15,32 +16,108 @@ pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str;
pub type NodeIndex = u64;
pub type EpochId = u64;
pub type DealingIndex = u32;
pub type ContractDealing = ContractSafeBytes;
// we really don't need to hold more data than that (even u8 would have been enough),
// but explicitly make it different type than `DealingIndex` so type system would detect any
// accidental misuses
pub type ChunkIndex = u16;
pub type PartialContractDealingData = ContractSafeBytes;
/// Defines the maximum size of a dealing chunk. Currently set to 2kB
pub const MAX_DEALING_CHUNK_SIZE: usize = 2048;
/// Defines the maximum size of a full dealing.
/// Currently set to 100kB (which is enough for a dealing created for 100 parties)
pub const MAX_DEALING_SIZE: usize = 102400;
pub const MAX_DEALING_CHUNKS: usize = MAX_DEALING_SIZE / MAX_DEALING_CHUNK_SIZE;
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
pub const DEFAULT_DEALINGS: usize = 2 + 2 + 1;
#[cw_serde]
pub struct PartialContractDealing {
pub index: DealingIndex,
pub data: ContractDealing,
pub struct SubmittedChunk {
pub size: usize,
// this field is updated by the contract itself to indicate when this particular chunk has been received
pub submission_height: Option<u64>,
}
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,
impl SubmittedChunk {
pub fn new(size: usize) -> Self {
SubmittedChunk {
size,
submission_height: None,
}
}
}
#[cw_serde]
pub struct DealingMetadata {
pub dealing_index: DealingIndex,
pub submitted_chunks: BTreeMap<ChunkIndex, SubmittedChunk>,
}
impl DealingMetadata {
pub fn new(dealing_index: DealingIndex, dealing_len: usize, chunk_size: usize) -> Self {
let (full_chunks, overflow) = (dealing_len / chunk_size, dealing_len % chunk_size);
let mut submitted_chunks = BTreeMap::new();
for id in 0..full_chunks {
submitted_chunks.insert(id as ChunkIndex, SubmittedChunk::new(chunk_size));
}
if overflow != 0 {
submitted_chunks.insert(full_chunks as ChunkIndex, SubmittedChunk::new(overflow));
}
DealingMetadata {
dealing_index,
submitted_chunks,
}
}
pub fn is_complete(&self) -> bool {
self.submitted_chunks
.values()
.all(|c| c.submission_height.is_some())
}
pub fn total_size(&self) -> usize {
self.submitted_chunks.values().map(|c| c.size).sum()
}
}
#[cw_serde]
pub struct PartialContractDealing {
pub dealing_index: DealingIndex,
pub chunk_index: ChunkIndex,
pub data: PartialContractDealingData,
}
impl PartialContractDealing {
pub fn new(
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
data: PartialContractDealingData,
) -> Self {
PartialContractDealing {
dealing_index,
chunk_index,
data,
}
}
}
// impl From<(DealingIndex, ContractDealing)> for PartialContractDealing {
// fn from(value: (DealingIndex, ContractDealing)) -> Self {
// PartialContractDealing {
// dealing_index: value.0,
// data: value.1,
// }
// }
// }
#[cw_serde]
pub struct InitialReplacementData {
pub initial_dealers: Vec<Addr>,
@@ -277,3 +354,37 @@ impl EpochState {
matches!(self, EpochState::InProgress)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dealing_metadata() {
const CHUNK_SIZE: usize = 512;
let test_cases = [
(CHUNK_SIZE - 10, CHUNK_SIZE, 1),
(CHUNK_SIZE, CHUNK_SIZE, 1),
(CHUNK_SIZE + 10, CHUNK_SIZE, 2),
(CHUNK_SIZE * 2, CHUNK_SIZE, 2),
(CHUNK_SIZE * 2 + 1, CHUNK_SIZE, 3),
(CHUNK_SIZE * 10 + 42, CHUNK_SIZE, 11),
];
for (dealing_len, chunk_size, expected_chunks) in test_cases {
let meta = DealingMetadata::new(42, dealing_len, chunk_size);
assert_eq!(expected_chunks, meta.submitted_chunks.len());
assert_eq!(dealing_len, meta.total_size());
let mut expected_last = dealing_len % chunk_size;
if expected_last == 0 {
expected_last = chunk_size;
}
assert_eq!(
meta.submitted_chunks.last_key_value().unwrap().1.size,
expected_last
);
}
}
}
@@ -10,8 +10,8 @@ use std::ops::Deref;
// currently set to 128 bytes
pub const MAX_DISPLAY_SIZE: usize = 128;
// TODO: if we are to use this for different types, it might make sense to introduce something like
// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait
// helps to transfer bytes between contract boundary to decrease amount of data sent accross
// after it's put to `Binary`
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)]
pub struct ContractSafeBytes(pub Vec<u8>);
+2 -2
View File
@@ -6,7 +6,7 @@ use crate::dealers::queries::{
};
use crate::dealers::transactions::try_add_dealer;
use crate::dealings::queries::{query_dealing, query_dealing_status, query_dealings_paged};
use crate::dealings::transactions::try_commit_dealings;
use crate::dealings::transactions::try_commit_dealings_chunk;
use crate::epoch_state::queries::{
query_current_epoch, query_current_epoch_threshold, query_initial_dealers,
};
@@ -101,7 +101,7 @@ pub fn execute(
resharing,
),
ExecuteMsg::CommitDealing { dealing, resharing } => {
try_commit_dealings(deps, info, dealing, resharing)
try_commit_dealings_chunk(deps, info, dealing, resharing)
}
ExecuteMsg::CommitVerificationKeyShare { share, resharing } => {
try_commit_verification_key_share(deps, env, info, share, resharing)
+260 -258
View File
@@ -35,15 +35,16 @@ pub fn query_dealing(
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,
})
todo!()
// 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(
@@ -53,254 +54,255 @@ pub fn query_dealings_paged(
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);
let dealer = deps.api.addr_validate(&dealer)?;
let start = start_after.map(Bound::exclusive);
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.index);
Ok(PagedDealingsResponse::new(
epoch_id,
dealer,
dealings,
start_next_after,
))
todo!()
// let limit = limit
// .unwrap_or(storage::DEALINGS_PAGE_DEFAULT_LIMIT)
// .min(storage::DEALINGS_PAGE_MAX_LIMIT);
//
// let dealer = deps.api.addr_validate(&dealer)?;
// let start = start_after.map(Bound::exclusive);
//
// 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.dealing_index);
//
// Ok(PagedDealingsResponse::new(
// epoch_id,
// dealer,
// dealings,
// start_next_after,
// ))
}
#[cfg(test)]
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, 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<'_>, 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 test_query_dealing() {
let mut deps = init_contract();
let bad_address = "FOOMP".to_string();
assert!(query_dealing(deps.as_ref(), 0, bad_address, 0).is_err());
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());
// insert the dealing
let dealing = partial_dealing_fixture();
StoredDealing::save(
deps.as_mut().storage,
0,
&Addr::unchecked("foo"),
dealing.clone(),
);
let retrieved = query_dealing(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
assert_eq!(retrieved.epoch_id, 0);
assert_eq!(retrieved.dealing_index, dealing.index);
assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
assert_eq!(retrieved.dealing.unwrap(), dealing.data);
}
#[test]
fn test_query_dealing_status() {
let mut deps = init_contract();
let bad_address = "FOOMP".to_string();
assert!(query_dealing_status(deps.as_ref(), 0, bad_address, 0).is_err());
let empty = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
assert_eq!(empty.epoch_id, 0);
assert_eq!(empty.dealing_index, 0);
assert_eq!(empty.dealer, Addr::unchecked("foo"));
assert!(!empty.dealing_submitted);
// insert the dealing
let dealing = partial_dealing_fixture();
StoredDealing::save(
deps.as_mut().storage,
0,
&Addr::unchecked("foo"),
dealing.clone(),
);
let retrieved = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
assert_eq!(retrieved.epoch_id, 0);
assert_eq!(retrieved.dealing_index, dealing.index);
assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
assert!(retrieved.dealing_submitted)
}
#[cfg(test)]
mod query_dealings {
use super::*;
use nym_coconut_dkg_common::types::DEFAULT_DEALINGS;
#[test]
fn dealings_empty_on_init() {
let deps = init_contract();
let all_dealings = StoredDealing::unchecked_all_entries(&deps.storage);
assert!(all_dealings.is_empty())
}
#[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 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);
}
}
#[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 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();
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(), 0, 10, DEFAULT_DEALINGS as u32);
// 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();
// 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());
}
}
}
}
// #[cfg(test)]
// 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, 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<'_>, 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 {
// dealing_index: dealing_index,
// data: dealing_bytes_fixture(),
// },
// )
// }
// }
// }
//
// #[test]
// fn test_query_dealing() {
// let mut deps = init_contract();
//
// let bad_address = "FOOMP".to_string();
// assert!(query_dealing(deps.as_ref(), 0, bad_address, 0).is_err());
//
// 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());
//
// // insert the dealing
// let dealing = partial_dealing_fixture();
// StoredDealing::save(
// deps.as_mut().storage,
// 0,
// &Addr::unchecked("foo"),
// dealing.clone(),
// );
//
// 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.dealing_index);
// assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
// assert_eq!(retrieved.dealing.unwrap(), dealing.data);
// }
//
// #[test]
// fn test_query_dealing_status() {
// let mut deps = init_contract();
//
// let bad_address = "FOOMP".to_string();
// assert!(query_dealing_status(deps.as_ref(), 0, bad_address, 0).is_err());
//
// let empty = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
// assert_eq!(empty.epoch_id, 0);
// assert_eq!(empty.dealing_index, 0);
// assert_eq!(empty.dealer, Addr::unchecked("foo"));
// assert!(!empty.dealing_submitted);
//
// // insert the dealing
// let dealing = partial_dealing_fixture();
// StoredDealing::save(
// deps.as_mut().storage,
// 0,
// &Addr::unchecked("foo"),
// dealing.clone(),
// );
//
// let retrieved = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
// assert_eq!(retrieved.epoch_id, 0);
// assert_eq!(retrieved.dealing_index, dealing.dealing_index);
// assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
// assert!(retrieved.dealing_submitted)
// }
//
// #[cfg(test)]
// mod query_dealings {
// use super::*;
// use nym_coconut_dkg_common::types::DEFAULT_DEALINGS;
//
// #[test]
// fn dealings_empty_on_init() {
// let deps = init_contract();
// let all_dealings = StoredDealing::unchecked_all_entries(&deps.storage);
// assert!(all_dealings.is_empty())
// }
//
// #[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 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);
// }
// }
//
// #[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 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();
//
// 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(), 0, 10, DEFAULT_DEALINGS as u32);
//
// // 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();
//
// // 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());
// }
// }
// }
// }
+283 -225
View File
@@ -1,10 +1,12 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::ContractError;
use cosmwasm_std::{Addr, Order, Record, StdResult, Storage};
use cw_storage_plus::{Bound, Key, KeyDeserialize, Path, Prefix, Prefixer, PrimaryKey};
use cw_storage_plus::{Bound, Key, KeyDeserialize, Map, Path, Prefix, Prefixer, PrimaryKey};
use nym_coconut_dkg_common::types::{
ContractDealing, ContractSafeBytes, DealingIndex, EpochId, PartialContractDealing,
ChunkIndex, ContractSafeBytes, DealingIndex, DealingMetadata, EpochId, PartialContractDealing,
PartialContractDealingData,
};
pub(crate) const DEALINGS_PAGE_MAX_LIMIT: u32 = 2;
@@ -12,54 +14,105 @@ pub(crate) const DEALINGS_PAGE_DEFAULT_LIMIT: u32 = 1;
type Dealer<'a> = &'a Addr;
/// Metadata for a dealing for given `EpochId`, submitted by particular `Dealer` for given `DealingIndex`.
const DEALINGS_METADATA: Map<(EpochId, Dealer, DealingIndex), DealingMetadata> =
Map::new("dealings_metadata");
pub(crate) fn metadata_exists(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> bool {
DEALINGS_METADATA.has(storage, (epoch_id, dealer, dealing_index))
}
pub(crate) fn may_read_metadata(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> Result<Option<DealingMetadata>, ContractError> {
Ok(DEALINGS_METADATA.may_load(storage, (epoch_id, dealer, dealing_index))?)
}
pub(crate) fn must_read_metadata(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> Result<DealingMetadata, ContractError> {
DEALINGS_METADATA
.may_load(storage, (epoch_id, dealer, dealing_index))?
.ok_or_else(|| ContractError::UnavailableDealingMetadata {
epoch_id,
dealer: dealer.to_owned(),
dealing_index,
})
}
pub(crate) fn store_metadata(
storage: &mut dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
metadata: &DealingMetadata,
) -> Result<(), ContractError> {
Ok(DEALINGS_METADATA.save(storage, (epoch_id, dealer, dealing_index), metadata)?)
}
// dealings are stored in a multilevel map with the following hierarchy:
// - epoch-id:
// - issuer-address:
// - dealing id:
// - dealing content
// - chunk_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!
pub(crate) struct StoredDealing;
// part of `StoredDealing` to make existence lookup cheaper
// TODO: do it later since we need to chunk the dealings anyway
// pub(crate) struct UNIMPLEMENTED_DealingLookup;
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 deserialize_dealing_record(
kv: Record,
) -> StdResult<(DealingIndex, PartialContractDealingData)> {
todo!()
// 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,
chunk_index: ChunkIndex,
) -> Path<Vec<u8>> {
// just replicate the behaviour from `Map::key`
let key = (epoch_id, dealer, dealing_index);
// note: `PrimaryKey` trait is not implemented for tuple (T, U, V, W), only for up to (T, U, V)
// that's why we create a (T, U, (V, W)) tuple(s) instead
let key = (epoch_id, dealer, (dealing_index, chunk_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"),
)
}
// fn prefix(prefix: (EpochId, Dealer)) -> Prefix<DealingIndex, ContractSafeBytes, DealingIndex> {
// todo!()
// // 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,
@@ -67,7 +120,8 @@ impl StoredDealing {
dealer: &Addr,
dealing_index: DealingIndex,
) -> bool {
StoredDealing::storage_key(epoch_id, dealer, dealing_index).has(storage)
todo!()
// StoredDealing::storage_key(epoch_id, dealer, dealing_index).has(storage)
}
pub(crate) fn save(
@@ -77,7 +131,8 @@ impl StoredDealing {
dealing: PartialContractDealing,
) {
// NOTE: we're storing bytes directly here!
let storage_key = StoredDealing::storage_key(epoch_id, dealer, dealing.index);
let storage_key =
Self::storage_key(epoch_id, dealer, dealing.dealing_index, dealing.chunk_index);
storage.set(&storage_key, dealing.data.as_slice());
}
@@ -86,212 +141,215 @@ impl StoredDealing {
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)
chunk_index: ChunkIndex,
) -> Option<PartialContractDealingData> {
let storage_key = Self::storage_key(epoch_id, dealer, dealing_index, chunk_index);
storage.get(&storage_key).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))
}
// pub(crate) fn prefix_range<'a>(
// storage: &'a dyn Storage,
// prefix: (EpochId, Dealer),
// start: Option<Bound<DealingIndex>>,
// ) -> impl Iterator<Item = StdResult<PartialContractDealing>> + 'a {
// vec![].into_iter()
// // todo!()
// // 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()
) -> Vec<((EpochId, Addr, DealingIndex), PartialContractDealingData)> {
todo!()
// 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);
}
}
// #[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,
// ) -> PartialContractDealingData {
// 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 {
// dealing_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 {
// dealing_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().dealing_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().dealing_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);
// }
// }
+274 -141
View File
@@ -2,60 +2,192 @@
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage as dealers_storage;
use crate::dealings::storage::StoredDealing;
use crate::dealings::storage::{
metadata_exists, must_read_metadata, store_metadata, 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::{EpochState, PartialContractDealing};
use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, Storage};
use nym_coconut_dkg_common::types::{
DealingMetadata, EpochState, PartialContractDealing, MAX_DEALING_CHUNKS,
};
pub fn try_commit_dealings(
deps: DepsMut<'_>,
info: MessageInfo,
dealing: PartialContractDealing,
// make sure the epoch is in the dealing exchange and the message sender is a valid dealer for this epoch
fn ensure_permission(
storage: &dyn Storage,
sender: &Addr,
resharing: bool,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::DealingExchange { resharing })?;
) -> Result<(), ContractError> {
check_epoch_state(storage, EpochState::DealingExchange { resharing })?;
// ensure the sender is a dealer
if dealers_storage::current_dealers()
.may_load(deps.storage, &info.sender)?
.may_load(storage, sender)?
.is_none()
{
return Err(ContractError::NotADealer);
}
if resharing
&& !INITIAL_REPLACEMENT_DATA
.load(deps.storage)?
.load(storage)?
.initial_dealers
.contains(&info.sender)
.contains(sender)
{
return Err(ContractError::NotAnInitialDealer);
}
Ok(())
}
pub fn try_submit_dealings_metadata(
deps: DepsMut,
info: MessageInfo,
metadata: DealingMetadata,
resharing: bool,
) -> Result<Response, ContractError> {
ensure_permission(deps.storage, &info.sender, resharing)?;
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
// don't allow overwriting existing metadata
if metadata_exists(
deps.storage,
epoch.epoch_id,
&info.sender,
metadata.dealing_index,
) {
return Err(ContractError::MetadataAlreadyExists {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: metadata.dealing_index,
});
}
// make sure the metadata is not empty
if metadata.submitted_chunks.is_empty() {
return Err(ContractError::EmptyMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: metadata.dealing_index,
});
}
// make sure the dealing has non-zero size
if metadata.total_size() == 0 {
return Err(ContractError::EmptyMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: metadata.dealing_index,
});
}
// make sure the dealing index is in the allowed range
// note: dealing indexing starts from 0
if dealing.index >= state.key_size {
if metadata.dealing_index >= state.key_size {
return Err(ContractError::DealingOutOfRange {
epoch_id: epoch.epoch_id,
dealer: info.sender,
index: dealing.index,
index: metadata.dealing_index,
key_size: state.key_size,
});
}
// 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 {
let chunks = metadata.submitted_chunks.len();
// make sure the number of dealing chunks is in the allowed range
// to prevent somebody splitting their dealings into 10B chunks
if chunks > MAX_DEALING_CHUNKS {
return Err(ContractError::TooFragmentedMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
index: dealing.index,
dealing_index: metadata.dealing_index,
chunks,
});
}
// make sure all chunks, but the last one, have the same size
// SAFETY: we checked for whether `metadata.submitted_chunks` is empty and returned an error in that case
#[allow(clippy::unwrap_used)]
let first_chunk_size = metadata.submitted_chunks.first_key_value().unwrap().1.size;
for (&chunk_index, chunk_info) in metadata.submitted_chunks.iter().take(chunks - 1) {
if chunk_info.size != first_chunk_size {
return Err(ContractError::UnevenChunkSplit {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: metadata.dealing_index,
chunk_index,
first_chunk_size,
size: chunk_info.size,
});
}
}
// finally, store the metadata
store_metadata(
deps.storage,
epoch.epoch_id,
&info.sender,
metadata.dealing_index,
&metadata,
)?;
Ok(Response::new())
}
pub fn try_commit_dealings_chunk(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
dealing: PartialContractDealing,
resharing: bool,
) -> Result<Response, ContractError> {
ensure_permission(deps.storage, &info.sender, resharing)?;
let epoch = CURRENT_EPOCH.load(deps.storage)?;
// read meta
let mut metadata = must_read_metadata(
deps.storage,
epoch.epoch_id,
&info.sender,
dealing.dealing_index,
)?;
// check if the received chunk is within the declared range
let Some(submission_status) = metadata.submitted_chunks.get_mut(&dealing.chunk_index) else {
return Err(ContractError::DealingChunkNotInMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: dealing.dealing_index,
chunk_index: dealing.chunk_index,
});
};
// check if this dealer has already committed this particular dealing chunk
if let Some(submission_height) = submission_status.submission_height {
return Err(ContractError::DealingChunkAlreadyCommitted {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: dealing.dealing_index,
chunk_index: dealing.chunk_index,
block_height: submission_height,
});
}
// update the metadata
submission_status.submission_height = Some(env.block.height);
store_metadata(
deps.storage,
epoch.epoch_id,
&info.sender,
dealing.dealing_index,
&metadata,
)?;
// store the dealing
StoredDealing::save(deps.storage, epoch.epoch_id, &info.sender, dealing);
Ok(Response::new())
@@ -78,127 +210,128 @@ pub(crate) mod tests {
#[test]
fn invalid_commit_dealing() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let owner = Addr::unchecked("owner1");
let info = mock_info(owner.as_str(), &[]);
let dealing = partial_dealing_fixture();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
expected_state: EpochState::DealingExchange { resharing: false }.to_string()
}
);
env.block.time = env
.block
.time
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
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.clone(), false).unwrap_err();
assert_eq!(ret, ContractError::NotADealer);
let dealer_details = DealerDetails {
address: owner.clone(),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: String::new(),
assigned_index: 1,
};
dealers_storage::current_dealers()
.save(deps.as_mut().storage, &owner, &dealer_details)
.unwrap();
// assume we're in resharing mode
CURRENT_EPOCH
.update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
epoch.state = EpochState::DealingExchange { resharing: true };
Ok(epoch)
})
.unwrap();
INITIAL_REPLACEMENT_DATA
.save(
deps.as_mut().storage,
&InitialReplacementData {
initial_dealers: vec![],
initial_height: 1,
},
)
.unwrap();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), true).unwrap_err();
assert_eq!(ret, ContractError::NotAnInitialDealer);
INITIAL_REPLACEMENT_DATA
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
data.initial_dealers = vec![dealer_details_fixture(1).address];
Ok(data)
})
.unwrap();
// 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::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());
todo!()
// let mut deps = helpers::init_contract();
// let mut env = mock_env();
// try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
//
// let owner = Addr::unchecked("owner1");
// let info = mock_info(owner.as_str(), &[]);
// let dealing = partial_dealing_fixture();
//
// let ret =
// try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
// assert_eq!(
// ret,
// ContractError::IncorrectEpochState {
// current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
// expected_state: EpochState::DealingExchange { resharing: false }.to_string()
// }
// );
//
// env.block.time = env
// .block
// .time
// .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
// 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.clone(), false).unwrap_err();
// assert_eq!(ret, ContractError::NotADealer);
//
// let dealer_details = DealerDetails {
// address: owner.clone(),
// bte_public_key_with_proof: String::new(),
// ed25519_identity: String::new(),
// announce_address: String::new(),
// assigned_index: 1,
// };
// dealers_storage::current_dealers()
// .save(deps.as_mut().storage, &owner, &dealer_details)
// .unwrap();
//
// // assume we're in resharing mode
// CURRENT_EPOCH
// .update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
// epoch.state = EpochState::DealingExchange { resharing: true };
// Ok(epoch)
// })
// .unwrap();
// INITIAL_REPLACEMENT_DATA
// .save(
// deps.as_mut().storage,
// &InitialReplacementData {
// initial_dealers: vec![],
// initial_height: 1,
// },
// )
// .unwrap();
// let ret =
// try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), true).unwrap_err();
// assert_eq!(ret, ContractError::NotAnInitialDealer);
//
// INITIAL_REPLACEMENT_DATA
// .update::<_, ContractError>(deps.as_mut().storage, |mut data| {
// data.initial_dealers = vec![dealer_details_fixture(1).address];
// Ok(data)
// })
// .unwrap();
//
// // 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 {
// dealing_index: 42,
// data: ContractSafeBytes(vec![1, 2, 3]),
// },
// false,
// )
// .unwrap_err();
// assert_eq!(
// ret,
// 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());
}
}
+55 -10
View File
@@ -3,7 +3,7 @@
use cosmwasm_std::{Addr, StdError};
use cw_controllers::AdminError;
use nym_coconut_dkg_common::types::{DealingIndex, EpochId};
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId, MAX_DEALING_CHUNKS};
use thiserror::Error;
/// Custom errors for contract failure conditions.
@@ -37,7 +37,7 @@ pub enum ContractError {
EpochNotInitialised,
#[error(
"Requested action needs state to be {expected_state}, currently in state {current_state}, "
"Requested action needs state to be {expected_state}, currently in state {current_state}"
)]
IncorrectEpochState {
current_state: String,
@@ -50,18 +50,24 @@ 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 {
#[error("Dealer {dealer} has already committed dealing chunk for epoch {epoch_id} with dealing index {dealing_index} and chunk index {chunk_index} at height {block_height}")]
DealingChunkAlreadyCommitted {
epoch_id: EpochId,
dealer: Addr,
index: DealingIndex,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
block_height: u64,
},
#[error(
"Dealer {dealer} has attempted to commit dealing for epoch {epoch_id} with index {index} while the key size is set to {key_size}"
)]
#[error("dealer {dealer} tried to commit chunk {chunk_index} of dealing {dealing_index} for epoch {epoch_id}, but it hasn't been declared in the prior metadata")]
DealingChunkNotInMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
},
#[error("dealer {dealer} has attempted to commit dealing chunk for epoch {epoch_id} with dealing index {index} while the key size is set to {key_size}")]
DealingOutOfRange {
epoch_id: EpochId,
dealer: Addr,
@@ -69,6 +75,45 @@ pub enum ContractError {
key_size: u32,
},
#[error("dealer {dealer} has attempted to commit dealing metadata for epoch {epoch_id} for dealing index {dealing_index} with {chunks} chunks while at most {} chunks are allowed", MAX_DEALING_CHUNKS)]
TooFragmentedMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunks: usize,
},
#[error("")]
UnevenChunkSplit {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
first_chunk_size: usize,
size: usize,
},
#[error("dealer {dealer} has attempted to commit dealing metadata for epoch {epoch_id} for dealing index {dealing_index} zero chunks")]
EmptyMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
},
#[error("metadata for dealing for epoch {epoch_id} from {dealer} at index {dealing_index} does not exist")]
UnavailableDealingMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
},
#[error("metadata for dealing for epoch {epoch_id} from {dealer} at index {dealing_index} already exists")]
MetadataAlreadyExists {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
},
#[error("This dealer has already committed {commitment}")]
AlreadyCommitted { commitment: String },
@@ -24,10 +24,11 @@ pub fn dealing_bytes_fixture() -> ContractSafeBytes {
}
pub fn partial_dealing_fixture() -> PartialContractDealing {
PartialContractDealing {
index: 0,
data: ContractSafeBytes(vec![1, 2, 3]),
}
todo!()
// PartialContractDealing {
// dealing_index: 0,
// data: ContractSafeBytes(vec![1, 2, 3]),
// }
}
pub fn dealer_details_fixture(assigned_index: u64) -> DealerDetails {