Merge pull request #4405 from nymtech/bugfix/further-dkg-changes
Bugfix/further dkg changes
This commit is contained in:
@@ -7,19 +7,19 @@ use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::AccountId;
|
||||
use nym_coconut_dkg_common::types::ChunkIndex;
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_coconut_dkg_common::types::{ChunkIndex, NodeIndex, StateAdvanceResponse};
|
||||
use serde::Deserialize;
|
||||
|
||||
use nym_coconut_dkg_common::dealer::RegisteredDealerDetails;
|
||||
pub use nym_coconut_dkg_common::{
|
||||
dealer::{DealerDetailsResponse, PagedDealerResponse},
|
||||
dealer::{DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse},
|
||||
dealing::{
|
||||
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
||||
DealingMetadataResponse, DealingStatusResponse,
|
||||
},
|
||||
msg::QueryMsg as DkgQueryMsg,
|
||||
types::{
|
||||
DealerDetails, DealingIndex, Epoch, EpochId, EpochState, InitialReplacementData, State,
|
||||
},
|
||||
types::{DealerDetails, DealingIndex, Epoch, EpochId, EpochState, State},
|
||||
verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse},
|
||||
};
|
||||
|
||||
@@ -40,13 +40,25 @@ pub trait DkgQueryClient {
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn can_advance_state(&self) -> Result<StateAdvanceResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::CanAdvanceState {};
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_current_epoch_threshold(&self) -> Result<Option<u64>, NyxdError> {
|
||||
let request = DkgQueryMsg::GetCurrentEpochThreshold {};
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>, NyxdError> {
|
||||
let request = DkgQueryMsg::GetInitialDealers {};
|
||||
async fn get_registered_dealer_details(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RegisteredDealerDetails, NyxdError> {
|
||||
let request = DkgQueryMsg::GetRegisteredDealer {
|
||||
dealer_address: address.to_string(),
|
||||
epoch_id,
|
||||
};
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
@@ -69,12 +81,12 @@ pub trait DkgQueryClient {
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_past_dealers_paged(
|
||||
async fn get_dealer_indices_paged(
|
||||
&self,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedDealerResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetPastDealers { start_after, limit };
|
||||
) -> Result<PagedDealerIndexResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetDealerIndices { start_after, limit };
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
@@ -190,8 +202,8 @@ pub trait PagedDkgQueryClient: DkgQueryClient {
|
||||
collect_paged!(self, get_current_dealers_paged, dealers)
|
||||
}
|
||||
|
||||
async fn get_all_past_dealers(&self) -> Result<Vec<DealerDetails>, NyxdError> {
|
||||
collect_paged!(self, get_past_dealers_paged, dealers)
|
||||
async fn get_all_dealer_indices(&self) -> Result<Vec<(Addr, NodeIndex)>, NyxdError> {
|
||||
collect_paged!(self, get_dealer_indices_paged, indices)
|
||||
}
|
||||
|
||||
async fn get_all_verification_key_shares(
|
||||
@@ -238,18 +250,24 @@ mod tests {
|
||||
match msg {
|
||||
DkgQueryMsg::GetState {} => client.get_state().ignore(),
|
||||
DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(),
|
||||
DkgQueryMsg::CanAdvanceState {} => client.can_advance_state().ignore(),
|
||||
DkgQueryMsg::GetCurrentEpochThreshold {} => {
|
||||
client.get_current_epoch_threshold().ignore()
|
||||
}
|
||||
DkgQueryMsg::GetInitialDealers {} => client.get_initial_dealers().ignore(),
|
||||
DkgQueryMsg::GetRegisteredDealer {
|
||||
dealer_address,
|
||||
epoch_id,
|
||||
} => client
|
||||
.get_registered_dealer_details(&dealer_address.parse().unwrap(), epoch_id)
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetDealerDetails { dealer_address } => client
|
||||
.get_dealer_details(&dealer_address.parse().unwrap())
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetCurrentDealers { limit, start_after } => client
|
||||
.get_current_dealers_paged(start_after, limit)
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetPastDealers { limit, start_after } => {
|
||||
client.get_past_dealers_paged(start_after, limit).ignore()
|
||||
DkgQueryMsg::GetDealerIndices { limit, start_after } => {
|
||||
client.get_dealer_indices_paged(start_after, limit).ignore()
|
||||
}
|
||||
DkgQueryMsg::GetDealingStatus {
|
||||
epoch_id,
|
||||
|
||||
+19
-12
@@ -39,13 +39,6 @@ pub trait DkgSigningClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn surpass_threshold(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::SurpassedThreshold {};
|
||||
|
||||
self.execute_dkg_contract(fee, req, "surpass DKG threshold".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn register_dealer(
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
@@ -85,10 +78,9 @@ pub trait DkgSigningClient {
|
||||
async fn submit_dealing_chunk(
|
||||
&self,
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::CommitDealingsChunk { chunk, resharing };
|
||||
let req = DkgExecuteMsg::CommitDealingsChunk { chunk };
|
||||
|
||||
self.execute_dkg_contract(fee, req, "dealing chunk commitment".to_string(), vec![])
|
||||
.await
|
||||
@@ -130,6 +122,20 @@ pub trait DkgSigningClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn trigger_dkg_reset(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::TriggerReset {};
|
||||
|
||||
self.execute_dkg_contract(fee, req, "trigger DKG reset".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn trigger_dkg_resharing(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::TriggerResharing {};
|
||||
|
||||
self.execute_dkg_contract(fee, req, "trigger DKG resharing".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -192,8 +198,8 @@ mod tests {
|
||||
} => client
|
||||
.submit_dealing_metadata(dealing_index, chunks, resharing, None)
|
||||
.ignore(),
|
||||
DkgExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
|
||||
client.submit_dealing_chunk(chunk, resharing, None).ignore()
|
||||
DkgExecuteMsg::CommitDealingsChunk { chunk } => {
|
||||
client.submit_dealing_chunk(chunk, None).ignore()
|
||||
}
|
||||
DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client
|
||||
.submit_verification_key_share(share, resharing, None)
|
||||
@@ -201,8 +207,9 @@ mod tests {
|
||||
DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => client
|
||||
.verify_verification_key_share(&owner.parse().unwrap(), resharing, None)
|
||||
.ignore(),
|
||||
DkgExecuteMsg::SurpassedThreshold {} => client.surpass_threshold(None).ignore(),
|
||||
DkgExecuteMsg::AdvanceEpochState {} => client.advance_dkg_epoch_state(None).ignore(),
|
||||
DkgExecuteMsg::TriggerReset {} => client.trigger_dkg_reset(None).ignore(),
|
||||
DkgExecuteMsg::TriggerResharing {} => client.trigger_dkg_resharing(None).ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,32 @@ pub struct DealerDetails {
|
||||
pub assigned_index: NodeIndex,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealerRegistrationDetails {
|
||||
pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||
pub ed25519_identity: String,
|
||||
pub announce_address: String,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub enum DealerType {
|
||||
Current,
|
||||
Past,
|
||||
Current { assigned_index: NodeIndex },
|
||||
Past { assigned_index: NodeIndex },
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl DealerType {
|
||||
pub fn is_current(&self) -> bool {
|
||||
matches!(&self, DealerType::Current)
|
||||
matches!(&self, DealerType::Current { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct RegisteredDealerDetails {
|
||||
pub details: Option<DealerRegistrationDetails>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealerDetailsResponse {
|
||||
pub details: Option<DealerDetails>,
|
||||
@@ -65,3 +77,20 @@ impl PagedDealerResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct PagedDealerIndexResponse {
|
||||
pub indices: Vec<(Addr, NodeIndex)>,
|
||||
|
||||
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
|
||||
pub start_next_after: Option<Addr>,
|
||||
}
|
||||
|
||||
impl PagedDealerIndexResponse {
|
||||
pub fn new(indices: Vec<(Addr, NodeIndex)>, start_next_after: Option<Addr>) -> Self {
|
||||
PagedDealerIndexResponse {
|
||||
indices,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,15 @@ use cosmwasm_schema::cw_serde;
|
||||
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::{
|
||||
dealer::{DealerDetailsResponse, PagedDealerResponse},
|
||||
dealer::{
|
||||
DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse,
|
||||
RegisteredDealerDetails,
|
||||
},
|
||||
dealing::{
|
||||
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
||||
DealingMetadataResponse, DealingStatusResponse,
|
||||
},
|
||||
types::{Epoch, InitialReplacementData, State},
|
||||
types::{Epoch, State, StateAdvanceResponse},
|
||||
verification_key::{PagedVKSharesResponse, VkShareResponse},
|
||||
};
|
||||
#[cfg(feature = "schema")]
|
||||
@@ -53,7 +56,6 @@ pub enum ExecuteMsg {
|
||||
|
||||
CommitDealingsChunk {
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
CommitVerificationKeyShare {
|
||||
@@ -66,9 +68,11 @@ pub enum ExecuteMsg {
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
SurpassedThreshold {},
|
||||
|
||||
AdvanceEpochState {},
|
||||
|
||||
TriggerReset {},
|
||||
|
||||
TriggerResharing {},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
@@ -83,8 +87,14 @@ pub enum QueryMsg {
|
||||
#[cfg_attr(feature = "schema", returns(u64))]
|
||||
GetCurrentEpochThreshold {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(Option<InitialReplacementData>))]
|
||||
GetInitialDealers {},
|
||||
#[cfg_attr(feature = "schema", returns(StateAdvanceResponse))]
|
||||
CanAdvanceState {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(RegisteredDealerDetails))]
|
||||
GetRegisteredDealer {
|
||||
dealer_address: String,
|
||||
epoch_id: Option<EpochId>,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(DealerDetailsResponse))]
|
||||
GetDealerDetails { dealer_address: String },
|
||||
@@ -95,8 +105,8 @@ pub enum QueryMsg {
|
||||
start_after: Option<String>,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(PagedDealerResponse))]
|
||||
GetPastDealers {
|
||||
#[cfg_attr(feature = "schema", returns(PagedDealerIndexResponse))]
|
||||
GetDealerIndices {
|
||||
limit: Option<u32>,
|
||||
start_after: Option<String>,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ use cosmwasm_schema::cw_serde;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use crate::dealer::{DealerDetails, PagedDealerResponse};
|
||||
pub use crate::dealer::{DealerDetails, DealerRegistrationDetails, PagedDealerResponse};
|
||||
pub use contracts_common::dealings::ContractSafeBytes;
|
||||
pub use cosmwasm_std::{Addr, Coin, Timestamp};
|
||||
pub use cw4::Cw4Contract;
|
||||
@@ -22,9 +22,19 @@ pub type ChunkIndex = u16;
|
||||
pub type PartialContractDealingData = ContractSafeBytes;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct InitialReplacementData {
|
||||
pub initial_dealers: Vec<Addr>,
|
||||
pub initial_height: u64,
|
||||
#[derive(Copy, Default)]
|
||||
pub struct StateAdvanceResponse {
|
||||
pub current_state: EpochState,
|
||||
pub progress: StateProgress,
|
||||
pub deadline: Option<Timestamp>,
|
||||
pub reached_deadline: bool,
|
||||
pub is_complete: bool,
|
||||
}
|
||||
|
||||
impl StateAdvanceResponse {
|
||||
pub fn can_advance(&self) -> bool {
|
||||
self.reached_deadline || self.is_complete
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
@@ -40,6 +50,26 @@ pub struct TimeConfiguration {
|
||||
pub in_progress_time_secs: u64,
|
||||
}
|
||||
|
||||
impl TimeConfiguration {
|
||||
pub fn state_duration(&self, state: EpochState) -> Option<u64> {
|
||||
match state {
|
||||
EpochState::WaitingInitialisation => None,
|
||||
EpochState::PublicKeySubmission { .. } => Some(self.public_key_submission_time_secs),
|
||||
EpochState::DealingExchange { .. } => Some(self.dealing_exchange_time_secs),
|
||||
EpochState::VerificationKeySubmission { .. } => {
|
||||
Some(self.verification_key_submission_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyValidation { .. } => {
|
||||
Some(self.verification_key_validation_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyFinalization { .. } => {
|
||||
Some(self.verification_key_finalization_time_secs)
|
||||
}
|
||||
EpochState::InProgress => Some(self.in_progress_time_secs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TimeConfiguration {
|
||||
type Err = String;
|
||||
|
||||
@@ -87,13 +117,41 @@ pub struct State {
|
||||
pub key_size: u32,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy, Default)]
|
||||
pub struct StateProgress {
|
||||
/// Counts the number of dealers that have registered in this epoch.
|
||||
// ideally we want to have here all group members
|
||||
pub registered_dealers: u32,
|
||||
|
||||
/// Counts the number of resharing dealers that have registered in this epoch.
|
||||
/// This field is only populated during a resharing exchange.
|
||||
/// It is always <= registered_dealers.
|
||||
pub registered_resharing_dealers: u32,
|
||||
|
||||
/// Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.
|
||||
// we expect registered_dealers * state.key_size number of dealings here (each dealer has to submit key_size number of dealings)
|
||||
pub submitted_dealings: u32,
|
||||
|
||||
/// Counts the number of submitted verification key shared from the dealers.
|
||||
// we expect registered_dealers number of keys here
|
||||
pub submitted_key_shares: u32,
|
||||
|
||||
/// Counts the number of verified key shares.
|
||||
// we expect submitted_key_shares number of verified keys here
|
||||
pub verified_keys: u32,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy, Default)]
|
||||
pub struct Epoch {
|
||||
pub state: EpochState,
|
||||
pub epoch_id: EpochId,
|
||||
pub state_progress: StateProgress,
|
||||
pub time_configuration: TimeConfiguration,
|
||||
pub finish_timestamp: Option<Timestamp>,
|
||||
|
||||
#[serde(alias = "finish_timestamp")]
|
||||
pub deadline: Option<Timestamp>,
|
||||
}
|
||||
|
||||
impl Epoch {
|
||||
@@ -103,35 +161,45 @@ impl Epoch {
|
||||
time_configuration: TimeConfiguration,
|
||||
current_timestamp: Timestamp,
|
||||
) -> Self {
|
||||
let duration = match state {
|
||||
EpochState::WaitingInitialisation => None,
|
||||
EpochState::PublicKeySubmission { .. } => {
|
||||
Some(time_configuration.public_key_submission_time_secs)
|
||||
}
|
||||
EpochState::DealingExchange { .. } => {
|
||||
Some(time_configuration.dealing_exchange_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeySubmission { .. } => {
|
||||
Some(time_configuration.verification_key_submission_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyValidation { .. } => {
|
||||
Some(time_configuration.verification_key_validation_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyFinalization { .. } => {
|
||||
Some(time_configuration.verification_key_finalization_time_secs)
|
||||
}
|
||||
EpochState::InProgress => Some(time_configuration.in_progress_time_secs),
|
||||
};
|
||||
let duration = time_configuration.state_duration(state);
|
||||
|
||||
Epoch {
|
||||
state,
|
||||
epoch_id,
|
||||
state_progress: Default::default(),
|
||||
time_configuration,
|
||||
finish_timestamp: duration.map(|d| current_timestamp.plus_seconds(d)),
|
||||
deadline: duration.map(|d| current_timestamp.plus_seconds(d)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(mut self, next_state: EpochState, current_timestamp: Timestamp) -> Self {
|
||||
self.state = next_state;
|
||||
let duration = self.time_configuration.state_duration(next_state);
|
||||
self.deadline = duration.map(|d| current_timestamp.plus_seconds(d));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn next_reset(self, current_timestamp: Timestamp) -> Self {
|
||||
Epoch::new(
|
||||
EpochState::PublicKeySubmission { resharing: false },
|
||||
self.epoch_id + 1,
|
||||
self.time_configuration,
|
||||
current_timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn next_resharing(self, current_timestamp: Timestamp) -> Self {
|
||||
Epoch::new(
|
||||
EpochState::PublicKeySubmission { resharing: true },
|
||||
self.epoch_id + 1,
|
||||
self.time_configuration,
|
||||
current_timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn final_timestamp_secs(&self) -> Option<u64> {
|
||||
let mut finish = self.finish_timestamp?.seconds();
|
||||
let mut finish = self.deadline?.seconds();
|
||||
let time_configuration = self.time_configuration;
|
||||
let mut curr_epoch_state = self.state;
|
||||
while let Some(state) = curr_epoch_state.next() {
|
||||
@@ -256,4 +324,8 @@ impl EpochState {
|
||||
pub fn is_in_progress(&self) -> bool {
|
||||
matches!(self, EpochState::InProgress)
|
||||
}
|
||||
|
||||
pub fn is_dealing_exchange(&self) -> bool {
|
||||
matches!(self, EpochState::DealingExchange { .. })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ where
|
||||
.as_secs();
|
||||
|
||||
if epoch.state.is_final() {
|
||||
if let Some(finish_timestamp) = epoch.finish_timestamp {
|
||||
if let Some(finish_timestamp) = epoch.deadline {
|
||||
if current_timestamp_secs + SAFETY_BUFFER_SECS >= finish_timestamp.seconds() {
|
||||
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
|
||||
exit(0);
|
||||
|
||||
Generated
+3
-1
@@ -178,6 +178,7 @@ dependencies = [
|
||||
name = "coconut-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"cw-controllers",
|
||||
@@ -194,8 +195,10 @@ dependencies = [
|
||||
"nym-coconut-dkg-common",
|
||||
"nym-group-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"rand_chacha 0.2.2",
|
||||
"schemars",
|
||||
"serde",
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -1225,7 +1228,6 @@ dependencies = [
|
||||
"cw4-group",
|
||||
"nym-coconut-dkg-common",
|
||||
"nym-group-contract-common",
|
||||
"rusty-fork",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
|
||||
@@ -30,7 +30,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" }
|
||||
rusty-fork = "0.3"
|
||||
|
||||
[features]
|
||||
schema-gen = ["nym-coconut-dkg-common/schema", "cosmwasm-schema"]
|
||||
|
||||
@@ -180,15 +180,11 @@
|
||||
"commit_dealings_chunk": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"chunk",
|
||||
"resharing"
|
||||
"chunk"
|
||||
],
|
||||
"properties": {
|
||||
"chunk": {
|
||||
"$ref": "#/definitions/PartialContractDealing"
|
||||
},
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -249,10 +245,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"surpassed_threshold"
|
||||
"advance_epoch_state"
|
||||
],
|
||||
"properties": {
|
||||
"surpassed_threshold": {
|
||||
"advance_epoch_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -262,10 +258,23 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"advance_epoch_state"
|
||||
"trigger_reset"
|
||||
],
|
||||
"properties": {
|
||||
"advance_epoch_state": {
|
||||
"trigger_reset": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"trigger_resharing"
|
||||
],
|
||||
"properties": {
|
||||
"trigger_resharing": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -368,16 +377,45 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_initial_dealers"
|
||||
"can_advance_state"
|
||||
],
|
||||
"properties": {
|
||||
"get_initial_dealers": {
|
||||
"can_advance_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_registered_dealer"
|
||||
],
|
||||
"properties": {
|
||||
"get_registered_dealer": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealer_address"
|
||||
],
|
||||
"properties": {
|
||||
"dealer_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"epoch_id": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -431,10 +469,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_past_dealers"
|
||||
"get_dealer_indices"
|
||||
],
|
||||
"properties": {
|
||||
"get_past_dealers": {
|
||||
"get_dealer_indices": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
@@ -716,6 +754,215 @@
|
||||
},
|
||||
"sudo": null,
|
||||
"responses": {
|
||||
"can_advance_state": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "StateAdvanceResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_state",
|
||||
"is_complete",
|
||||
"progress",
|
||||
"reached_deadline"
|
||||
],
|
||||
"properties": {
|
||||
"current_state": {
|
||||
"$ref": "#/definitions/EpochState"
|
||||
},
|
||||
"deadline": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Timestamp"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"is_complete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"progress": {
|
||||
"$ref": "#/definitions/StateProgress"
|
||||
},
|
||||
"reached_deadline": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"EpochState": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"waiting_initialisation",
|
||||
"in_progress"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"public_key_submission"
|
||||
],
|
||||
"properties": {
|
||||
"public_key_submission": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealing_exchange"
|
||||
],
|
||||
"properties": {
|
||||
"dealing_exchange": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_submission"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_submission": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_validation"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_validation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_finalization"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_finalization": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"StateProgress": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"registered_dealers",
|
||||
"registered_resharing_dealers",
|
||||
"submitted_dealings",
|
||||
"submitted_key_shares",
|
||||
"verified_keys"
|
||||
],
|
||||
"properties": {
|
||||
"registered_dealers": {
|
||||
"description": "Counts the number of dealers that have registered in this epoch.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"registered_resharing_dealers": {
|
||||
"description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_dealings": {
|
||||
"description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_key_shares": {
|
||||
"description": "Counts the number of submitted verification key shared from the dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"verified_keys": {
|
||||
"description": "Counts the number of verified key shares.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Timestamp": {
|
||||
"description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Uint64"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Uint64": {
|
||||
"description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_c_w2_contract_version": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ContractVersion",
|
||||
@@ -813,15 +1060,11 @@
|
||||
"required": [
|
||||
"epoch_id",
|
||||
"state",
|
||||
"state_progress",
|
||||
"time_configuration"
|
||||
],
|
||||
"properties": {
|
||||
"epoch_id": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"finish_timestamp": {
|
||||
"deadline": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Timestamp"
|
||||
@@ -831,9 +1074,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"epoch_id": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/EpochState"
|
||||
},
|
||||
"state_progress": {
|
||||
"$ref": "#/definitions/StateProgress"
|
||||
},
|
||||
"time_configuration": {
|
||||
"$ref": "#/definitions/TimeConfiguration"
|
||||
}
|
||||
@@ -956,6 +1207,49 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"StateProgress": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"registered_dealers",
|
||||
"registered_resharing_dealers",
|
||||
"submitted_dealings",
|
||||
"submitted_key_shares",
|
||||
"verified_keys"
|
||||
],
|
||||
"properties": {
|
||||
"registered_dealers": {
|
||||
"description": "Counts the number of dealers that have registered in this epoch.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"registered_resharing_dealers": {
|
||||
"description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_dealings": {
|
||||
"description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_key_shares": {
|
||||
"description": "Counts the number of submitted verification key shared from the dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"verified_keys": {
|
||||
"description": "Counts the number of verified key shares.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"TimeConfiguration": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -1154,15 +1448,109 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DealerType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"current",
|
||||
"past",
|
||||
"unknown"
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current"
|
||||
],
|
||||
"properties": {
|
||||
"current": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"assigned_index"
|
||||
],
|
||||
"properties": {
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"past"
|
||||
],
|
||||
"properties": {
|
||||
"past": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"assigned_index"
|
||||
],
|
||||
"properties": {
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_dealer_indices": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PagedDealerIndexResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"indices"
|
||||
],
|
||||
"properties": {
|
||||
"indices": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
}
|
||||
},
|
||||
"start_next_after": {
|
||||
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_dealing_chunk": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "DealingChunkResponse",
|
||||
@@ -1455,70 +1843,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_initial_dealers": {
|
||||
"get_registered_dealer": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Nullable_InitialReplacementData",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/InitialReplacementData"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"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"
|
||||
},
|
||||
"InitialReplacementData": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"initial_dealers",
|
||||
"initial_height"
|
||||
],
|
||||
"properties": {
|
||||
"initial_dealers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
}
|
||||
},
|
||||
"initial_height": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_past_dealers": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PagedDealerResponse",
|
||||
"title": "RegisteredDealerDetails",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealers",
|
||||
"per_page"
|
||||
],
|
||||
"properties": {
|
||||
"dealers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/DealerDetails"
|
||||
}
|
||||
},
|
||||
"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.",
|
||||
"details": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
"$ref": "#/definitions/DealerRegistrationDetails"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -1528,31 +1861,17 @@
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"DealerDetails": {
|
||||
"DealerRegistrationDetails": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"address",
|
||||
"announce_address",
|
||||
"assigned_index",
|
||||
"bte_public_key_with_proof",
|
||||
"ed25519_identity"
|
||||
],
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
"announce_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"bte_public_key_with_proof": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -91,15 +91,11 @@
|
||||
"commit_dealings_chunk": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"chunk",
|
||||
"resharing"
|
||||
"chunk"
|
||||
],
|
||||
"properties": {
|
||||
"chunk": {
|
||||
"$ref": "#/definitions/PartialContractDealing"
|
||||
},
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -160,10 +156,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"surpassed_threshold"
|
||||
"advance_epoch_state"
|
||||
],
|
||||
"properties": {
|
||||
"surpassed_threshold": {
|
||||
"advance_epoch_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -173,10 +169,23 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"advance_epoch_state"
|
||||
"trigger_reset"
|
||||
],
|
||||
"properties": {
|
||||
"advance_epoch_state": {
|
||||
"trigger_reset": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"trigger_resharing"
|
||||
],
|
||||
"properties": {
|
||||
"trigger_resharing": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
||||
@@ -44,16 +44,45 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_initial_dealers"
|
||||
"can_advance_state"
|
||||
],
|
||||
"properties": {
|
||||
"get_initial_dealers": {
|
||||
"can_advance_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_registered_dealer"
|
||||
],
|
||||
"properties": {
|
||||
"get_registered_dealer": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealer_address"
|
||||
],
|
||||
"properties": {
|
||||
"dealer_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"epoch_id": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -107,10 +136,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_past_dealers"
|
||||
"get_dealer_indices"
|
||||
],
|
||||
"properties": {
|
||||
"get_past_dealers": {
|
||||
"get_dealer_indices": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "StateAdvanceResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_state",
|
||||
"is_complete",
|
||||
"progress",
|
||||
"reached_deadline"
|
||||
],
|
||||
"properties": {
|
||||
"current_state": {
|
||||
"$ref": "#/definitions/EpochState"
|
||||
},
|
||||
"deadline": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Timestamp"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"is_complete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"progress": {
|
||||
"$ref": "#/definitions/StateProgress"
|
||||
},
|
||||
"reached_deadline": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"EpochState": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"waiting_initialisation",
|
||||
"in_progress"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"public_key_submission"
|
||||
],
|
||||
"properties": {
|
||||
"public_key_submission": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealing_exchange"
|
||||
],
|
||||
"properties": {
|
||||
"dealing_exchange": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_submission"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_submission": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_validation"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_validation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_finalization"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_finalization": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"StateProgress": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"registered_dealers",
|
||||
"registered_resharing_dealers",
|
||||
"submitted_dealings",
|
||||
"submitted_key_shares",
|
||||
"verified_keys"
|
||||
],
|
||||
"properties": {
|
||||
"registered_dealers": {
|
||||
"description": "Counts the number of dealers that have registered in this epoch.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"registered_resharing_dealers": {
|
||||
"description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_dealings": {
|
||||
"description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_key_shares": {
|
||||
"description": "Counts the number of submitted verification key shared from the dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"verified_keys": {
|
||||
"description": "Counts the number of verified key shares.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Timestamp": {
|
||||
"description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Uint64"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Uint64": {
|
||||
"description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,11 @@
|
||||
"required": [
|
||||
"epoch_id",
|
||||
"state",
|
||||
"state_progress",
|
||||
"time_configuration"
|
||||
],
|
||||
"properties": {
|
||||
"epoch_id": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"finish_timestamp": {
|
||||
"deadline": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Timestamp"
|
||||
@@ -23,9 +19,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"epoch_id": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/EpochState"
|
||||
},
|
||||
"state_progress": {
|
||||
"$ref": "#/definitions/StateProgress"
|
||||
},
|
||||
"time_configuration": {
|
||||
"$ref": "#/definitions/TimeConfiguration"
|
||||
}
|
||||
@@ -148,6 +152,49 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"StateProgress": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"registered_dealers",
|
||||
"registered_resharing_dealers",
|
||||
"submitted_dealings",
|
||||
"submitted_key_shares",
|
||||
"verified_keys"
|
||||
],
|
||||
"properties": {
|
||||
"registered_dealers": {
|
||||
"description": "Counts the number of dealers that have registered in this epoch.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"registered_resharing_dealers": {
|
||||
"description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_dealings": {
|
||||
"description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_key_shares": {
|
||||
"description": "Counts the number of submitted verification key shared from the dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"verified_keys": {
|
||||
"description": "Counts the number of verified key shares.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"TimeConfiguration": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -57,11 +57,59 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DealerType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"current",
|
||||
"past",
|
||||
"unknown"
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current"
|
||||
],
|
||||
"properties": {
|
||||
"current": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"assigned_index"
|
||||
],
|
||||
"properties": {
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"past"
|
||||
],
|
||||
"properties": {
|
||||
"past": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"assigned_index"
|
||||
],
|
||||
"properties": {
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PagedDealerIndexResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"indices"
|
||||
],
|
||||
"properties": {
|
||||
"indices": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
}
|
||||
},
|
||||
"start_next_after": {
|
||||
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "RegisteredDealerDetails",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"details": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DealerRegistrationDetails"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"DealerRegistrationDetails": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"announce_address",
|
||||
"bte_public_key_with_proof",
|
||||
"ed25519_identity"
|
||||
],
|
||||
"properties": {
|
||||
"announce_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"bte_public_key_with_proof": {
|
||||
"type": "string"
|
||||
},
|
||||
"ed25519_identity": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::dealers::queries::{
|
||||
query_current_dealers_paged, query_dealer_details, query_past_dealers_paged,
|
||||
query_current_dealers_paged, query_dealer_details, query_dealers_indices_paged,
|
||||
query_registered_dealer_details,
|
||||
};
|
||||
use crate::dealers::transactions::try_add_dealer;
|
||||
use crate::dealings::queries::{
|
||||
@@ -11,11 +12,11 @@ use crate::dealings::queries::{
|
||||
};
|
||||
use crate::dealings::transactions::{try_commit_dealings_chunk, try_submit_dealings_metadata};
|
||||
use crate::epoch_state::queries::{
|
||||
query_current_epoch, query_current_epoch_threshold, query_initial_dealers,
|
||||
query_can_advance_state, query_current_epoch, query_current_epoch_threshold,
|
||||
};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::transactions::{
|
||||
advance_epoch_state, try_initiate_dkg, try_surpassed_threshold,
|
||||
try_advance_epoch_state, try_initiate_dkg, try_trigger_reset, try_trigger_resharing,
|
||||
};
|
||||
use crate::error::ContractError;
|
||||
use crate::state::queries::query_state;
|
||||
@@ -108,8 +109,8 @@ pub fn execute(
|
||||
chunks,
|
||||
resharing,
|
||||
} => try_submit_dealings_metadata(deps, info, dealing_index, chunks, resharing),
|
||||
ExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
|
||||
try_commit_dealings_chunk(deps, env, info, chunk, resharing)
|
||||
ExecuteMsg::CommitDealingsChunk { chunk } => {
|
||||
try_commit_dealings_chunk(deps, env, info, chunk)
|
||||
}
|
||||
ExecuteMsg::CommitVerificationKeyShare { share, resharing } => {
|
||||
try_commit_verification_key_share(deps, env, info, share, resharing)
|
||||
@@ -117,28 +118,37 @@ pub fn execute(
|
||||
ExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => {
|
||||
try_verify_verification_key_share(deps, info, owner, resharing)
|
||||
}
|
||||
ExecuteMsg::SurpassedThreshold {} => try_surpassed_threshold(deps, env),
|
||||
ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env),
|
||||
ExecuteMsg::AdvanceEpochState {} => try_advance_epoch_state(deps, env),
|
||||
ExecuteMsg::TriggerReset {} => try_trigger_reset(deps, env, info),
|
||||
ExecuteMsg::TriggerResharing {} => try_trigger_resharing(deps, env, info),
|
||||
}
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
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::CanAdvanceState {} => to_binary(&query_can_advance_state(deps.storage, env)?)?,
|
||||
QueryMsg::GetCurrentEpochThreshold {} => {
|
||||
to_binary(&query_current_epoch_threshold(deps.storage)?)?
|
||||
}
|
||||
QueryMsg::GetInitialDealers {} => to_binary(&query_initial_dealers(deps.storage)?)?,
|
||||
QueryMsg::GetRegisteredDealer {
|
||||
dealer_address,
|
||||
epoch_id,
|
||||
} => to_binary(&query_registered_dealer_details(
|
||||
deps,
|
||||
dealer_address,
|
||||
epoch_id,
|
||||
)?)?,
|
||||
QueryMsg::GetDealerDetails { dealer_address } => {
|
||||
to_binary(&query_dealer_details(deps, dealer_address)?)?
|
||||
}
|
||||
QueryMsg::GetCurrentDealers { limit, start_after } => {
|
||||
to_binary(&query_current_dealers_paged(deps, start_after, limit)?)?
|
||||
}
|
||||
QueryMsg::GetPastDealers { limit, start_after } => {
|
||||
to_binary(&query_past_dealers_paged(deps, start_after, limit)?)?
|
||||
QueryMsg::GetDealerIndices { limit, start_after } => {
|
||||
to_binary(&query_dealers_indices_paged(deps, start_after, limit)?)?
|
||||
}
|
||||
QueryMsg::GetDealingsMetadata {
|
||||
epoch_id,
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::dealers::storage::{self, IndexedDealersMap};
|
||||
use crate::dealers::storage::{
|
||||
self, get_dealer_details, get_dealer_index, get_registration_details, DEALERS_INDICES,
|
||||
EPOCH_DEALERS_MAP,
|
||||
};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use cosmwasm_std::{Deps, Order, StdResult};
|
||||
use cw_storage_plus::Bound;
|
||||
use nym_coconut_dkg_common::dealer::{DealerDetailsResponse, DealerType, PagedDealerResponse};
|
||||
use nym_coconut_dkg_common::dealer::{
|
||||
DealerDetailsResponse, DealerType, PagedDealerIndexResponse, PagedDealerResponse,
|
||||
RegisteredDealerDetails,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, EpochId};
|
||||
|
||||
fn query_dealers(
|
||||
pub fn query_registered_dealer_details(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
underlying_map: &IndexedDealersMap<'_>,
|
||||
) -> StdResult<PagedDealerResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(storage::DEALERS_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::DEALERS_PAGE_MAX_LIMIT) as usize;
|
||||
dealer_address: String,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> StdResult<RegisteredDealerDetails> {
|
||||
let addr = deps.api.addr_validate(&dealer_address)?;
|
||||
|
||||
let addr = start_after
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.transpose()?;
|
||||
let epoch_id = match epoch_id {
|
||||
Some(epoch_id) => epoch_id,
|
||||
None => CURRENT_EPOCH.load(deps.storage)?.epoch_id,
|
||||
};
|
||||
|
||||
let start = addr.as_ref().map(Bound::exclusive);
|
||||
|
||||
let dealers = underlying_map
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
let start_next_after = dealers.last().map(|dealer| dealer.address.clone());
|
||||
|
||||
Ok(PagedDealerResponse::new(dealers, limit, start_next_after))
|
||||
Ok(RegisteredDealerDetails {
|
||||
details: get_registration_details(deps.storage, &addr, epoch_id).ok(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn query_dealer_details(
|
||||
@@ -38,32 +36,93 @@ pub fn query_dealer_details(
|
||||
dealer_address: String,
|
||||
) -> StdResult<DealerDetailsResponse> {
|
||||
let addr = deps.api.addr_validate(&dealer_address)?;
|
||||
if let Some(current) = storage::current_dealers().may_load(deps.storage, &addr)? {
|
||||
let current_epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
|
||||
|
||||
// if the address has registration data for the current epoch, it means it's an active dealer
|
||||
if let Ok(dealer_details) = get_dealer_details(deps.storage, &addr, current_epoch_id) {
|
||||
let assigned_index = dealer_details.assigned_index;
|
||||
return Ok(DealerDetailsResponse::new(
|
||||
Some(current),
|
||||
DealerType::Current,
|
||||
Some(dealer_details),
|
||||
DealerType::Current { assigned_index },
|
||||
));
|
||||
}
|
||||
if let Some(past) = storage::past_dealers().may_load(deps.storage, &addr)? {
|
||||
return Ok(DealerDetailsResponse::new(Some(past), DealerType::Past));
|
||||
|
||||
// and if has had an assigned index it must have been a dealer at some point in the past
|
||||
if let Ok(assigned_index) = get_dealer_index(deps.storage, &addr, current_epoch_id) {
|
||||
return Ok(DealerDetailsResponse::new(
|
||||
None,
|
||||
DealerType::Past { assigned_index },
|
||||
));
|
||||
}
|
||||
|
||||
Ok(DealerDetailsResponse::new(None, DealerType::Unknown))
|
||||
}
|
||||
|
||||
pub fn query_dealers_indices_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedDealerIndexResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(storage::DEALER_INDICES_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::DEALER_INDICES_PAGE_MAX_LIMIT) as usize;
|
||||
let addr = start_after
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.transpose()?;
|
||||
|
||||
let start = addr.as_ref().map(Bound::exclusive);
|
||||
|
||||
let dealers = DEALERS_INDICES
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
let start_next_after = dealers.last().map(|dealer| dealer.0.clone());
|
||||
|
||||
Ok(PagedDealerIndexResponse::new(dealers, start_next_after))
|
||||
}
|
||||
|
||||
pub fn query_current_dealers_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedDealerResponse> {
|
||||
query_dealers(deps, start_after, limit, &storage::current_dealers())
|
||||
}
|
||||
let limit = limit
|
||||
.unwrap_or(storage::DEALERS_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::DEALERS_PAGE_MAX_LIMIT) as usize;
|
||||
let addr = start_after
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.transpose()?;
|
||||
|
||||
pub fn query_past_dealers_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedDealerResponse> {
|
||||
query_dealers(deps, start_after, limit, &storage::past_dealers())
|
||||
let start = addr.as_ref().map(Bound::exclusive);
|
||||
|
||||
let current_epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
|
||||
|
||||
let dealers = EPOCH_DEALERS_MAP
|
||||
.prefix(current_epoch_id)
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| {
|
||||
res.map(|(address, details)| {
|
||||
// SAFETY: if we have DealerRegistrationDetails saved, it means we MUST also have its node index
|
||||
// otherwise some serious invariants have been broken in the contract, and we're in trouble
|
||||
#[allow(clippy::expect_used)]
|
||||
let assigned_index = get_dealer_index(deps.storage, &address, current_epoch_id)
|
||||
.expect("could not retrieve dealer index for a registered dealer");
|
||||
|
||||
DealerDetails {
|
||||
address,
|
||||
bte_public_key_with_proof: details.bte_public_key_with_proof,
|
||||
ed25519_identity: details.ed25519_identity,
|
||||
announce_address: details.announce_address,
|
||||
assigned_index,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
let start_next_after = dealers.last().map(|dealer| dealer.address.clone());
|
||||
|
||||
Ok(PagedDealerResponse::new(dealers, limit, start_next_after))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -71,24 +130,22 @@ pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::dealers::storage::{DEALERS_PAGE_DEFAULT_LIMIT, DEALERS_PAGE_MAX_LIMIT};
|
||||
use crate::support::tests::fixtures::dealer_details_fixture;
|
||||
use crate::support::tests::helpers::init_contract;
|
||||
use crate::support::tests::helpers::{init_contract, insert_dealer};
|
||||
use cosmwasm_std::DepsMut;
|
||||
|
||||
fn fill_dealers(deps: DepsMut<'_>, mapping: &IndexedDealersMap<'_>, size: usize) {
|
||||
for n in 0..size {
|
||||
let dealer_details = dealer_details_fixture(n as u64);
|
||||
mapping
|
||||
.save(deps.storage, &dealer_details.address, &dealer_details)
|
||||
.unwrap();
|
||||
fn fill_dealers(mut deps: DepsMut<'_>, epoch_id: EpochId, size: usize) {
|
||||
for assigned_index in 0..size {
|
||||
let dealer_details = dealer_details_fixture(assigned_index as u64);
|
||||
insert_dealer(deps.branch(), epoch_id, &dealer_details);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_dealers(deps: DepsMut<'_>, mapping: &IndexedDealersMap<'_>, size: usize) {
|
||||
for n in 0..size {
|
||||
let dealer_details = dealer_details_fixture(n as u64);
|
||||
mapping
|
||||
.remove(deps.storage, &dealer_details.address)
|
||||
.unwrap();
|
||||
fn remove_dealers(deps: DepsMut<'_>, epoch_id: EpochId, size: usize) {
|
||||
for assigned_index in 0..size {
|
||||
let dealer_details = dealer_details_fixture(assigned_index as u64);
|
||||
DEALERS_INDICES.remove(deps.storage, &dealer_details.address);
|
||||
|
||||
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &dealer_details.address));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,10 +153,8 @@ pub(crate) mod tests {
|
||||
fn dealers_empty_on_init() {
|
||||
let deps = init_contract();
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
let page1 = query_dealers(deps.as_ref(), None, None, &mapping).unwrap();
|
||||
assert_eq!(0, page1.dealers.len() as u32);
|
||||
}
|
||||
let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(0, page1.dealers.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -107,30 +162,26 @@ pub(crate) mod tests {
|
||||
let mut deps = init_contract();
|
||||
let limit = 2;
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 1000);
|
||||
fill_dealers(deps.as_mut(), 0, 1000);
|
||||
|
||||
let page1 = query_dealers(deps.as_ref(), None, Option::from(limit), &mapping).unwrap();
|
||||
assert_eq!(limit, page1.dealers.len() as u32);
|
||||
let page1 = query_current_dealers_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
|
||||
assert_eq!(limit, page1.dealers.len() as u32);
|
||||
|
||||
remove_dealers(deps.as_mut(), &mapping, 1000);
|
||||
}
|
||||
remove_dealers(deps.as_mut(), 0, 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dealers_paged_retrieval_has_default_limit() {
|
||||
let mut deps = init_contract();
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 1000);
|
||||
fill_dealers(deps.as_mut(), 0, 1000);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_dealers(deps.as_ref(), None, None, &mapping).unwrap();
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap();
|
||||
|
||||
assert_eq!(DEALERS_PAGE_DEFAULT_LIMIT, page1.dealers.len() as u32);
|
||||
assert_eq!(DEALERS_PAGE_DEFAULT_LIMIT, page1.dealers.len() as u32);
|
||||
|
||||
remove_dealers(deps.as_mut(), &mapping, 1000);
|
||||
}
|
||||
remove_dealers(deps.as_mut(), 0, 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -140,18 +191,16 @@ pub(crate) mod tests {
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT;
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 1000);
|
||||
fill_dealers(deps.as_mut(), 0, 1000);
|
||||
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(crazy_limit), &mapping).unwrap();
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DEALERS_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.dealers.len() as u32);
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DEALERS_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.dealers.len() as u32);
|
||||
|
||||
remove_dealers(deps.as_mut(), &mapping, 1000);
|
||||
}
|
||||
remove_dealers(deps.as_mut(), 0, 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -160,62 +209,52 @@ pub(crate) mod tests {
|
||||
|
||||
let per_page = 2;
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 1);
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(per_page), &mapping).unwrap();
|
||||
fill_dealers(deps.as_mut(), 0, 1);
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.dealers.len());
|
||||
remove_dealers(deps.as_mut(), &mapping, 1);
|
||||
}
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.dealers.len());
|
||||
remove_dealers(deps.as_mut(), 0, 1);
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 2);
|
||||
// page1 should have 2 results on it
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(per_page), &mapping).unwrap();
|
||||
assert_eq!(2, page1.dealers.len());
|
||||
remove_dealers(deps.as_mut(), &mapping, 2);
|
||||
}
|
||||
fill_dealers(deps.as_mut(), 0, 2);
|
||||
// page1 should have 2 results on it
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.dealers.len());
|
||||
remove_dealers(deps.as_mut(), 0, 2);
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 3);
|
||||
// page1 still has 2 results
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(per_page), &mapping).unwrap();
|
||||
assert_eq!(2, page1.dealers.len());
|
||||
fill_dealers(deps.as_mut(), 0, 3);
|
||||
// page1 still has 2 results
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.dealers.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_dealers(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
&mapping,
|
||||
)
|
||||
.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_current_dealers_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.dealers.len());
|
||||
remove_dealers(deps.as_mut(), &mapping, 3);
|
||||
}
|
||||
assert_eq!(1, page2.dealers.len());
|
||||
remove_dealers(deps.as_mut(), 0, 3);
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 4);
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(per_page), &mapping).unwrap();
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
let page2 = query_dealers(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
&mapping,
|
||||
)
|
||||
.unwrap();
|
||||
fill_dealers(deps.as_mut(), 0, 4);
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
let page2 = query_current_dealers_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.dealers.len());
|
||||
remove_dealers(deps.as_mut(), &mapping, 4);
|
||||
}
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.dealers.len());
|
||||
remove_dealers(deps.as_mut(), 0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,102 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Addr, StdResult, Storage};
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, UniqueIndex};
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, NodeIndex};
|
||||
use crate::error::ContractError;
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::{StdResult, Storage};
|
||||
use cw_storage_plus::{Item, Map};
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, DealerRegistrationDetails, EpochId, NodeIndex};
|
||||
|
||||
const CURRENT_DEALERS_PK: &str = "crd";
|
||||
const PAST_DEALERS_PK: &str = "ptd";
|
||||
const DEALERS_NODE_INDEX_IDX_NAMESPACE: &str = "dni";
|
||||
pub(crate) const DEALER_INDICES_PAGE_MAX_LIMIT: u32 = 80;
|
||||
pub(crate) const DEALER_INDICES_PAGE_DEFAULT_LIMIT: u32 = 40;
|
||||
|
||||
pub(crate) const DEALERS_PAGE_MAX_LIMIT: u32 = 75;
|
||||
pub(crate) const DEALERS_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
pub(crate) const DEALERS_PAGE_MAX_LIMIT: u32 = 25;
|
||||
pub(crate) const DEALERS_PAGE_DEFAULT_LIMIT: u32 = 10;
|
||||
|
||||
pub(crate) const NODE_INDEX_COUNTER: Item<NodeIndex> = Item::new("node_index_counter");
|
||||
|
||||
pub(crate) type IndexedDealersMap<'a> = IndexedMap<'a, &'a Addr, DealerDetails, DealersIndex<'a>>;
|
||||
pub(crate) const DEALERS_INDICES: Map<Dealer, NodeIndex> = Map::new("dealer_index");
|
||||
|
||||
pub(crate) struct DealersIndex<'a> {
|
||||
pub(crate) node_index: UniqueIndex<'a, NodeIndex, DealerDetails>,
|
||||
}
|
||||
pub(crate) const EPOCH_DEALERS_MAP: Map<(EpochId, Dealer), DealerRegistrationDetails> =
|
||||
Map::new("epoch_dealers");
|
||||
|
||||
impl<'a> IndexList<DealerDetails> for DealersIndex<'a> {
|
||||
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<DealerDetails>> + '_> {
|
||||
let v: Vec<&dyn Index<DealerDetails>> = vec![&self.node_index];
|
||||
Box::new(v.into_iter())
|
||||
/// Attempts to retrieve a pre-assign node index associated with given dealer.
|
||||
/// If one doesn't exist, a new one is assigned.
|
||||
pub(crate) fn get_or_assign_index(
|
||||
storage: &mut dyn Storage,
|
||||
dealer: Dealer,
|
||||
) -> StdResult<NodeIndex> {
|
||||
if let Some(index) = DEALERS_INDICES.may_load(storage, dealer)? {
|
||||
return Ok(index);
|
||||
}
|
||||
let index = next_node_index(storage)?;
|
||||
DEALERS_INDICES.save(storage, dealer, &index)?;
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
pub(crate) fn current_dealers<'a>() -> IndexedDealersMap<'a> {
|
||||
let indexes = DealersIndex {
|
||||
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
|
||||
};
|
||||
IndexedMap::new(CURRENT_DEALERS_PK, indexes)
|
||||
pub(crate) fn save_dealer_details_if_not_a_dealer(
|
||||
storage: &mut dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
details: DealerRegistrationDetails,
|
||||
) -> Result<(), ContractError> {
|
||||
if EPOCH_DEALERS_MAP.has(storage, (epoch_id, dealer)) {
|
||||
return Err(ContractError::AlreadyADealer);
|
||||
}
|
||||
EPOCH_DEALERS_MAP.save(storage, (epoch_id, dealer), &details)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn past_dealers<'a>() -> IndexedDealersMap<'a> {
|
||||
let indexes = DealersIndex {
|
||||
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
|
||||
};
|
||||
IndexedMap::new(PAST_DEALERS_PK, indexes)
|
||||
pub(crate) fn ensure_dealer(
|
||||
storage: &dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<(), ContractError> {
|
||||
if !is_dealer(storage, dealer, epoch_id) {
|
||||
return Err(ContractError::NotADealer { epoch_id });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_dealer(storage: &dyn Storage, dealer: Dealer, epoch_id: EpochId) -> bool {
|
||||
EPOCH_DEALERS_MAP.has(storage, (epoch_id, dealer))
|
||||
}
|
||||
|
||||
// note: `epoch_id` is provided purely for the error message. it has nothing to do with storage retrieval
|
||||
pub(crate) fn get_dealer_index(
|
||||
storage: &dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<NodeIndex, ContractError> {
|
||||
DEALERS_INDICES
|
||||
.may_load(storage, dealer)?
|
||||
.ok_or(ContractError::NotADealer { epoch_id })
|
||||
}
|
||||
|
||||
pub(crate) fn get_registration_details(
|
||||
storage: &dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<DealerRegistrationDetails, ContractError> {
|
||||
EPOCH_DEALERS_MAP
|
||||
.may_load(storage, (epoch_id, dealer))?
|
||||
.ok_or(ContractError::NotADealer { epoch_id })
|
||||
}
|
||||
|
||||
pub(crate) fn get_dealer_details(
|
||||
storage: &dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<DealerDetails, ContractError> {
|
||||
let registration_details = get_registration_details(storage, dealer, epoch_id)?;
|
||||
let assigned_index = get_dealer_index(storage, dealer, epoch_id)?;
|
||||
Ok(DealerDetails {
|
||||
address: dealer.to_owned(),
|
||||
bte_public_key_with_proof: registration_details.bte_public_key_with_proof,
|
||||
ed25519_identity: registration_details.ed25519_identity,
|
||||
announce_address: registration_details.announce_address,
|
||||
assigned_index,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn next_node_index(store: &mut dyn Storage) -> StdResult<NodeIndex> {
|
||||
|
||||
@@ -1,34 +1,24 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::dealers::storage as dealers_storage;
|
||||
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
|
||||
use crate::dealers::storage::{
|
||||
get_or_assign_index, is_dealer, save_dealer_details_if_not_a_dealer,
|
||||
};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::STATE;
|
||||
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState};
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::{Deps, DepsMut, MessageInfo, Response, StdResult};
|
||||
use nym_coconut_dkg_common::dealer::DealerRegistrationDetails;
|
||||
use nym_coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState};
|
||||
|
||||
// currently we only require that
|
||||
// a) it's part of the signer group
|
||||
// b) it isn't already a dealer
|
||||
fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<(), ContractError> {
|
||||
if dealers_storage::current_dealers()
|
||||
.may_load(deps.storage, dealer)?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ContractError::AlreadyADealer);
|
||||
}
|
||||
fn ensure_group_member(deps: Deps, dealer: Dealer) -> Result<(), ContractError> {
|
||||
let state = STATE.load(deps.storage)?;
|
||||
|
||||
let height = if resharing {
|
||||
Some(INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state
|
||||
.group_addr
|
||||
.is_voting_member(&deps.querier, dealer, height)?
|
||||
.is_voting_member(&deps.querier, dealer, None)?
|
||||
.ok_or(ContractError::Unauthorized {})?;
|
||||
|
||||
Ok(())
|
||||
@@ -37,42 +27,57 @@ fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<()
|
||||
// future optimisation:
|
||||
// for a recurring dealer just let it refresh the keys without having to do all the storage operations
|
||||
pub fn try_add_dealer(
|
||||
mut deps: DepsMut<'_>,
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||
identity_key: String,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
let epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?;
|
||||
|
||||
verify_dealer(deps.branch(), &info.sender, resharing)?;
|
||||
// make sure this potential dealer actually belong to the group
|
||||
ensure_group_member(deps.as_ref(), &info.sender)?;
|
||||
|
||||
// if it was already a dealer in the past, assign the same node index
|
||||
let node_index = if let Some(prior_details) =
|
||||
dealers_storage::past_dealers().may_load(deps.storage, &info.sender)?
|
||||
{
|
||||
// since this dealer is going to become active now, remove it from the past dealers
|
||||
dealers_storage::past_dealers().replace(
|
||||
deps.storage,
|
||||
&info.sender,
|
||||
None,
|
||||
Some(&prior_details),
|
||||
)?;
|
||||
prior_details.assigned_index
|
||||
} else {
|
||||
dealers_storage::next_node_index(deps.storage)?
|
||||
};
|
||||
let node_index = get_or_assign_index(deps.storage, &info.sender)?;
|
||||
|
||||
// save the dealer into the storage
|
||||
let dealer_details = DealerDetails {
|
||||
address: info.sender.clone(),
|
||||
// save the dealer into the storage (if it hasn't already been saved)
|
||||
let dealer_details = DealerRegistrationDetails {
|
||||
bte_public_key_with_proof: bte_key_with_proof,
|
||||
ed25519_identity: identity_key,
|
||||
announce_address,
|
||||
assigned_index: node_index,
|
||||
};
|
||||
dealers_storage::current_dealers().save(deps.storage, &info.sender, &dealer_details)?;
|
||||
save_dealer_details_if_not_a_dealer(
|
||||
deps.storage,
|
||||
&info.sender,
|
||||
epoch.epoch_id,
|
||||
dealer_details,
|
||||
)?;
|
||||
|
||||
// check if it's a resharing dealer
|
||||
|
||||
let is_resharing_dealer = resharing
|
||||
&& is_dealer(
|
||||
deps.storage,
|
||||
&info.sender,
|
||||
epoch
|
||||
.epoch_id
|
||||
.checked_sub(1)
|
||||
.expect("epoch invariant broken: resharing during 0th epoch"),
|
||||
);
|
||||
|
||||
// increment the number of registered dealers
|
||||
CURRENT_EPOCH.update(deps.storage, |epoch| -> StdResult<_> {
|
||||
let mut updated_epoch = epoch;
|
||||
updated_epoch.state_progress.registered_dealers += 1;
|
||||
|
||||
if is_resharing_dealer {
|
||||
updated_epoch.state_progress.registered_resharing_dealers += 1;
|
||||
}
|
||||
|
||||
Ok(updated_epoch)
|
||||
})?;
|
||||
|
||||
Ok(Response::new().add_attribute("node_index", node_index.to_string()))
|
||||
}
|
||||
@@ -80,63 +85,12 @@ pub fn try_add_dealer(
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::dealers::storage::current_dealers;
|
||||
use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg};
|
||||
use crate::support::tests::fixtures::dealer_details_fixture;
|
||||
use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg};
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, GROUP_MEMBERS};
|
||||
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cw4::Member;
|
||||
use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
|
||||
use rusty_fork::rusty_fork_test;
|
||||
|
||||
rusty_fork_test! {
|
||||
#[test]
|
||||
fn verification() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let new_dealer = Addr::unchecked("new_dealer");
|
||||
let details1 = dealer_details_fixture(1);
|
||||
let details2 = dealer_details_fixture(2);
|
||||
let details3 = dealer_details_fixture(3);
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details1.address, &details1)
|
||||
.unwrap();
|
||||
let err = verify_dealer(deps.as_mut(), &details1.address, false).unwrap_err();
|
||||
assert_eq!(err, ContractError::AlreadyADealer);
|
||||
|
||||
INITIAL_REPLACEMENT_DATA
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
&InitialReplacementData {
|
||||
initial_dealers: vec![details1.address, details2.address, details3.address],
|
||||
initial_height: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let err = verify_dealer(deps.as_mut(), &new_dealer, false).unwrap_err();
|
||||
assert_eq!(err, ContractError::Unauthorized);
|
||||
|
||||
GROUP_MEMBERS.lock().unwrap().push((
|
||||
Member {
|
||||
addr: new_dealer.to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
2,
|
||||
));
|
||||
verify_dealer(deps.as_mut(), &new_dealer, false).unwrap();
|
||||
|
||||
let err = verify_dealer(deps.as_mut(), &new_dealer, true).unwrap_err();
|
||||
assert_eq!(err, ContractError::Unauthorized);
|
||||
|
||||
INITIAL_REPLACEMENT_DATA
|
||||
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
|
||||
data.initial_height = 2;
|
||||
Ok(data)
|
||||
})
|
||||
.unwrap();
|
||||
verify_dealer(deps.as_mut(), &new_dealer, true).unwrap();
|
||||
}
|
||||
}
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_coconut_dkg_common::types::TimeConfiguration;
|
||||
|
||||
#[test]
|
||||
fn invalid_state() {
|
||||
@@ -156,7 +110,7 @@ pub(crate) mod tests {
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
|
||||
add_fixture_dealer(deps.as_mut());
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
|
||||
let ret = try_add_dealer(
|
||||
deps.as_mut(),
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{Addr, Storage};
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::Storage;
|
||||
use cw_storage_plus::{Key, Map, Path, PrimaryKey};
|
||||
use nym_coconut_dkg_common::dealing::{DealingMetadata, PartialContractDealing};
|
||||
use nym_coconut_dkg_common::types::{
|
||||
ChunkIndex, ContractSafeBytes, DealingIndex, EpochId, PartialContractDealingData,
|
||||
};
|
||||
|
||||
type Dealer<'a> = &'a Addr;
|
||||
|
||||
/// Metadata for a dealing for given `EpochId`, submitted by particular `Dealer` for given `DealingIndex`.
|
||||
pub(crate) const DEALINGS_METADATA: Map<(EpochId, Dealer, DealingIndex), DealingMetadata> =
|
||||
Map::new("dealings_metadata");
|
||||
@@ -180,7 +179,7 @@ impl StoredDealing {
|
||||
pub(crate) fn unchecked_all_entries(
|
||||
storage: &dyn Storage,
|
||||
) -> Vec<(
|
||||
(EpochId, Addr, (DealingIndex, ChunkIndex)),
|
||||
(EpochId, cosmwasm_std::Addr, (DealingIndex, ChunkIndex)),
|
||||
PartialContractDealingData,
|
||||
)> {
|
||||
use cw_storage_plus::KeyDeserialize;
|
||||
@@ -210,6 +209,7 @@ impl StoredDealing {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers::init_contract;
|
||||
use cosmwasm_std::Addr;
|
||||
use cw_storage_plus::Bound;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::dealers::storage as dealers_storage;
|
||||
use crate::dealers::storage::ensure_dealer;
|
||||
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::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::STATE;
|
||||
@@ -13,31 +13,25 @@ use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, Storage};
|
||||
use nym_coconut_dkg_common::dealing::{
|
||||
DealingChunkInfo, DealingMetadata, PartialContractDealing, MAX_DEALING_CHUNKS,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochState};
|
||||
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId, EpochState};
|
||||
|
||||
// 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,
|
||||
current_epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), ContractError> {
|
||||
check_epoch_state(storage, EpochState::DealingExchange { resharing })?;
|
||||
|
||||
// ensure the sender is a dealer
|
||||
if dealers_storage::current_dealers()
|
||||
.may_load(storage, sender)?
|
||||
.is_none()
|
||||
{
|
||||
return Err(ContractError::NotADealer);
|
||||
}
|
||||
if resharing
|
||||
&& !INITIAL_REPLACEMENT_DATA
|
||||
.load(storage)?
|
||||
.initial_dealers
|
||||
.contains(sender)
|
||||
{
|
||||
return Err(ContractError::NotAnInitialDealer);
|
||||
// ensure the sender is a dealer for this epoch
|
||||
ensure_dealer(storage, sender, current_epoch_id)?;
|
||||
|
||||
// if we're in resharing, make sure this sender has also been a dealer in the previous epoch
|
||||
if resharing {
|
||||
ensure_dealer(storage, sender, current_epoch_id.saturating_sub(1))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -48,10 +42,10 @@ pub fn try_submit_dealings_metadata(
|
||||
chunks: Vec<DealingChunkInfo>,
|
||||
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)?;
|
||||
let state = STATE.load(deps.storage)?;
|
||||
|
||||
ensure_permission(deps.storage, &info.sender, epoch.epoch_id, resharing)?;
|
||||
|
||||
// don't allow overwriting existing metadata
|
||||
if metadata_exists(deps.storage, epoch.epoch_id, &info.sender, dealing_index) {
|
||||
@@ -139,11 +133,11 @@ pub fn try_commit_dealings_chunk(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
ensure_permission(deps.storage, &info.sender, resharing)?;
|
||||
// note: checking permissions is implicit as if the metadata exists,
|
||||
// the sender must have been allowed to submit it
|
||||
|
||||
let epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
let mut epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
|
||||
// read meta
|
||||
let mut metadata = must_read_metadata(
|
||||
@@ -199,6 +193,13 @@ pub fn try_commit_dealings_chunk(
|
||||
// store the dealing
|
||||
StoredDealing::save(deps.storage, epoch.epoch_id, &info.sender, chunk);
|
||||
|
||||
// this is less than ideal since we have to iterate through all the chunks, but realistically,
|
||||
// there won't be a lot of them
|
||||
if metadata.is_complete() {
|
||||
epoch.state_progress.submitted_dealings += 1;
|
||||
CURRENT_EPOCH.save(deps.storage, &epoch)?;
|
||||
}
|
||||
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
@@ -206,18 +207,14 @@ pub fn try_commit_dealings_chunk(
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg};
|
||||
use crate::support::tests::fixtures::{
|
||||
dealer_details_fixture, dealing_metadata_fixture, partial_dealing_fixture,
|
||||
};
|
||||
use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg};
|
||||
use crate::support::tests::fixtures::{dealing_metadata_fixture, partial_dealing_fixture};
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS};
|
||||
use crate::support::tests::helpers::{add_current_dealer, re_register_dealer, ADMIN_ADDRESS};
|
||||
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::{
|
||||
ContractSafeBytes, InitialReplacementData, TimeConfiguration,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::{ContractSafeBytes, TimeConfiguration};
|
||||
|
||||
#[test]
|
||||
fn invalid_commit_dealing_chunk() {
|
||||
@@ -227,41 +224,27 @@ pub(crate) mod tests {
|
||||
|
||||
let owner = Addr::unchecked("owner1");
|
||||
let info = mock_info(owner.as_str(), &[]);
|
||||
let dealing = partial_dealing_fixture();
|
||||
let chunk = partial_dealing_fixture();
|
||||
|
||||
let ret = try_commit_dealings_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
dealing.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
// no dealing metadata
|
||||
let ret =
|
||||
try_commit_dealings_chunk(deps.as_mut(), env.clone(), info.clone(), chunk.clone())
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::IncorrectEpochState {
|
||||
current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
|
||||
expected_state: EpochState::DealingExchange { resharing: false }.to_string()
|
||||
ContractError::UnavailableDealingMetadata {
|
||||
epoch_id: 0,
|
||||
dealer: info.sender.clone(),
|
||||
dealing_index: chunk.dealing_index,
|
||||
}
|
||||
);
|
||||
|
||||
// add dealing metadata
|
||||
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.clone()).unwrap();
|
||||
|
||||
let ret = try_commit_dealings_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
dealing.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(ret, ContractError::NotADealer);
|
||||
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let dealer_details = DealerDetails {
|
||||
address: owner.clone(),
|
||||
bte_public_key_with_proof: String::new(),
|
||||
@@ -269,60 +252,12 @@ pub(crate) mod tests {
|
||||
announce_address: String::new(),
|
||||
assigned_index: 1,
|
||||
};
|
||||
dealers_storage::current_dealers()
|
||||
.save(deps.as_mut().storage, &owner, &dealer_details)
|
||||
.unwrap();
|
||||
add_current_dealer(deps.as_mut(), &dealer_details);
|
||||
|
||||
// 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_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
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();
|
||||
|
||||
// TODO: test case: no metadata
|
||||
//
|
||||
//
|
||||
|
||||
// add dealing metadata
|
||||
try_submit_dealings_metadata(
|
||||
deps.as_mut(),
|
||||
info.clone(),
|
||||
0,
|
||||
chunk.dealing_index,
|
||||
dealing_metadata_fixture(),
|
||||
false,
|
||||
)
|
||||
@@ -338,7 +273,6 @@ pub(crate) mod tests {
|
||||
chunk_index: 42,
|
||||
data: ContractSafeBytes(vec![1, 2, 3]),
|
||||
},
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
@@ -352,24 +286,14 @@ pub(crate) mod tests {
|
||||
);
|
||||
|
||||
// 'good' dealing
|
||||
let ret = try_commit_dealings_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
dealing.clone(),
|
||||
false,
|
||||
);
|
||||
let ret =
|
||||
try_commit_dealings_chunk(deps.as_mut(), env.clone(), info.clone(), chunk.clone());
|
||||
assert!(ret.is_ok());
|
||||
|
||||
// duplicate dealing
|
||||
let ret = try_commit_dealings_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
dealing.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
let ret =
|
||||
try_commit_dealings_chunk(deps.as_mut(), env.clone(), info.clone(), chunk.clone())
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::DealingChunkAlreadyCommitted {
|
||||
@@ -388,6 +312,7 @@ pub(crate) mod tests {
|
||||
Ok(epoch)
|
||||
})
|
||||
.unwrap();
|
||||
re_register_dealer(deps.as_mut(), &info.sender);
|
||||
|
||||
try_submit_dealings_metadata(
|
||||
deps.as_mut(),
|
||||
@@ -398,7 +323,7 @@ pub(crate) mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ret = try_commit_dealings_chunk(deps.as_mut(), env, info, dealing.clone(), false);
|
||||
let ret = try_commit_dealings_chunk(deps.as_mut(), env, info, chunk.clone());
|
||||
assert!(ret.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,37 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD};
|
||||
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
|
||||
use crate::epoch_state::utils::check_state_completion;
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::Storage;
|
||||
use nym_coconut_dkg_common::types::{Epoch, InitialReplacementData};
|
||||
use cosmwasm_std::{Env, Storage};
|
||||
use nym_coconut_dkg_common::types::{Epoch, EpochState, StateAdvanceResponse};
|
||||
|
||||
pub(crate) fn query_can_advance_state(
|
||||
storage: &dyn Storage,
|
||||
env: Env,
|
||||
) -> Result<StateAdvanceResponse, ContractError> {
|
||||
let epoch = CURRENT_EPOCH.load(storage)?;
|
||||
|
||||
if epoch.state == EpochState::WaitingInitialisation {
|
||||
return Ok(StateAdvanceResponse::default());
|
||||
}
|
||||
|
||||
let is_complete = check_state_completion(storage, &epoch)?;
|
||||
let reached_deadline = if let Some(finish_timestamp) = epoch.deadline {
|
||||
finish_timestamp <= env.block.time
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Ok(StateAdvanceResponse {
|
||||
current_state: epoch.state,
|
||||
progress: epoch.state_progress,
|
||||
deadline: epoch.deadline,
|
||||
reached_deadline,
|
||||
is_complete,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
|
||||
CURRENT_EPOCH
|
||||
@@ -18,12 +45,6 @@ pub(crate) fn query_current_epoch_threshold(
|
||||
Ok(THRESHOLD.may_load(storage)?)
|
||||
}
|
||||
|
||||
pub(crate) fn query_initial_dealers(
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Option<InitialReplacementData>, ContractError> {
|
||||
Ok(INITIAL_REPLACEMENT_DATA.may_load(storage)?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use super::*;
|
||||
@@ -37,7 +58,7 @@ pub(crate) mod test {
|
||||
let mut deps = init_contract();
|
||||
let epoch = query_current_epoch(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(epoch.state, EpochState::WaitingInitialisation);
|
||||
assert_eq!(epoch.finish_timestamp, None);
|
||||
assert_eq!(epoch.deadline, None);
|
||||
|
||||
let env = mock_env();
|
||||
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
|
||||
@@ -48,7 +69,7 @@ pub(crate) mod test {
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp.unwrap(),
|
||||
epoch.deadline.unwrap(),
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs)
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cw_storage_plus::Item;
|
||||
use nym_coconut_dkg_common::types::{Epoch, InitialReplacementData};
|
||||
use nym_coconut_dkg_common::types::Epoch;
|
||||
|
||||
pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch");
|
||||
pub const THRESHOLD: Item<u64> = Item::new("threshold");
|
||||
pub const INITIAL_REPLACEMENT_DATA: Item<InitialReplacementData> =
|
||||
Item::new("initial_replacement_data");
|
||||
|
||||
@@ -1,930 +0,0 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::dealers::storage::{current_dealers, past_dealers};
|
||||
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::storage::{DKG_ADMIN, STATE};
|
||||
use crate::verification_key_shares::storage::verified_dealers;
|
||||
use cosmwasm_std::{Addr, Deps, DepsMut, Env, MessageInfo, Order, Response, Storage};
|
||||
use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
|
||||
|
||||
fn reset_dkg_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
THRESHOLD.remove(storage);
|
||||
let dealers: Vec<_> = current_dealers()
|
||||
.keys(storage, None, None, Order::Ascending)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
for dealer_addr in dealers {
|
||||
let details = current_dealers().load(storage, &dealer_addr)?;
|
||||
current_dealers().remove(storage, &dealer_addr)?;
|
||||
past_dealers().save(storage, &dealer_addr, &details)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dealers_still_active(
|
||||
deps: &Deps<'_>,
|
||||
dealers: impl Iterator<Item = Addr>,
|
||||
) -> Result<usize, ContractError> {
|
||||
let state = STATE.load(deps.storage)?;
|
||||
let mut still_active = 0;
|
||||
for dealer_addr in dealers {
|
||||
if state
|
||||
.group_addr
|
||||
.is_voting_member(&deps.querier, &dealer_addr, None)?
|
||||
.is_some()
|
||||
{
|
||||
still_active += 1;
|
||||
}
|
||||
}
|
||||
Ok(still_active)
|
||||
}
|
||||
|
||||
fn dealers_eq_members(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
|
||||
let verified_dealers = verified_dealers(deps.storage)?;
|
||||
let all_dealers = verified_dealers.len();
|
||||
let dealers_still_active = dealers_still_active(&deps.as_ref(), verified_dealers.into_iter())?;
|
||||
let group_members = STATE
|
||||
.load(deps.storage)?
|
||||
.group_addr
|
||||
.list_members(&deps.querier, None, None)?
|
||||
.len();
|
||||
|
||||
Ok(dealers_still_active == all_dealers && all_dealers == group_members)
|
||||
}
|
||||
|
||||
fn replacement_threshold_surpassed(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
|
||||
let threshold = THRESHOLD.load(deps.storage)? as usize;
|
||||
let initial_dealers = verified_dealers(deps.storage)?;
|
||||
if initial_dealers.is_empty() {
|
||||
// possibly failed DKG, just reset and start again
|
||||
return Ok(true);
|
||||
}
|
||||
let initial_dealer_count = initial_dealers.len();
|
||||
let replacement_threshold = threshold - (initial_dealers.len() + 2 - 1) / 2 + 1;
|
||||
let removed_dealer_count =
|
||||
initial_dealer_count - dealers_still_active(&deps.as_ref(), initial_dealers.into_iter())?;
|
||||
|
||||
Ok(removed_dealer_count >= replacement_threshold)
|
||||
}
|
||||
|
||||
pub(crate) fn try_initiate_dkg(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
// only the admin is allowed to kick start the process
|
||||
DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
|
||||
|
||||
let epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
if !matches!(epoch.state, EpochState::WaitingInitialisation) {
|
||||
return Err(ContractError::AlreadyInitialised);
|
||||
}
|
||||
|
||||
// the first exchange won't involve resharing
|
||||
let initial_state = EpochState::PublicKeySubmission { resharing: false };
|
||||
let initial_epoch = Epoch::new(initial_state, 0, epoch.time_configuration, env.block.time);
|
||||
CURRENT_EPOCH.save(deps.storage, &initial_epoch)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response, ContractError> {
|
||||
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
if current_epoch.state == EpochState::WaitingInitialisation {
|
||||
return Err(ContractError::WaitingInitialisation);
|
||||
}
|
||||
|
||||
if let Some(finish_timestamp) = current_epoch.finish_timestamp {
|
||||
if finish_timestamp > env.block.time {
|
||||
return Err(ContractError::EarlyEpochStateAdvancement(
|
||||
finish_timestamp
|
||||
.minus_seconds(env.block.time.seconds())
|
||||
.seconds(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let next_epoch = if let Some(state) = current_epoch.state.next() {
|
||||
// We are during DKG process
|
||||
let mut new_state = state;
|
||||
if let EpochState::DealingExchange { .. } = state {
|
||||
let current_dealers = current_dealers()
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.collect::<Result<Vec<Addr>, _>>()?;
|
||||
let group_members =
|
||||
STATE
|
||||
.load(deps.storage)?
|
||||
.group_addr
|
||||
.list_members(&deps.querier, None, None)?;
|
||||
if current_dealers.len() < group_members.len() {
|
||||
// If not all group members registered yet, we just stay in the same state until
|
||||
// they either register or they get kicked out of the group
|
||||
new_state = current_epoch.state;
|
||||
} else {
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
|
||||
THRESHOLD.save(deps.storage, &threshold)?;
|
||||
}
|
||||
};
|
||||
Epoch::new(
|
||||
new_state,
|
||||
current_epoch.epoch_id,
|
||||
current_epoch.time_configuration,
|
||||
env.block.time,
|
||||
)
|
||||
} 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,
|
||||
current_epoch.time_configuration,
|
||||
env.block.time,
|
||||
)
|
||||
} else {
|
||||
// Dealer set changed, we need to redo DKG...
|
||||
let state = if replacement_threshold_surpassed(&deps)? {
|
||||
// ... in reset mode
|
||||
INITIAL_REPLACEMENT_DATA.remove(deps.storage);
|
||||
EpochState::default()
|
||||
} else {
|
||||
// ... in reshare mode
|
||||
if INITIAL_REPLACEMENT_DATA.may_load(deps.storage)?.is_some() {
|
||||
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
|
||||
// TODO: FIXME: for second reshare the added set of dealers won't be allowed to participate
|
||||
data.initial_height = env.block.height;
|
||||
Ok(data)
|
||||
})?;
|
||||
} else {
|
||||
let replacement_data = InitialReplacementData {
|
||||
initial_dealers: verified_dealers(deps.storage)?,
|
||||
initial_height: env.block.height,
|
||||
};
|
||||
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
|
||||
}
|
||||
|
||||
EpochState::PublicKeySubmission { resharing: true }
|
||||
};
|
||||
reset_dkg_state(deps.storage)?;
|
||||
Epoch::new(
|
||||
state,
|
||||
current_epoch.epoch_id + 1,
|
||||
current_epoch.time_configuration,
|
||||
env.block.time,
|
||||
)
|
||||
};
|
||||
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub(crate) fn try_surpassed_threshold(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
) -> Result<Response, ContractError> {
|
||||
check_epoch_state(deps.storage, EpochState::InProgress)?;
|
||||
|
||||
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_dkg_state(deps.storage)?;
|
||||
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
|
||||
Ok(Epoch::new(
|
||||
EpochState::default(),
|
||||
epoch.epoch_id + 1,
|
||||
epoch.time_configuration,
|
||||
env.block.time,
|
||||
))
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::error::ContractError::EarlyEpochStateAdvancement;
|
||||
use crate::support::tests::fixtures::{dealer_details_fixture, vk_share_fixture};
|
||||
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS, GROUP_MEMBERS};
|
||||
use crate::verification_key_shares::storage::vk_shares;
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::Addr;
|
||||
use cw4::Member;
|
||||
use cw_controllers::AdminError;
|
||||
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
|
||||
|
||||
rusty_fork_test! {
|
||||
// Using values from the DKG document
|
||||
#[test]
|
||||
fn threshold_surpassed() {
|
||||
let mut deps = init_contract();
|
||||
let two_thirds = |n: u64| (2 * n + 3 - 1) / 3;
|
||||
let three_fourths = |n: u64| (3 * n + 4 - 1) / 4;
|
||||
let ninty_pc = |n: u64| (9 * n + 10 - 2) / 10;
|
||||
let mut limits = [3, 4, 5, 5, 7, 11, 10, 14, 21, 18, 26, 41].iter();
|
||||
|
||||
for n in [10, 25, 50, 100] {
|
||||
let dealers: Vec<_> = (0..n).map(dealer_details_fixture).collect();
|
||||
let shares: Vec<_> = (0..n)
|
||||
.map(|idx| vk_share_fixture(&format!("owner{}", idx), 0))
|
||||
.collect();
|
||||
let initial_dealers = dealers.iter().map(|d| d.address.clone()).collect();
|
||||
let data = InitialReplacementData {
|
||||
initial_dealers,
|
||||
initial_height: 1,
|
||||
};
|
||||
for share in shares {
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), &share)
|
||||
.unwrap();
|
||||
}
|
||||
for f in [two_thirds, three_fourths, ninty_pc] {
|
||||
let threshold = f(n);
|
||||
THRESHOLD.save(deps.as_mut().storage, &threshold).unwrap();
|
||||
INITIAL_REPLACEMENT_DATA
|
||||
.save(deps.as_mut().storage, &data)
|
||||
.unwrap();
|
||||
|
||||
let limit = *limits.next().unwrap();
|
||||
{
|
||||
let mut group_members = GROUP_MEMBERS.lock().unwrap();
|
||||
for dealer in dealers.iter() {
|
||||
group_members.push((
|
||||
Member {
|
||||
addr: dealer.address.to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
}
|
||||
for _ in 1..limit {
|
||||
group_members.pop();
|
||||
}
|
||||
}
|
||||
assert!(!replacement_threshold_surpassed(&deps.as_mut()).unwrap());
|
||||
GROUP_MEMBERS.lock().unwrap().pop();
|
||||
assert!(replacement_threshold_surpassed(&deps.as_mut()).unwrap());
|
||||
|
||||
*GROUP_MEMBERS.lock().unwrap() = vec![];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dealers_and_members() {
|
||||
let mut deps = init_contract();
|
||||
|
||||
assert!(dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
let share = vk_share_fixture("owner2", 0);
|
||||
let different_share = vk_share_fixture("owner4", 0);
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), &share)
|
||||
.unwrap();
|
||||
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
vk_shares()
|
||||
.remove(deps.as_mut().storage, (&share.owner, 0))
|
||||
.unwrap();
|
||||
GROUP_MEMBERS.lock().unwrap().push((
|
||||
Member {
|
||||
addr: "owner2".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
vk_shares()
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
(&different_share.owner, 0),
|
||||
&different_share,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
vk_shares()
|
||||
.remove(deps.as_mut().storage, (&different_share.owner, 0))
|
||||
.unwrap();
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), &share)
|
||||
.unwrap();
|
||||
assert!(dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn still_active() {
|
||||
let mut deps = init_contract();
|
||||
{
|
||||
let mut group = GROUP_MEMBERS.lock().unwrap();
|
||||
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner1".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner2".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner3".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
}
|
||||
assert_eq!(
|
||||
0,
|
||||
dealers_still_active(
|
||||
&deps.as_ref(),
|
||||
current_dealers()
|
||||
.keys(&deps.storage, None, None, Order::Ascending)
|
||||
.flatten()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
for i in 0..3_u64 {
|
||||
let details = dealer_details_fixture(i + 1);
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details.address, &details)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
i as usize + 1,
|
||||
dealers_still_active(
|
||||
&deps.as_ref(),
|
||||
current_dealers()
|
||||
.keys(&deps.storage, None, None, Order::Ascending)
|
||||
.flatten()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advance_state() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
|
||||
{
|
||||
let mut group = GROUP_MEMBERS.lock().unwrap();
|
||||
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner1".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner2".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner3".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner4".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
}
|
||||
|
||||
// can't advance the state if dkg hasn't been initiated
|
||||
assert_eq!(
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
ContractError::WaitingInitialisation
|
||||
);
|
||||
|
||||
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
|
||||
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp.unwrap(),
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs)
|
||||
);
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs - 1);
|
||||
assert_eq!(
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(1)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(1);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
|
||||
// setup dealer details
|
||||
let all_shares: [_; 4] =
|
||||
std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
|
||||
for share in all_shares.iter() {
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), share)
|
||||
.unwrap();
|
||||
}
|
||||
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
|
||||
for details in all_details.iter() {
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details.address, details)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert!(INITIAL_REPLACEMENT_DATA
|
||||
.may_load(&deps.storage)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::DealingExchange { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp.unwrap(),
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.dealing_exchange_time_secs)
|
||||
);
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.dealing_exchange_time_secs - 2);
|
||||
assert_eq!(
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(2)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(3);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::VerificationKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp.unwrap(),
|
||||
env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_submission_time_secs
|
||||
)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_submission_time_secs
|
||||
- 2,
|
||||
);
|
||||
assert_eq!(
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(2)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(3);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::VerificationKeyValidation { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp.unwrap(),
|
||||
env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_validation_time_secs
|
||||
)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_validation_time_secs
|
||||
- 3,
|
||||
);
|
||||
assert_eq!(
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(3)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(3);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::VerificationKeyFinalization { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp.unwrap(),
|
||||
env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_finalization_time_secs
|
||||
)
|
||||
);
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().verification_key_finalization_time_secs - 1);
|
||||
assert_eq!(
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(1)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(1);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(epoch.state, EpochState::InProgress);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp.unwrap(),
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.in_progress_time_secs)
|
||||
);
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.in_progress_time_secs - 100);
|
||||
assert_eq!(
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(100)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(50);
|
||||
assert_eq!(
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(50)
|
||||
);
|
||||
|
||||
// Group hasn't changed, so we remain in the same epoch, with updated finish timestamp
|
||||
env.block.time = env.block.time.plus_seconds(100);
|
||||
let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
let expected_epoch = Epoch::new(
|
||||
EpochState::InProgress,
|
||||
prev_epoch.epoch_id,
|
||||
prev_epoch.time_configuration,
|
||||
env.block.time,
|
||||
);
|
||||
assert_eq!(curr_epoch, expected_epoch);
|
||||
|
||||
// Group changed slightly, so re-run dkg in reshare mode
|
||||
*GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = (
|
||||
Member {
|
||||
addr: "owner5".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
);
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.in_progress_time_secs);
|
||||
let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
let expected_epoch = Epoch::new(
|
||||
EpochState::PublicKeySubmission { resharing: true },
|
||||
prev_epoch.epoch_id + 1,
|
||||
prev_epoch.time_configuration,
|
||||
env.block.time,
|
||||
);
|
||||
assert_eq!(curr_epoch, expected_epoch);
|
||||
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
|
||||
let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap();
|
||||
let expected_replacement_data = InitialReplacementData {
|
||||
initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(),
|
||||
initial_height: 12345,
|
||||
};
|
||||
assert_eq!(replacement_data, expected_replacement_data);
|
||||
|
||||
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2));
|
||||
for details in all_details.iter() {
|
||||
past_dealers()
|
||||
.remove(deps.as_mut().storage, &details.address)
|
||||
.unwrap();
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details.address, details)
|
||||
.unwrap();
|
||||
}
|
||||
for times in [
|
||||
epoch.time_configuration.public_key_submission_time_secs,
|
||||
epoch.time_configuration.dealing_exchange_time_secs,
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_submission_time_secs,
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_validation_time_secs,
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_finalization_time_secs,
|
||||
] {
|
||||
env.block.time = env.block.time.plus_seconds(times);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
}
|
||||
|
||||
let all_shares: [_; 4] = std::array::from_fn(|i| {
|
||||
let mut share = vk_share_fixture(&format!("owner{}", i + 1), 1);
|
||||
share.verified = i % 2 == 0;
|
||||
share
|
||||
});
|
||||
for share in all_shares.iter() {
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), share)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Group changed even more, surpassing threshold, so re-run dkg in reset mode
|
||||
*GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = (
|
||||
Member {
|
||||
addr: "owner6".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
);
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.in_progress_time_secs);
|
||||
let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
let expected_epoch = Epoch::new(
|
||||
EpochState::PublicKeySubmission { resharing: true },
|
||||
prev_epoch.epoch_id + 1,
|
||||
prev_epoch.time_configuration,
|
||||
env.block.time,
|
||||
);
|
||||
assert_eq!(curr_epoch, expected_epoch);
|
||||
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn surpass_threshold() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
|
||||
|
||||
let time_configuration = TimeConfiguration::default();
|
||||
{
|
||||
let mut group = GROUP_MEMBERS.lock().unwrap();
|
||||
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner1".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner2".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
group.push((
|
||||
Member {
|
||||
addr: "owner3".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
}
|
||||
|
||||
let ret = try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::IncorrectEpochState {
|
||||
current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
|
||||
expected_state: EpochState::InProgress.to_string()
|
||||
}
|
||||
);
|
||||
|
||||
let all_shares: [_; 3] =
|
||||
std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
|
||||
for share in all_shares.iter() {
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), share)
|
||||
.unwrap();
|
||||
}
|
||||
let all_details: [_; 3] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
|
||||
for details in all_details.iter() {
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details.address, details)
|
||||
.unwrap();
|
||||
}
|
||||
let all_shares: [_; 3] =
|
||||
std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
|
||||
for share in all_shares.iter() {
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, share.epoch_id), share)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for times in [
|
||||
time_configuration.public_key_submission_time_secs,
|
||||
time_configuration.dealing_exchange_time_secs,
|
||||
time_configuration.verification_key_submission_time_secs,
|
||||
time_configuration.verification_key_validation_time_secs,
|
||||
time_configuration.verification_key_finalization_time_secs,
|
||||
] {
|
||||
env.block.time = env.block.time.plus_seconds(times);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
}
|
||||
let curr_epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
|
||||
assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2);
|
||||
|
||||
// epoch hasn't advanced as we are still in the threshold range
|
||||
try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap();
|
||||
assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2);
|
||||
assert_eq!(CURRENT_EPOCH.load(&deps.storage).unwrap(), curr_epoch);
|
||||
|
||||
*GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = (
|
||||
Member {
|
||||
addr: "owner4".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
);
|
||||
// epoch hasn't advanced as we are still in the threshold range
|
||||
try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap();
|
||||
assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2);
|
||||
assert_eq!(CURRENT_EPOCH.load(&deps.storage).unwrap(), curr_epoch);
|
||||
|
||||
*GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = (
|
||||
Member {
|
||||
addr: "owner5".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
);
|
||||
try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap();
|
||||
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
|
||||
let next_epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
|
||||
assert_eq!(
|
||||
next_epoch,
|
||||
Epoch::new(
|
||||
EpochState::default(),
|
||||
curr_epoch.epoch_id + 1,
|
||||
curr_epoch.time_configuration,
|
||||
env.block.time,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initialising_dkg() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let initial_epoch_info = CURRENT_EPOCH.load(&deps.storage).unwrap();
|
||||
assert!(initial_epoch_info.finish_timestamp.is_none());
|
||||
|
||||
// can only be executed by the admin
|
||||
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info("not an admin", &[]))
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::Admin(AdminError::NotAdmin {}), res);
|
||||
|
||||
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[]));
|
||||
assert!(res.is_ok());
|
||||
|
||||
// can't be initialised more than once
|
||||
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[]))
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::AlreadyInitialised, res);
|
||||
|
||||
// sets the correct epoch data
|
||||
let epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
|
||||
assert_eq!(epoch.epoch_id, 0);
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.time_configuration,
|
||||
initial_epoch_info.time_configuration
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp.unwrap(),
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_state() {
|
||||
let mut deps = init_contract();
|
||||
let all_details: [_; 100] = std::array::from_fn(|i| dealer_details_fixture(i as u64));
|
||||
|
||||
THRESHOLD.save(deps.as_mut().storage, &42).unwrap();
|
||||
for details in all_details.iter() {
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details.address, details)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
reset_dkg_state(deps.as_mut().storage).unwrap();
|
||||
|
||||
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
|
||||
for details in all_details {
|
||||
assert!(current_dealers()
|
||||
.may_load(deps.as_mut().storage, &details.address)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
past_dealers()
|
||||
.load(&deps.storage, &details.address)
|
||||
.unwrap(),
|
||||
details
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_threshold() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
|
||||
|
||||
assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none());
|
||||
|
||||
for i in 1..101 {
|
||||
let address = Addr::unchecked(format!("dealer{}", i));
|
||||
current_dealers()
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
&address,
|
||||
&DealerDetails {
|
||||
address: address.clone(),
|
||||
bte_public_key_with_proof: "bte_public_key_with_proof".to_string(),
|
||||
ed25519_identity: "identity".to_string(),
|
||||
announce_address: "127.0.0.1".to_string(),
|
||||
assigned_index: i,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
assert_eq!(
|
||||
THRESHOLD.may_load(deps.as_mut().storage).unwrap().unwrap(),
|
||||
67
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
|
||||
use crate::epoch_state::transactions::reset_dkg_state;
|
||||
use crate::epoch_state::utils::check_state_completion;
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{Deps, DepsMut, Env, Response};
|
||||
use nym_coconut_dkg_common::types::{Epoch, EpochState};
|
||||
|
||||
fn ensure_can_advance_state(
|
||||
deps: Deps<'_>,
|
||||
env: &Env,
|
||||
current_epoch: &Epoch,
|
||||
) -> Result<(), ContractError> {
|
||||
if current_epoch.state == EpochState::WaitingInitialisation {
|
||||
return Err(ContractError::WaitingInitialisation);
|
||||
}
|
||||
|
||||
// check if we completed the state, so we could short circuit the deadline
|
||||
if check_state_completion(deps.storage, current_epoch)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// otherwise fallback to the deadline
|
||||
if let Some(finish_timestamp) = current_epoch.deadline {
|
||||
if finish_timestamp > env.block.time {
|
||||
return Err(ContractError::EarlyEpochStateAdvancement(
|
||||
finish_timestamp
|
||||
.minus_seconds(env.block.time.seconds())
|
||||
.seconds(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response, ContractError> {
|
||||
// TODO: the only case where this can retrigger itself is when insufficient number of parties completed it, i.e. we don't have threshold
|
||||
|
||||
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
|
||||
// checks whether the given phase has either completed or reached its deadline
|
||||
ensure_can_advance_state(deps.as_ref(), &env, ¤t_epoch)?;
|
||||
|
||||
let next_state = match current_epoch.state.next() {
|
||||
Some(next_state) => next_state,
|
||||
None => {
|
||||
debug_assert!(current_epoch.state.is_in_progress());
|
||||
// TODO: that's for the future because it will involve more changes in the other bits of the codebase
|
||||
// but change epoch_id upon extending time of the "in progress" phase and instead store a map of
|
||||
// [current_epoch_id => epoch_id_of_keys_creation] for key retrieval
|
||||
EpochState::InProgress
|
||||
}
|
||||
};
|
||||
|
||||
// if we're advancing into dealing exchange, we need to set the threshold value based on the number of registered dealers
|
||||
if next_state.is_dealing_exchange() {
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
let registered_dealers = current_epoch.state_progress.registered_dealers as u64;
|
||||
// set the threshold to 2/3 amount of registered dealers
|
||||
let threshold = (2 * registered_dealers + 3 - 1) / 3;
|
||||
THRESHOLD.save(deps.storage, &threshold)?;
|
||||
}
|
||||
|
||||
// edge case: we have completed DKG with fewer than threshold number of verified keys.
|
||||
// we have no choice but to reset since no credentials can be issued anyway.
|
||||
// TODO: is this actually a desired behaviour?
|
||||
let next_epoch = if next_state.is_in_progress() {
|
||||
let threshold = THRESHOLD.load(deps.storage)?;
|
||||
if (current_epoch.state_progress.verified_keys as u64) < threshold {
|
||||
reset_dkg_state(deps.storage)?;
|
||||
current_epoch.next_reset(env.block.time)
|
||||
} else {
|
||||
current_epoch.update(next_state, env.block.time)
|
||||
}
|
||||
} else {
|
||||
current_epoch.update(next_state, env.block.time)
|
||||
};
|
||||
|
||||
// update the epoch state
|
||||
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
|
||||
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::epoch_state::transactions::try_initiate_dkg;
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError::EarlyEpochStateAdvancement;
|
||||
use crate::state::storage::STATE;
|
||||
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{StdResult, Storage};
|
||||
use nym_coconut_dkg_common::types::TimeConfiguration;
|
||||
|
||||
#[test]
|
||||
fn short_circuit_advance_state() {
|
||||
fn epoch_in_state(state: EpochState, env: &Env) -> Epoch {
|
||||
Epoch::new(state, 0, Default::default(), env.block.time)
|
||||
}
|
||||
|
||||
fn set_epoch(storage: &mut dyn Storage, epoch: Epoch) {
|
||||
CURRENT_EPOCH.save(storage, &epoch).unwrap();
|
||||
}
|
||||
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
// it's never possible to short-circuit `WaitingInitialisation`
|
||||
let epoch = epoch_in_state(EpochState::WaitingInitialisation, &env);
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
// neither PublicKeySubmission (in either resharing or non-resharing)
|
||||
let epoch = epoch_in_state(EpochState::PublicKeySubmission { resharing: false }, &env);
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let epoch = epoch_in_state(EpochState::PublicKeySubmission { resharing: true }, &env);
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let key_size = STATE.load(&deps.storage).unwrap().key_size;
|
||||
|
||||
THRESHOLD.save(deps.as_mut().storage, &3).unwrap();
|
||||
|
||||
// we can short-circuit `DealingExchange` if all dealers submitted their dealings
|
||||
|
||||
// no dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false }, &env);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
// some dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false }, &env);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_dealings = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
// all dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false }, &env);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_dealings = key_size * 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_ok());
|
||||
check_epoch_state(
|
||||
deps.as_ref().storage,
|
||||
EpochState::VerificationKeySubmission { resharing: false },
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// no dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: true }, &env);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.registered_resharing_dealers = 4;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
// some dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: true }, &env);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.registered_resharing_dealers = 4;
|
||||
epoch.state_progress.submitted_dealings = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
// all dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: true }, &env);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.registered_resharing_dealers = 4;
|
||||
epoch.state_progress.submitted_dealings = key_size * 4;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_ok());
|
||||
check_epoch_state(
|
||||
deps.as_ref().storage,
|
||||
EpochState::VerificationKeySubmission { resharing: true },
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// we can short-circuit `VerificationKeySubmission` if all dealers submitted their verification keys
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeySubmission { resharing: false },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeySubmission { resharing: true },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeySubmission { resharing: false },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_key_shares = 4;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeySubmission { resharing: true },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_key_shares = 4;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeySubmission { resharing: false },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_ok());
|
||||
check_epoch_state(
|
||||
deps.as_ref().storage,
|
||||
EpochState::VerificationKeyValidation { resharing: false },
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeySubmission { resharing: true },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_ok());
|
||||
check_epoch_state(
|
||||
deps.as_ref().storage,
|
||||
EpochState::VerificationKeyValidation { resharing: true },
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// can't short-circuit `VerificationKeyValidation` => we rely on multisig votes here
|
||||
let epoch = epoch_in_state(
|
||||
EpochState::VerificationKeyValidation { resharing: false },
|
||||
&env,
|
||||
);
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let epoch = epoch_in_state(
|
||||
EpochState::VerificationKeyValidation { resharing: true },
|
||||
&env,
|
||||
);
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
// we can short-circuit `VerificationKeyFinalization` if all submitted keys got verified
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeyFinalization { resharing: false },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeyFinalization { resharing: true },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeyFinalization { resharing: false },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
epoch.state_progress.verified_keys = 4;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeyFinalization { resharing: true },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
epoch.state_progress.verified_keys = 4;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeyFinalization { resharing: false },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
epoch.state_progress.verified_keys = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_ok());
|
||||
check_epoch_state(deps.as_ref().storage, EpochState::InProgress).unwrap();
|
||||
|
||||
let mut epoch = epoch_in_state(
|
||||
EpochState::VerificationKeyFinalization { resharing: true },
|
||||
&env,
|
||||
);
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
epoch.state_progress.verified_keys = 5;
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_ok());
|
||||
check_epoch_state(deps.as_ref().storage, EpochState::InProgress).unwrap();
|
||||
|
||||
// it's never possible to short-circuit `InProgress`
|
||||
let epoch = epoch_in_state(EpochState::InProgress, &env);
|
||||
set_epoch(deps.as_mut().storage, epoch);
|
||||
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advance_state_with_deadline() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
|
||||
// can't advance the state if dkg hasn't been initiated
|
||||
assert_eq!(
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
ContractError::WaitingInitialisation
|
||||
);
|
||||
|
||||
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
|
||||
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.deadline.unwrap(),
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs)
|
||||
);
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs - 1);
|
||||
assert_eq!(
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(1)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(1);
|
||||
|
||||
// add some dealers to prevent short-circuiting
|
||||
CURRENT_EPOCH
|
||||
.update(deps.as_mut().storage, |mut e| -> StdResult<_> {
|
||||
e.state_progress.registered_dealers = 42;
|
||||
Ok(e)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs);
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::DealingExchange { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.deadline.unwrap(),
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.dealing_exchange_time_secs)
|
||||
);
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.dealing_exchange_time_secs - 2);
|
||||
assert_eq!(
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(2)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(3);
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::VerificationKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.deadline.unwrap(),
|
||||
env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_submission_time_secs
|
||||
)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_submission_time_secs
|
||||
- 2,
|
||||
);
|
||||
assert_eq!(
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(2)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(3);
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::VerificationKeyValidation { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.deadline.unwrap(),
|
||||
env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_validation_time_secs
|
||||
)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_validation_time_secs
|
||||
- 3,
|
||||
);
|
||||
assert_eq!(
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(3)
|
||||
);
|
||||
|
||||
// add some key shares to prevent short-circuiting
|
||||
CURRENT_EPOCH
|
||||
.update(deps.as_mut().storage, |mut e| -> StdResult<_> {
|
||||
e.state_progress.submitted_key_shares = 42;
|
||||
Ok(e)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(3);
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::VerificationKeyFinalization { resharing: false }
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
epoch.deadline.unwrap(),
|
||||
env.block.time.plus_seconds(
|
||||
epoch
|
||||
.time_configuration
|
||||
.verification_key_finalization_time_secs
|
||||
)
|
||||
);
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().verification_key_finalization_time_secs - 1);
|
||||
assert_eq!(
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(1)
|
||||
);
|
||||
|
||||
// add some finalized keys to prevent reset
|
||||
CURRENT_EPOCH
|
||||
.update(deps.as_mut().storage, |mut e| -> StdResult<_> {
|
||||
e.state_progress.verified_keys = 42;
|
||||
Ok(e)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(1);
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(epoch.state, EpochState::InProgress);
|
||||
assert_eq!(
|
||||
epoch.deadline.unwrap(),
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.in_progress_time_secs)
|
||||
);
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.in_progress_time_secs - 100);
|
||||
assert_eq!(
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(100)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(50);
|
||||
assert_eq!(
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
|
||||
EarlyEpochStateAdvancement(50)
|
||||
);
|
||||
|
||||
// Group hasn't changed, so we remain in the same epoch, with updated finish timestamp
|
||||
env.block.time = env.block.time.plus_seconds(100);
|
||||
let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
let mut expected_epoch = Epoch::new(
|
||||
EpochState::InProgress,
|
||||
prev_epoch.epoch_id,
|
||||
prev_epoch.time_configuration,
|
||||
env.block.time,
|
||||
);
|
||||
expected_epoch.state_progress = curr_epoch.state_progress;
|
||||
assert_eq!(curr_epoch, expected_epoch);
|
||||
|
||||
// advancing from key finalization without threshold keys verified results in reset
|
||||
THRESHOLD.save(deps.as_mut().storage, &42).unwrap();
|
||||
let mut epoch = Epoch::new(
|
||||
EpochState::VerificationKeyFinalization { resharing: true },
|
||||
10,
|
||||
TimeConfiguration::default(),
|
||||
env.block.time,
|
||||
);
|
||||
|
||||
// fewer than the threshold
|
||||
epoch.state_progress.verified_keys = 41;
|
||||
CURRENT_EPOCH.save(deps.as_mut().storage, &epoch).unwrap();
|
||||
env.block.time = env.block.time.plus_seconds(5000000);
|
||||
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
let expected_epoch = Epoch::new(
|
||||
EpochState::PublicKeySubmission { resharing: false },
|
||||
epoch.epoch_id + 1,
|
||||
epoch.time_configuration,
|
||||
env.block.time,
|
||||
);
|
||||
assert_eq!(curr_epoch, expected_epoch);
|
||||
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_threshold() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
|
||||
|
||||
assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none());
|
||||
|
||||
CURRENT_EPOCH
|
||||
.update(deps.as_mut().storage, |mut e| -> StdResult<_> {
|
||||
e.state_progress.registered_dealers = 100;
|
||||
Ok(e)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
try_advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
assert_eq!(
|
||||
THRESHOLD.may_load(deps.as_mut().storage).unwrap().unwrap(),
|
||||
67
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::DKG_ADMIN;
|
||||
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, Storage};
|
||||
use nym_coconut_dkg_common::types::{Epoch, EpochState};
|
||||
|
||||
pub use advance_epoch_state::try_advance_epoch_state;
|
||||
|
||||
pub mod advance_epoch_state;
|
||||
|
||||
fn reset_dkg_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
THRESHOLD.remove(storage);
|
||||
|
||||
// dealings are preserved in the storage and saved per epoch, so we don't have to do anything about them
|
||||
// the same is true for dealer details
|
||||
// and epoch progress is reset when new struct is constructed
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn try_initiate_dkg(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
// only the admin is allowed to kick start the process
|
||||
DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
|
||||
|
||||
let epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
if !matches!(epoch.state, EpochState::WaitingInitialisation) {
|
||||
return Err(ContractError::AlreadyInitialised);
|
||||
}
|
||||
|
||||
// the first exchange won't involve resharing
|
||||
let initial_state = EpochState::PublicKeySubmission { resharing: false };
|
||||
let initial_epoch = Epoch::new(initial_state, 0, epoch.time_configuration, env.block.time);
|
||||
CURRENT_EPOCH.save(deps.storage, &initial_epoch)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub(crate) fn try_trigger_reset(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
// only the admin is allowed to trigger DKG reset
|
||||
DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
|
||||
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
|
||||
// only allow reset when the DKG exchange isn't in progress
|
||||
if !current_epoch.state.is_in_progress() {
|
||||
return Err(ContractError::CantReshareDuringExchange);
|
||||
}
|
||||
|
||||
let next_epoch = current_epoch.next_reset(env.block.time);
|
||||
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
|
||||
|
||||
reset_dkg_state(deps.storage)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub(crate) fn try_trigger_resharing(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
// only the admin is allowed to trigger DKG resharing
|
||||
DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
|
||||
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
|
||||
// only allow resharing when the DKG exchange isn't in progress
|
||||
if !current_epoch.state.is_in_progress() {
|
||||
return Err(ContractError::CantReshareDuringExchange);
|
||||
}
|
||||
|
||||
let next_epoch = current_epoch.next_resharing(env.block.time);
|
||||
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
|
||||
|
||||
reset_dkg_state(deps.storage)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cw_controllers::AdminError;
|
||||
use nym_coconut_dkg_common::types::EpochState;
|
||||
|
||||
#[test]
|
||||
fn initialising_dkg() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let initial_epoch_info = CURRENT_EPOCH.load(&deps.storage).unwrap();
|
||||
assert!(initial_epoch_info.deadline.is_none());
|
||||
|
||||
// can only be executed by the admin
|
||||
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info("not an admin", &[]))
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::Admin(AdminError::NotAdmin {}), res);
|
||||
|
||||
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[]));
|
||||
assert!(res.is_ok());
|
||||
|
||||
// can't be initialised more than once
|
||||
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[]))
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::AlreadyInitialised, res);
|
||||
|
||||
// sets the correct epoch data
|
||||
let epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
|
||||
assert_eq!(epoch.epoch_id, 0);
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.time_configuration,
|
||||
initial_epoch_info.time_configuration
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.deadline.unwrap(),
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_state() {
|
||||
let mut deps = init_contract();
|
||||
|
||||
THRESHOLD.save(deps.as_mut().storage, &42).unwrap();
|
||||
|
||||
reset_dkg_state(deps.as_mut().storage).unwrap();
|
||||
|
||||
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,52 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::STATE;
|
||||
use cosmwasm_std::Storage;
|
||||
use nym_coconut_dkg_common::types::EpochState;
|
||||
use nym_coconut_dkg_common::types::{Epoch, EpochState};
|
||||
|
||||
// check if we completed the state, so we could short circuit the deadline
|
||||
pub(crate) fn check_state_completion(
|
||||
storage: &dyn Storage,
|
||||
epoch: &Epoch,
|
||||
) -> Result<bool, ContractError> {
|
||||
let contract_state = STATE.load(storage)?;
|
||||
|
||||
match epoch.state {
|
||||
EpochState::WaitingInitialisation => Ok(false),
|
||||
// to check this one we'd need to query for all group members, but we can't rely on this
|
||||
EpochState::PublicKeySubmission { .. } => Ok(false),
|
||||
|
||||
// if every dealer has submitted all dealings, we're done
|
||||
EpochState::DealingExchange { resharing } => {
|
||||
// during resharing, we only expect to receive dealings from resharing dealers
|
||||
let expected_dealings = if !resharing {
|
||||
contract_state.key_size * epoch.state_progress.registered_dealers
|
||||
} else {
|
||||
contract_state.key_size * epoch.state_progress.registered_resharing_dealers
|
||||
};
|
||||
|
||||
Ok(expected_dealings == epoch.state_progress.submitted_dealings)
|
||||
}
|
||||
|
||||
// if every dealer has submitted its partial key, we're done
|
||||
EpochState::VerificationKeySubmission { .. } => Ok(epoch
|
||||
.state_progress
|
||||
.submitted_key_shares
|
||||
== epoch.state_progress.registered_dealers),
|
||||
|
||||
// no short-circuiting this one since the voting is happening in the multisig contract
|
||||
EpochState::VerificationKeyValidation { .. } => Ok(false),
|
||||
|
||||
// if every submitted partial key has been verified, we're done
|
||||
EpochState::VerificationKeyFinalization { .. } => {
|
||||
Ok(epoch.state_progress.verified_keys == epoch.state_progress.submitted_key_shares)
|
||||
}
|
||||
EpochState::InProgress => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_epoch_state(
|
||||
storage: &dyn Storage,
|
||||
@@ -26,8 +68,142 @@ pub(crate) mod test {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers::init_contract;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::Timestamp;
|
||||
use nym_coconut_dkg_common::types::{Epoch, TimeConfiguration};
|
||||
|
||||
#[test]
|
||||
fn checking_state_completion() {
|
||||
fn epoch_in_state(state: EpochState) -> Epoch {
|
||||
Epoch::new(state, 0, Default::default(), Timestamp::from_seconds(69))
|
||||
}
|
||||
|
||||
let deps = init_contract();
|
||||
|
||||
// it's never possible to short-circuit `WaitingInitialisation`
|
||||
let epoch = epoch_in_state(EpochState::WaitingInitialisation);
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// neither PublicKeySubmission (in either resharing or non-resharing)
|
||||
let epoch = epoch_in_state(EpochState::PublicKeySubmission { resharing: false });
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let epoch = epoch_in_state(EpochState::PublicKeySubmission { resharing: true });
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let key_size = STATE.load(&deps.storage).unwrap().key_size;
|
||||
|
||||
// we can short-circuit `DealingExchange` if all dealers submitted their dealings
|
||||
|
||||
// no dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// some dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_dealings = 5;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// all dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_dealings = key_size * 5;
|
||||
assert!(check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// no dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: true });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.registered_resharing_dealers = 4;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// some dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: true });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.registered_resharing_dealers = 4;
|
||||
epoch.state_progress.submitted_dealings = 5;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// all dealings
|
||||
let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: true });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.registered_resharing_dealers = 4;
|
||||
epoch.state_progress.submitted_dealings = key_size * 4;
|
||||
assert!(check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// we can short-circuit `VerificationKeySubmission` if all dealers submitted their verification keys
|
||||
let mut epoch = epoch_in_state(EpochState::VerificationKeySubmission { resharing: false });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch = epoch_in_state(EpochState::VerificationKeySubmission { resharing: true });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch = epoch_in_state(EpochState::VerificationKeySubmission { resharing: false });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_key_shares = 4;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch = epoch_in_state(EpochState::VerificationKeySubmission { resharing: true });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_key_shares = 4;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch = epoch_in_state(EpochState::VerificationKeySubmission { resharing: false });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
assert!(check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch = epoch_in_state(EpochState::VerificationKeySubmission { resharing: true });
|
||||
epoch.state_progress.registered_dealers = 5;
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
assert!(check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// can't short-circuit `VerificationKeyValidation` => we rely on multisig votes here
|
||||
let epoch = epoch_in_state(EpochState::VerificationKeyValidation { resharing: false });
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let epoch = epoch_in_state(EpochState::VerificationKeyValidation { resharing: true });
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// we can short-circuit `VerificationKeyFinalization` if all submitted keys got verified
|
||||
let mut epoch =
|
||||
epoch_in_state(EpochState::VerificationKeyFinalization { resharing: false });
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch = epoch_in_state(EpochState::VerificationKeyFinalization { resharing: true });
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch =
|
||||
epoch_in_state(EpochState::VerificationKeyFinalization { resharing: false });
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
epoch.state_progress.verified_keys = 4;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch = epoch_in_state(EpochState::VerificationKeyFinalization { resharing: true });
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
epoch.state_progress.verified_keys = 4;
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch =
|
||||
epoch_in_state(EpochState::VerificationKeyFinalization { resharing: false });
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
epoch.state_progress.verified_keys = 5;
|
||||
assert!(check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
let mut epoch = epoch_in_state(EpochState::VerificationKeyFinalization { resharing: true });
|
||||
epoch.state_progress.submitted_key_shares = 5;
|
||||
epoch.state_progress.verified_keys = 5;
|
||||
assert!(check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
|
||||
// it's never possible to short-circuit `InProgress`
|
||||
let epoch = epoch_in_state(EpochState::InProgress);
|
||||
assert!(!check_state_completion(&deps.storage, &epoch).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn check_state() {
|
||||
let mut deps = init_contract();
|
||||
|
||||
@@ -45,11 +45,8 @@ pub enum ContractError {
|
||||
expected_state: String,
|
||||
},
|
||||
|
||||
#[error("This sender is not a dealer for the current epoch")]
|
||||
NotADealer,
|
||||
|
||||
#[error("This sender is not a dealer for the current resharing epoch")]
|
||||
NotAnInitialDealer,
|
||||
#[error("This sender is not a dealer for epoch {epoch_id}")]
|
||||
NotADealer { epoch_id: EpochId },
|
||||
|
||||
#[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 {
|
||||
@@ -136,4 +133,13 @@ pub enum ContractError {
|
||||
value: String,
|
||||
error_message: String,
|
||||
},
|
||||
|
||||
#[error("cannot perform DKG reset during an ongoing exchange")]
|
||||
CantResetDuringExchange,
|
||||
|
||||
#[error("cannot perform DKG resharing during an ongoing exchange")]
|
||||
CantReshareDuringExchange,
|
||||
|
||||
#[error("retrieved the maximum allowed number of cw4 members. for more the contracts have to be refactored")]
|
||||
PossiblyIncompleteGroupMembersQuery,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
pub(crate) type Dealer<'a> = &'a Addr;
|
||||
|
||||
mod constants;
|
||||
pub mod contract;
|
||||
mod dealers;
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::contract::instantiate;
|
||||
use crate::dealers::storage::current_dealers;
|
||||
use crate::dealers::storage::{DEALERS_INDICES, EPOCH_DEALERS_MAP};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
|
||||
use cosmwasm_std::{
|
||||
from_binary, to_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, OwnedDeps,
|
||||
QuerierResult, SystemResult, WasmQuery,
|
||||
};
|
||||
use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse};
|
||||
use nym_coconut_dkg_common::dealer::DealerRegistrationDetails;
|
||||
use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS;
|
||||
use nym_coconut_dkg_common::msg::InstantiateMsg;
|
||||
use nym_coconut_dkg_common::types::DealerDetails;
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, EpochId};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::fixtures::TEST_MIX_DENOM;
|
||||
@@ -20,23 +22,55 @@ pub const ADMIN_ADDRESS: &str = "admin address";
|
||||
pub const GROUP_CONTRACT: &str = "group contract address";
|
||||
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
|
||||
|
||||
// wtf, why is this a thing?
|
||||
pub(crate) static GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(Vec::new());
|
||||
|
||||
pub fn re_register_dealer(deps: DepsMut, dealer: &Addr) {
|
||||
let epoch_id = CURRENT_EPOCH.load(deps.storage).unwrap().epoch_id;
|
||||
let previous = epoch_id - 1;
|
||||
let details = EPOCH_DEALERS_MAP
|
||||
.load(deps.storage, (previous, dealer))
|
||||
.unwrap();
|
||||
EPOCH_DEALERS_MAP
|
||||
.save(deps.storage, (epoch_id, dealer), &details)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn add_current_dealer(deps: DepsMut<'_>, details: &DealerDetails) {
|
||||
let epoch_id = CURRENT_EPOCH.load(deps.storage).unwrap().epoch_id;
|
||||
insert_dealer(deps, epoch_id, details)
|
||||
}
|
||||
|
||||
pub fn insert_dealer(deps: DepsMut<'_>, epoch_id: EpochId, details: &DealerDetails) {
|
||||
DEALERS_INDICES
|
||||
.save(deps.storage, &details.address, &details.assigned_index)
|
||||
.unwrap();
|
||||
|
||||
EPOCH_DEALERS_MAP
|
||||
.save(
|
||||
deps.storage,
|
||||
(epoch_id, &details.address),
|
||||
&DealerRegistrationDetails {
|
||||
bte_public_key_with_proof: details.bte_public_key_with_proof.clone(),
|
||||
ed25519_identity: details.ed25519_identity.clone(),
|
||||
announce_address: details.announce_address.clone(),
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn add_fixture_dealer(deps: DepsMut<'_>) {
|
||||
let owner = Addr::unchecked("owner");
|
||||
current_dealers()
|
||||
.save(
|
||||
deps.storage,
|
||||
&owner,
|
||||
&DealerDetails {
|
||||
address: owner.clone(),
|
||||
bte_public_key_with_proof: String::new(),
|
||||
ed25519_identity: String::new(),
|
||||
announce_address: String::new(),
|
||||
assigned_index: 100,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
add_current_dealer(
|
||||
deps,
|
||||
&DealerDetails {
|
||||
address: owner.clone(),
|
||||
bte_public_key_with_proof: String::new(),
|
||||
ed25519_identity: String::new(),
|
||||
announce_address: String::new(),
|
||||
assigned_index: 100,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn querier_handler(query: &WasmQuery) -> QuerierResult {
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{Addr, Order, Storage};
|
||||
use cosmwasm_std::Addr;
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_coconut_dkg_common::verification_key::ContractVKShare;
|
||||
|
||||
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT: u32 = 75;
|
||||
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT: u32 = 30;
|
||||
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT: u32 = 10;
|
||||
|
||||
type VKShareKey<'a> = (&'a Addr, EpochId);
|
||||
|
||||
@@ -35,21 +33,3 @@ pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare,
|
||||
};
|
||||
IndexedMap::new(VK_SHARES_PK_NAMESPACE, indexes)
|
||||
}
|
||||
|
||||
pub(crate) fn verified_dealers(storage: &dyn Storage) -> Result<Vec<Addr>, ContractError> {
|
||||
let epoch_id = CURRENT_EPOCH.load(storage)?.epoch_id;
|
||||
Ok(vk_shares()
|
||||
.idx
|
||||
.epoch_id
|
||||
.prefix(epoch_id)
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.flatten()
|
||||
.filter_map(|(_, share)| {
|
||||
if share.verified {
|
||||
Some(share.owner)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::constants::BLOCK_TIME_FOR_VERIFICATION_SECS;
|
||||
use crate::dealers::storage as dealers_storage;
|
||||
use crate::dealers::storage::get_dealer_details;
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
@@ -25,11 +25,10 @@ pub fn try_commit_verification_key_share(
|
||||
deps.storage,
|
||||
EpochState::VerificationKeySubmission { resharing },
|
||||
)?;
|
||||
// ensure the sender is a dealer
|
||||
let details = dealers_storage::current_dealers()
|
||||
.load(deps.storage, &info.sender)
|
||||
.map_err(|_| ContractError::NotADealer)?;
|
||||
let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
|
||||
let mut epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
let epoch_id = epoch.epoch_id;
|
||||
|
||||
let details = get_dealer_details(deps.storage, &info.sender, epoch_id)?;
|
||||
if vk_shares()
|
||||
.may_load(deps.storage, (&info.sender, epoch_id))?
|
||||
.is_some()
|
||||
@@ -60,6 +59,9 @@ pub fn try_commit_verification_key_share(
|
||||
.plus_seconds(BLOCK_TIME_FOR_VERIFICATION_SECS),
|
||||
)?;
|
||||
|
||||
epoch.state_progress.submitted_key_shares += 1;
|
||||
CURRENT_EPOCH.save(deps.storage, &epoch)?;
|
||||
|
||||
Ok(Response::new().add_message(msg))
|
||||
}
|
||||
|
||||
@@ -75,7 +77,9 @@ pub fn try_verify_verification_key_share(
|
||||
deps.storage,
|
||||
EpochState::VerificationKeyFinalization { resharing },
|
||||
)?;
|
||||
let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
|
||||
let mut epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
let epoch_id = epoch.epoch_id;
|
||||
|
||||
MULTISIG.assert_admin(deps.as_ref(), &info.sender)?;
|
||||
vk_shares().update(deps.storage, (&owner, epoch_id), |vk_share| {
|
||||
vk_share
|
||||
@@ -88,15 +92,20 @@ pub fn try_verify_verification_key_share(
|
||||
})
|
||||
})?;
|
||||
|
||||
epoch.state_progress.verified_keys += 1;
|
||||
CURRENT_EPOCH.save(deps.storage, &epoch)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg};
|
||||
use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg};
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, MULTISIG_CONTRACT};
|
||||
use crate::support::tests::helpers::{
|
||||
add_current_dealer, add_fixture_dealer, ADMIN_ADDRESS, MULTISIG_CONTRACT,
|
||||
};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::Addr;
|
||||
use cw_controllers::AdminError;
|
||||
@@ -117,12 +126,12 @@ mod tests {
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let dealer = Addr::unchecked("requester");
|
||||
let announce_address = String::from("localhost");
|
||||
let dealer_details = DealerDetails {
|
||||
@@ -132,9 +141,7 @@ mod tests {
|
||||
announce_address: announce_address.clone(),
|
||||
assigned_index: 1,
|
||||
};
|
||||
dealers_storage::current_dealers()
|
||||
.save(deps.as_mut().storage, &dealer, &dealer_details)
|
||||
.unwrap();
|
||||
add_current_dealer(deps.as_mut(), &dealer_details);
|
||||
|
||||
try_commit_verification_key_share(deps.as_mut(), env, info.clone(), share.clone(), false)
|
||||
.unwrap();
|
||||
@@ -182,12 +189,12 @@ mod tests {
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let ret = try_commit_verification_key_share(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
@@ -196,7 +203,7 @@ mod tests {
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(ret, ContractError::NotADealer);
|
||||
assert_eq!(ret, ContractError::NotADealer { epoch_id: 0 });
|
||||
|
||||
let dealer = Addr::unchecked("requester");
|
||||
let dealer_details = DealerDetails {
|
||||
@@ -206,9 +213,7 @@ mod tests {
|
||||
announce_address: String::new(),
|
||||
assigned_index: 1,
|
||||
};
|
||||
dealers_storage::current_dealers()
|
||||
.save(deps.as_mut().storage, &dealer, &dealer_details)
|
||||
.unwrap();
|
||||
add_current_dealer(deps.as_mut(), &dealer_details);
|
||||
|
||||
try_commit_verification_key_share(
|
||||
deps.as_mut(),
|
||||
@@ -256,22 +261,22 @@ mod tests {
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().verification_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
|
||||
let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone(), false)
|
||||
.unwrap_err();
|
||||
@@ -304,12 +309,12 @@ mod tests {
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
|
||||
let dealer_details = DealerDetails {
|
||||
address: Addr::unchecked(&owner),
|
||||
@@ -318,25 +323,20 @@ mod tests {
|
||||
announce_address: String::new(),
|
||||
assigned_index: 1,
|
||||
};
|
||||
dealers_storage::current_dealers()
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
&Addr::unchecked(&owner),
|
||||
&dealer_details,
|
||||
)
|
||||
.unwrap();
|
||||
add_current_dealer(deps.as_mut(), &dealer_details);
|
||||
|
||||
try_commit_verification_key_share(deps.as_mut(), env.clone(), info, share, false).unwrap();
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().verification_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
|
||||
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner, false).unwrap();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ cw-storage-plus = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
|
||||
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
|
||||
bs58 = "0.4.0"
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
@@ -30,6 +32,8 @@ cw-multi-test = { workspace = true }
|
||||
cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
|
||||
rand_chacha = "0.2"
|
||||
|
||||
[[test]]
|
||||
name = "coconut-test"
|
||||
path = "src/tests.rs"
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response};
|
||||
use cosmwasm_std::{Addr, Coin, Empty};
|
||||
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
|
||||
use nym_multisig_contract_common::error::ContractError;
|
||||
use nym_multisig_contract_common::state::CONFIG;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -21,14 +19,6 @@ pub struct MigrateMsg {
|
||||
pub coconut_dkg_address: String,
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
let mut cfg = CONFIG.load(deps.storage)?;
|
||||
cfg.coconut_bandwidth_addr = deps.api.addr_validate(&msg.coconut_bandwidth_address)?;
|
||||
CONFIG.save(deps.storage, &cfg)?;
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
pub fn mock_app(init_funds: &[Coin]) -> App {
|
||||
AppBuilder::new().build(|router, _, storage| {
|
||||
router
|
||||
@@ -65,7 +55,7 @@ pub fn contract_multisig() -> Box<dyn Contract<Empty>> {
|
||||
cw3_flex_multisig::contract::instantiate,
|
||||
cw3_flex_multisig::contract::query,
|
||||
)
|
||||
.with_migrate(migrate);
|
||||
.with_migrate(cw3_flex_multisig::contract::migrate);
|
||||
Box::new(contract)
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ fn spend_credential_creates_proposal() {
|
||||
|
||||
let msg = MigrateMsg {
|
||||
coconut_bandwidth_address: coconut_bandwidth_contract_addr.to_string(),
|
||||
coconut_dkg_address: "".to_string(),
|
||||
coconut_dkg_address: "dkg-address".to_string(),
|
||||
};
|
||||
app.migrate_contract(
|
||||
Addr::unchecked(OWNER),
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::helpers::{contract_bandwidth, contract_dkg, contract_group, contract_multisig};
|
||||
use cosmwasm_std::{coins, Addr};
|
||||
use cw3::{Cw3Contract, ProposalListResponse, Status, Vote};
|
||||
use cw4::{Cw4Contract, Member};
|
||||
use cw_multi_test::{App, AppBuilder, Executor};
|
||||
use cw_utils::{Duration, Threshold};
|
||||
use nym_coconut_bandwidth_contract_common::msg::InstantiateMsg as BandwidthInstantiateMsg;
|
||||
use nym_coconut_dkg_common::dealing::{chunk_dealing, DealingChunkInfo, MAX_DEALING_CHUNK_SIZE};
|
||||
use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
|
||||
use nym_coconut_dkg_common::msg::InstantiateMsg as DkgInstantiateMsg;
|
||||
use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
|
||||
use nym_coconut_dkg_common::types::{Epoch, State};
|
||||
use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg;
|
||||
use nym_multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg;
|
||||
use nym_multisig_contract_common::msg::MigrateMsg as MultisigMigrateMsg;
|
||||
use rand_chacha::rand_core::{RngCore, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use subtle_encoding::bech32;
|
||||
|
||||
pub const PREFIX: &str = "n";
|
||||
pub const TEST_DENOM: &str = "unym";
|
||||
|
||||
pub const BANDWIDTH_POOL: &str = "pool";
|
||||
|
||||
pub const BLOCK_TIME_SECS: u64 = 6;
|
||||
|
||||
pub fn test_rng() -> ChaCha20Rng {
|
||||
let dummy_seed = [42u8; 32];
|
||||
ChaCha20Rng::from_seed(dummy_seed)
|
||||
}
|
||||
|
||||
pub fn random_address(rng: &mut ChaCha20Rng) -> Addr {
|
||||
let mut bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
|
||||
Addr::unchecked(bech32::encode(PREFIX, bytes))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct TestSetup {
|
||||
pub app: App,
|
||||
pub rng: ChaCha20Rng,
|
||||
pub global_admin: Addr,
|
||||
|
||||
pub multisig_contract: Cw3Contract,
|
||||
pub group_contract: Cw4Contract,
|
||||
pub dkg_contract: Addr,
|
||||
pub bandwidth_contract: Addr,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TestSetup {
|
||||
pub fn new() -> Self {
|
||||
let mut rng = test_rng();
|
||||
|
||||
let global_admin = random_address(&mut rng);
|
||||
let mut app = AppBuilder::new().build(|router, _, storage| {
|
||||
router
|
||||
.bank
|
||||
.init_balance(storage, &global_admin, coins(1000000000000000, TEST_DENOM))
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let group_id = app.store_code(contract_group());
|
||||
let multisig_id = app.store_code(contract_multisig());
|
||||
let bandwidth_id = app.store_code(contract_bandwidth());
|
||||
let dkg_id = app.store_code(contract_dkg());
|
||||
|
||||
// 1. init group contract
|
||||
let group_contract = app
|
||||
.instantiate_contract(
|
||||
group_id,
|
||||
global_admin.clone(),
|
||||
&GroupInstantiateMsg {
|
||||
admin: Some(global_admin.to_string()),
|
||||
members: vec![],
|
||||
},
|
||||
&[],
|
||||
"group contract",
|
||||
Some(global_admin.to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 2. init multisig contract
|
||||
let multisig_contract = app
|
||||
.instantiate_contract(
|
||||
multisig_id,
|
||||
global_admin.clone(),
|
||||
&MultisigInstantiateMsg {
|
||||
group_addr: group_contract.to_string(),
|
||||
coconut_bandwidth_contract_address: group_contract.to_string(),
|
||||
coconut_dkg_contract_address: group_contract.to_string(),
|
||||
threshold: Threshold::AbsolutePercentage {
|
||||
percentage: "0.67".parse().unwrap(),
|
||||
},
|
||||
max_voting_period: Duration::Time(3600),
|
||||
executor: None,
|
||||
proposal_deposit: None,
|
||||
},
|
||||
&[],
|
||||
"multisig contract",
|
||||
Some(global_admin.to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 3. init bandwidth contract
|
||||
let bandwidth_contract = app
|
||||
.instantiate_contract(
|
||||
bandwidth_id,
|
||||
global_admin.clone(),
|
||||
&BandwidthInstantiateMsg {
|
||||
multisig_addr: multisig_contract.to_string(),
|
||||
pool_addr: BANDWIDTH_POOL.to_string(),
|
||||
mix_denom: TEST_DENOM.to_string(),
|
||||
},
|
||||
&[],
|
||||
"bandwidth contract",
|
||||
Some(global_admin.to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 4. init dkg contract
|
||||
let dkg_contract = app
|
||||
.instantiate_contract(
|
||||
dkg_id,
|
||||
global_admin.clone(),
|
||||
&DkgInstantiateMsg {
|
||||
group_addr: group_contract.to_string(),
|
||||
multisig_addr: multisig_contract.to_string(),
|
||||
time_configuration: None,
|
||||
mix_denom: TEST_DENOM.to_string(),
|
||||
key_size: 5,
|
||||
},
|
||||
&[],
|
||||
"dkg contract",
|
||||
Some(global_admin.to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 5.migrate multisig contract with addresses of bandwidth and dkg contracts
|
||||
app.migrate_contract(
|
||||
global_admin.clone(),
|
||||
multisig_contract.clone(),
|
||||
&MultisigMigrateMsg {
|
||||
coconut_bandwidth_address: bandwidth_contract.to_string(),
|
||||
coconut_dkg_address: dkg_contract.to_string(),
|
||||
},
|
||||
multisig_id,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
TestSetup {
|
||||
app,
|
||||
rng,
|
||||
global_admin,
|
||||
multisig_contract: Cw3Contract(multisig_contract),
|
||||
group_contract: Cw4Contract(group_contract),
|
||||
dkg_contract,
|
||||
bandwidth_contract,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_address(&mut self) -> Addr {
|
||||
random_address(&mut self.rng)
|
||||
}
|
||||
|
||||
pub fn next_block(&mut self) {
|
||||
self.app.update_block(|block| {
|
||||
block.height += 1;
|
||||
block.time = block.time.plus_seconds(BLOCK_TIME_SECS);
|
||||
})
|
||||
}
|
||||
|
||||
// if we ever want to expand those tests, those queries should be moved to contract specific structs
|
||||
// (kinda like what cw4 has)
|
||||
pub fn dkg_state(&self) -> State {
|
||||
self.app
|
||||
.wrap()
|
||||
.query_wasm_smart(&self.dkg_contract, &DkgQueryMsg::GetState {})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn epoch(&self) -> Epoch {
|
||||
self.app
|
||||
.wrap()
|
||||
.query_wasm_smart(&self.dkg_contract, &DkgQueryMsg::GetCurrentEpochState {})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// TODO: this will not go beyond first page
|
||||
pub fn all_proposals(&self) -> ProposalListResponse {
|
||||
self.app
|
||||
.wrap()
|
||||
.query_wasm_smart(
|
||||
&self.multisig_contract.0,
|
||||
&nym_multisig_contract_common::msg::QueryMsg::ListProposals {
|
||||
start_after: None,
|
||||
limit: None,
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn admin(&self) -> Addr {
|
||||
self.global_admin.clone()
|
||||
}
|
||||
|
||||
pub fn remove_group_member(&mut self, addr: &Addr) {
|
||||
self.app
|
||||
.execute_contract(
|
||||
self.admin(),
|
||||
self.group_contract.addr(),
|
||||
&nym_group_contract_common::msg::ExecuteMsg::UpdateMembers {
|
||||
remove: vec![addr.to_string()],
|
||||
add: vec![],
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_mock_group_member(&mut self, weight: Option<u64>) -> Addr {
|
||||
let member_addr = self.random_address();
|
||||
let weight = weight.unwrap_or(10);
|
||||
|
||||
self.app
|
||||
.execute_contract(
|
||||
self.admin(),
|
||||
self.group_contract.addr(),
|
||||
&nym_group_contract_common::msg::ExecuteMsg::UpdateMembers {
|
||||
remove: vec![],
|
||||
add: vec![Member {
|
||||
addr: member_addr.to_string(),
|
||||
weight,
|
||||
}],
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
member_addr
|
||||
}
|
||||
|
||||
pub fn begin_dkg(&mut self) {
|
||||
self.app
|
||||
.execute_contract(
|
||||
self.admin(),
|
||||
self.dkg_contract.clone(),
|
||||
&DkgExecuteMsg::InitiateDkg {},
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn skip_to_dkg_state_end(&mut self) {
|
||||
let epoch = self.epoch();
|
||||
|
||||
self.app.update_block(|block| {
|
||||
block.height += 42;
|
||||
if let Some(finish_timestamp) = epoch.deadline {
|
||||
block.time = finish_timestamp.plus_seconds(BLOCK_TIME_SECS);
|
||||
} else {
|
||||
block.time = block.time.plus_seconds(BLOCK_TIME_SECS)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn advance_dkg_epoch(&mut self) {
|
||||
self.skip_to_dkg_state_end();
|
||||
self.unchecked_advance_dkg_epoch();
|
||||
}
|
||||
|
||||
pub fn unchecked_advance_dkg_epoch(&mut self) {
|
||||
self.app
|
||||
.execute_contract(
|
||||
self.admin(),
|
||||
self.dkg_contract.clone(),
|
||||
&DkgExecuteMsg::AdvanceEpochState {},
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn submit_dummy_dkg_keys(&mut self, member: &Addr, resharing: bool) {
|
||||
let mut bte_key_with_proof = [0u8; 32];
|
||||
self.rng.fill_bytes(&mut bte_key_with_proof);
|
||||
let bte_key_with_proof = bs58::encode(&bte_key_with_proof).into_string();
|
||||
|
||||
let mut identity_key = [0u8; 32];
|
||||
self.rng.fill_bytes(&mut identity_key);
|
||||
let identity_key = bs58::encode(&identity_key).into_string();
|
||||
|
||||
let mut announce_address = [0u8; 16];
|
||||
self.rng.fill_bytes(&mut announce_address);
|
||||
let announce_address = bs58::encode(&announce_address).into_string();
|
||||
|
||||
self.app
|
||||
.execute_contract(
|
||||
member.clone(),
|
||||
self.dkg_contract.clone(),
|
||||
&DkgExecuteMsg::RegisterDealer {
|
||||
bte_key_with_proof,
|
||||
identity_key,
|
||||
announce_address,
|
||||
resharing,
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn submit_dummy_dealings(&mut self, member: &Addr, resharing: bool) {
|
||||
let dealings = self.dkg_state().key_size;
|
||||
|
||||
for dealing_index in 0..dealings {
|
||||
let mut dealing_bytes = vec![0u8; 5000];
|
||||
self.rng.fill_bytes(&mut dealing_bytes);
|
||||
|
||||
let chunks = DealingChunkInfo::construct(dealing_bytes.len(), MAX_DEALING_CHUNK_SIZE);
|
||||
self.app
|
||||
.execute_contract(
|
||||
member.clone(),
|
||||
self.dkg_contract.clone(),
|
||||
&DkgExecuteMsg::CommitDealingsMetadata {
|
||||
dealing_index,
|
||||
chunks,
|
||||
resharing,
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
self.next_block();
|
||||
|
||||
let chunks = chunk_dealing(dealing_index, dealing_bytes, MAX_DEALING_CHUNK_SIZE);
|
||||
for (_, chunk) in chunks {
|
||||
self.app
|
||||
.execute_contract(
|
||||
member.clone(),
|
||||
self.dkg_contract.clone(),
|
||||
&DkgExecuteMsg::CommitDealingsChunk { chunk },
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
self.next_block();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn submit_dummy_vk_key(&mut self, member: &Addr, resharing: bool) {
|
||||
let mut derived_vk = vec![0u8; 256];
|
||||
self.rng.fill_bytes(&mut derived_vk);
|
||||
|
||||
let share = bs58::encode(&derived_vk).into_string();
|
||||
|
||||
self.app
|
||||
.execute_contract(
|
||||
member.clone(),
|
||||
self.dkg_contract.clone(),
|
||||
&DkgExecuteMsg::CommitVerificationKeyShare { share, resharing },
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn validate_dummy_keys(&mut self, member: &Addr) {
|
||||
for proposal in self.all_proposals().proposals {
|
||||
if proposal.status == Status::Open {
|
||||
self.app
|
||||
.execute_contract(
|
||||
member.clone(),
|
||||
self.multisig_contract.addr(),
|
||||
&nym_multisig_contract_common::msg::ExecuteMsg::Vote {
|
||||
proposal_id: proposal.id,
|
||||
vote: Vote::Yes,
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize_dummy_dkg(&mut self) {
|
||||
for proposal in self.all_proposals().proposals {
|
||||
assert_eq!(proposal.status, Status::Passed);
|
||||
|
||||
self.app
|
||||
.execute_contract(
|
||||
self.admin(),
|
||||
self.multisig_contract.addr(),
|
||||
&nym_multisig_contract_common::msg::ExecuteMsg::Execute {
|
||||
proposal_id: proposal.id,
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn full_dummy_dkg(&mut self, members: Vec<Addr>, resharing: bool) {
|
||||
for member in &members {
|
||||
self.submit_dummy_dkg_keys(member, resharing);
|
||||
self.next_block();
|
||||
}
|
||||
self.advance_dkg_epoch();
|
||||
|
||||
for member in &members {
|
||||
self.submit_dummy_dealings(member, resharing);
|
||||
self.next_block();
|
||||
}
|
||||
self.advance_dkg_epoch();
|
||||
|
||||
for member in &members {
|
||||
self.submit_dummy_vk_key(member, resharing);
|
||||
self.next_block();
|
||||
}
|
||||
self.advance_dkg_epoch();
|
||||
|
||||
for member in &members {
|
||||
self.validate_dummy_keys(member);
|
||||
self.next_block();
|
||||
}
|
||||
self.advance_dkg_epoch();
|
||||
|
||||
self.finalize_dummy_dkg();
|
||||
self.next_block();
|
||||
|
||||
self.advance_dkg_epoch();
|
||||
}
|
||||
}
|
||||
@@ -5,3 +5,4 @@ mod deposit_and_release;
|
||||
mod helpers;
|
||||
mod spend_credential_creates_proposal;
|
||||
mod submit_vk_creates_proposal;
|
||||
mod test_wrapper;
|
||||
|
||||
@@ -5,13 +5,15 @@ use crate::coconut::error::Result;
|
||||
use cw3::{ProposalResponse, VoteResponse};
|
||||
use cw4::MemberResponse;
|
||||
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
|
||||
use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse};
|
||||
use nym_coconut_dkg_common::dealer::{
|
||||
DealerDetails, DealerDetailsResponse, RegisteredDealerDetails,
|
||||
};
|
||||
use nym_coconut_dkg_common::dealing::{
|
||||
DealerDealingsStatusResponse, DealingChunkInfo, DealingMetadata, DealingStatusResponse,
|
||||
PartialContractDealing,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::{
|
||||
ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData,
|
||||
ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId,
|
||||
PartialContractDealingData, State,
|
||||
};
|
||||
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
|
||||
@@ -47,10 +49,14 @@ pub trait Client {
|
||||
|
||||
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_registered_dealer_details(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
) -> Result<RegisteredDealerDetails>;
|
||||
|
||||
async fn get_dealer_dealings_status(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
@@ -94,6 +100,8 @@ pub trait Client {
|
||||
|
||||
async fn execute_proposal(&self, proposal_id: u64) -> Result<()>;
|
||||
|
||||
async fn can_advance_epoch_state(&self) -> Result<bool>;
|
||||
|
||||
async fn advance_epoch_state(&self) -> Result<()>;
|
||||
|
||||
async fn register_dealer(
|
||||
@@ -111,11 +119,7 @@ pub trait Client {
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult>;
|
||||
|
||||
async fn submit_dealing_chunk(
|
||||
&self,
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult>;
|
||||
async fn submit_dealing_chunk(&self, chunk: PartialContractDealing) -> Result<ExecuteResult>;
|
||||
|
||||
async fn submit_verification_key_share(
|
||||
&self,
|
||||
|
||||
@@ -43,7 +43,7 @@ impl CachedEpoch {
|
||||
async fn update(&mut self, epoch: Epoch) -> Result<()> {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let validity_duration = if let Some(epoch_finish) = epoch.finish_timestamp {
|
||||
let validity_duration = if let Some(epoch_finish) = epoch.deadline {
|
||||
let state_end =
|
||||
OffsetDateTime::from_unix_timestamp(epoch_finish.seconds() as i64).unwrap();
|
||||
let until_epoch_state_end = state_end - now;
|
||||
|
||||
@@ -10,8 +10,8 @@ use nym_coconut_dkg_common::dealing::{
|
||||
DealerDealingsStatusResponse, DealingChunkInfo, PartialContractDealing,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::{
|
||||
ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData,
|
||||
NodeIndex, PartialContractDealingData, State as ContractState,
|
||||
ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, NodeIndex,
|
||||
PartialContractDealingData, State as ContractState,
|
||||
};
|
||||
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
|
||||
use nym_contracts_common::IdentityKey;
|
||||
@@ -62,18 +62,23 @@ impl DkgClient {
|
||||
self.inner.get_current_epoch_threshold().await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_initial_dealers(
|
||||
&self,
|
||||
) -> Result<Option<InitialReplacementData>, CoconutError> {
|
||||
self.inner.get_initial_dealers().await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_self_registered_dealer_details(
|
||||
&self,
|
||||
) -> Result<DealerDetailsResponse, CoconutError> {
|
||||
self.inner.get_self_registered_dealer_details().await
|
||||
}
|
||||
|
||||
pub(crate) async fn dealer_in_epoch(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
) -> Result<bool, CoconutError> {
|
||||
self.inner
|
||||
.get_registered_dealer_details(epoch_id, dealer)
|
||||
.await
|
||||
.map(|d| d.details.is_some())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>, CoconutError> {
|
||||
self.inner.get_current_dealers().await
|
||||
}
|
||||
@@ -151,6 +156,10 @@ impl DkgClient {
|
||||
self.inner.advance_epoch_state().await
|
||||
}
|
||||
|
||||
pub(crate) async fn can_advance_epoch_state(&self) -> Result<bool, CoconutError> {
|
||||
self.inner.can_advance_epoch_state().await
|
||||
}
|
||||
|
||||
pub(crate) async fn register_dealer(
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
@@ -190,9 +199,8 @@ impl DkgClient {
|
||||
pub(crate) async fn submit_dealing_chunk(
|
||||
&self,
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
self.inner.submit_dealing_chunk(chunk, resharing).await?;
|
||||
self.inner.submit_dealing_chunk(chunk).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,12 @@ pub enum DkgError {
|
||||
source: CoconutError,
|
||||
},
|
||||
|
||||
#[error("failed to query for the epoch state status: {source}")]
|
||||
StateStatusQueryFailure {
|
||||
#[source]
|
||||
source: CoconutError,
|
||||
},
|
||||
|
||||
#[error("failed to query the CW4 group contract for the membership status: {source}")]
|
||||
GroupQueryFailure {
|
||||
#[source]
|
||||
|
||||
@@ -17,7 +17,7 @@ use rand::{CryptoRng, Rng, RngCore};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::time::interval;
|
||||
use tokio::time::{interval, MissedTickBehavior};
|
||||
|
||||
mod error;
|
||||
pub(crate) mod keys;
|
||||
@@ -191,9 +191,22 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
self.state.in_progress_state_mut(epoch_id).unwrap().entered = true;
|
||||
}
|
||||
|
||||
// so at this point we don't need to be polling the contract so often anymore, but we can't easily
|
||||
// adjust the existing interval.
|
||||
// however, what we can do is just wait here for a bit each iteration
|
||||
tokio::time::sleep(Duration::from_secs(120)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_if_can_advance_epoch_state(&self) -> Result<bool, DkgError> {
|
||||
debug!("checking if we can advance the epoch state");
|
||||
self.dkg_client
|
||||
.can_advance_epoch_state()
|
||||
.await
|
||||
.map_err(|source| DkgError::StateStatusQueryFailure { source })
|
||||
}
|
||||
|
||||
async fn try_advance_dkg_state(&mut self) -> Result<(), DkgError> {
|
||||
// We try advancing the epoch state, on a best-effort basis
|
||||
info!("DKG: Trying to advance the epoch");
|
||||
@@ -234,13 +247,13 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
EpochState::InProgress => self.handle_in_progress(epoch.epoch_id).await?,
|
||||
};
|
||||
|
||||
// add a bit of variance so that all apis wouldn't attempt to trigger it at the same time
|
||||
let variance = self.rng.gen_range(0..=60);
|
||||
if let Some(epoch_finish) = epoch.finish_timestamp {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
if now.unix_timestamp() > epoch_finish.seconds() as i64 + variance {
|
||||
// TODO: make sure to not overload validator in case its running slow
|
||||
// i.e. send it once at most every X seconds
|
||||
if self.check_if_can_advance_epoch_state().await? {
|
||||
// add a bit of variance so that all apis wouldn't attempt to trigger it at the same time
|
||||
let variance = self.rng.gen_range(0..=60);
|
||||
tokio::time::sleep(Duration::from_secs(variance)).await;
|
||||
|
||||
// check if whether during our waiting somebody has already advanced the epoch
|
||||
if self.check_if_can_advance_epoch_state().await? {
|
||||
self.try_advance_dkg_state().await?
|
||||
}
|
||||
}
|
||||
@@ -248,8 +261,17 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reduced_tick_rate(&self, tick_duration: time::Duration) -> bool {
|
||||
// make sure to not trigger warnings if say the target rate is 10s, but our last tick took `9s999ms785µs321ns`
|
||||
// check for 95% of polling rate, so in that case if its below 9s500ms
|
||||
let target_nanos = self.polling_rate.as_nanos();
|
||||
let min = time::Duration::nanoseconds(((target_nanos * 95) / 100) as i64);
|
||||
tick_duration < min
|
||||
}
|
||||
|
||||
pub(crate) async fn run(mut self, mut shutdown: TaskClient) {
|
||||
let mut interval = interval(self.polling_rate);
|
||||
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||
|
||||
// sometimes when the process is running behind, the ticker resolves multiple times in quick succession
|
||||
// so explicitly track those instances and make sure we don't overload the validator with contract calls
|
||||
@@ -263,7 +285,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
let tick_duration = now - last_polled;
|
||||
last_polled = now;
|
||||
|
||||
if tick_duration < self.polling_rate {
|
||||
if self.reduced_tick_rate(tick_duration) {
|
||||
warn!("it seems the process is running behind. The current tick rate is lower than the polling rate. rate: {:?}, current tick: {}, previous tick: {}", self.polling_rate, tick_duration, last_tick_duration);
|
||||
last_tick_duration = tick_duration;
|
||||
continue
|
||||
|
||||
@@ -209,9 +209,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
.remove(chunk_index)
|
||||
.expect("chunking specification has changed mid-exchange!");
|
||||
debug!("[dealing {dealing_index}]: resubmitting chunk index {chunk_index}");
|
||||
self.dkg_client
|
||||
.submit_dealing_chunk(chunk, resharing)
|
||||
.await?;
|
||||
self.dkg_client.submit_dealing_chunk(chunk).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -243,26 +241,28 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
let human_index = chunk_index + 1;
|
||||
debug!("[dealing {dealing_index}]: submitting chunk index {chunk_index} ({human_index}/{total_chunks})");
|
||||
|
||||
self.dkg_client
|
||||
.submit_dealing_chunk(chunk, resharing)
|
||||
.await?;
|
||||
self.dkg_client.submit_dealing_chunk(chunk).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check whether this dealer can participate in the resharing
|
||||
/// by looking into the contract and ensuring it's in the list of initial dealers for this epoch
|
||||
async fn can_reshare(&self) -> Result<bool, DealingGenerationError> {
|
||||
let Some(initial_data) = self.dkg_client.get_initial_dealers().await? else {
|
||||
return Ok(false);
|
||||
};
|
||||
/// by looking into the contract and ensuring it's been a dealer in the previous epoch
|
||||
async fn can_reshare(&self, epoch_id: EpochId) -> Result<bool, DealingGenerationError> {
|
||||
// SAFETY:
|
||||
// it's impossible for the contract to trigger resharing for the 0th epoch
|
||||
// otherwise some serious invariants have been broken
|
||||
#[allow(clippy::expect_used)]
|
||||
let previous_epoch_id = epoch_id
|
||||
.checked_sub(1)
|
||||
.expect("resharing epoch invariant has been broken");
|
||||
|
||||
let address = self.dkg_client.get_address().await;
|
||||
Ok(initial_data
|
||||
.initial_dealers
|
||||
.iter()
|
||||
.any(|d| d.as_str() == address.as_ref()))
|
||||
Ok(self
|
||||
.dkg_client
|
||||
.dealer_in_epoch(previous_epoch_id, address.to_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Deal with the dealing generation case where the system requests resharing
|
||||
@@ -274,7 +274,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
old_keypair: KeyPairWithEpoch,
|
||||
) -> Result<(), DealingGenerationError> {
|
||||
// make sure we're allowed to participate in resharing
|
||||
if !self.can_reshare().await? {
|
||||
if !self.can_reshare(epoch_id).await? {
|
||||
// we have to wait for other dealers to give us the dealings (hopefully)
|
||||
warn!("we we have an existing coconut keypair, but we're not allowed to participate in resharing");
|
||||
return Ok(());
|
||||
@@ -286,7 +286,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
// it could be outdated and we can't use it for resharing
|
||||
let previous = epoch_id.saturating_sub(1);
|
||||
if old_keypair.issued_for_epoch != previous {
|
||||
warn!("our existing coconut keypair has been generated for an distant epoch ({} vs expected {previous} for resharing)", old_keypair.issued_for_epoch);
|
||||
warn!("our existing coconut keypair has been generated for a distant epoch ({} vs expected {previous} for resharing)", old_keypair.issued_for_epoch);
|
||||
// don't participate in resharing
|
||||
return Ok(());
|
||||
}
|
||||
@@ -427,7 +427,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
// sure, the if statements could be collapsed, but i prefer to explicitly repeat the block for readability
|
||||
if resharing {
|
||||
debug!("resharing + no prior key -> nothing to do");
|
||||
if self.can_reshare().await? {
|
||||
if self.can_reshare(epoch_id).await? {
|
||||
warn!("this dealer was expected to participate in resharing but it doesn't have any prior keys to use");
|
||||
}
|
||||
} else {
|
||||
@@ -475,7 +475,7 @@ pub(crate) mod tests {
|
||||
};
|
||||
use crate::coconut::tests::helpers::unchecked_decode_bte_key;
|
||||
use nym_coconut::{ttp_keygen, Parameters};
|
||||
use nym_coconut_dkg_common::types::InitialReplacementData;
|
||||
use nym_coconut_dkg_common::types::DealerRegistrationDetails;
|
||||
use nym_dkg::bte::PublicKeyWithProof;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -616,7 +616,7 @@ pub(crate) mod tests {
|
||||
let dealers = dealers_fixtures(&mut rng, 4);
|
||||
let self_dealer = dealers[0].clone();
|
||||
|
||||
let epoch = 0;
|
||||
let epoch = 1;
|
||||
|
||||
let mut keys = ttp_keygen(&Parameters::new(4).unwrap(), 3, 4).unwrap();
|
||||
let coconut_keypair = KeyPair::new();
|
||||
@@ -673,29 +673,42 @@ pub(crate) mod tests {
|
||||
let dealers = dealers_fixtures(&mut rng, 4);
|
||||
let self_dealer = dealers[0].clone();
|
||||
|
||||
let epoch = 0;
|
||||
let epoch = 1;
|
||||
|
||||
let mut keys = ttp_keygen(&Parameters::new(4).unwrap(), 3, 4).unwrap();
|
||||
let coconut_keypair = KeyPair::new();
|
||||
coconut_keypair
|
||||
.set(KeyPairWithEpoch::new(keys.pop().unwrap(), epoch))
|
||||
.set(KeyPairWithEpoch::new(keys.pop().unwrap(), epoch - 1))
|
||||
.await;
|
||||
|
||||
let initial_dealers = InitialReplacementData {
|
||||
initial_dealers: vec![self_dealer.address.clone()],
|
||||
initial_height: 100,
|
||||
};
|
||||
|
||||
let mut controller = TestingDkgControllerBuilder::default()
|
||||
.with_threshold(3)
|
||||
.with_dealers(dealers.clone())
|
||||
.with_as_dealer(self_dealer.clone())
|
||||
.with_keypair(coconut_keypair)
|
||||
.with_initial_epoch_id(epoch)
|
||||
.with_initial_dealers(initial_dealers)
|
||||
.build()
|
||||
.await;
|
||||
|
||||
let chain = controller.chain_state.clone();
|
||||
|
||||
// TODO: put that functionality in the builder
|
||||
chain
|
||||
.lock()
|
||||
.unwrap()
|
||||
.dkg_contract
|
||||
.dealers
|
||||
.entry(epoch - 1)
|
||||
.or_default()
|
||||
.insert(
|
||||
self_dealer.address.to_string(),
|
||||
DealerRegistrationDetails {
|
||||
bte_public_key_with_proof: self_dealer.bte_public_key_with_proof.clone(),
|
||||
ed25519_identity: self_dealer.ed25519_identity.clone(),
|
||||
announce_address: self_dealer.announce_address.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
let key_size = controller.dkg_client.get_contract_state().await?.key_size;
|
||||
|
||||
let res = controller.dealing_exchange(epoch, true).await;
|
||||
|
||||
@@ -186,7 +186,6 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
epoch_id: EpochId,
|
||||
dealer: &Addr,
|
||||
resharing: bool,
|
||||
initial_dealers: &[Addr],
|
||||
) -> Result<Result<HashMap<DealingIndex, Vec<u8>>, DealerRejectionReason>, KeyDerivationError>
|
||||
{
|
||||
let dealing_statuses = self
|
||||
@@ -208,11 +207,25 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
));
|
||||
}
|
||||
|
||||
// we might be in resharing mode and this dealer was not in "initial" set.
|
||||
// in that case we don't expect any dealings
|
||||
if resharing && !initial_dealers.contains(dealer) {
|
||||
return Ok(Ok(HashMap::new()));
|
||||
// if we're in the resharing mode and this dealer has not been a dealer in the previous epoch,
|
||||
// we don't expect to have received anything from them
|
||||
if resharing {
|
||||
// SAFETY:
|
||||
// it's impossible for the contract to trigger resharing for the 0th epoch
|
||||
// otherwise some serious invariants have been broken
|
||||
#[allow(clippy::expect_used)]
|
||||
let previous_epoch_id = epoch_id
|
||||
.checked_sub(1)
|
||||
.expect("resharing epoch invariant has been broken");
|
||||
if !self
|
||||
.dkg_client
|
||||
.dealer_in_epoch(previous_epoch_id, dealer.to_string())
|
||||
.await?
|
||||
{
|
||||
return Ok(Ok(HashMap::new()));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Err(DealerRejectionReason::NoDealingsProvided));
|
||||
}
|
||||
|
||||
@@ -251,22 +264,13 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
let mut valid_dealings: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new();
|
||||
|
||||
// given at MOST we'll have like 50 entries here, iterating over entire vector for lookup is fine
|
||||
let initial_dealers = self
|
||||
.dkg_client
|
||||
.get_initial_dealers()
|
||||
.await?
|
||||
.map(|i| i.initial_dealers)
|
||||
.unwrap_or_default();
|
||||
|
||||
// for every valid dealer in this epoch, obtain its dealings
|
||||
for (dealer, dealer_index) in self.state.valid_epoch_receivers(epoch_id)? {
|
||||
// note: if we're in resharing mode, the contract itself will forbid submission of dealings from
|
||||
// parties that were not "initial" dealers, so we don't have to worry about it
|
||||
// parties that were dealers in the previous epoch, so we don't have to worry about it
|
||||
|
||||
let raw_dealings = match self
|
||||
.get_raw_dealings(epoch_id, &dealer, resharing, &initial_dealers)
|
||||
.await?
|
||||
{
|
||||
let raw_dealings = match self.get_raw_dealings(epoch_id, &dealer, resharing).await? {
|
||||
Ok(dealings) => dealings,
|
||||
Err(rejection) => {
|
||||
self.blacklist_dealer(epoch_id, dealer, rejection)?;
|
||||
|
||||
@@ -10,7 +10,8 @@ use crate::coconut::keys::KeyPair;
|
||||
use crate::coconut::tests::{DummyClient, SharedFakeChain};
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_coconut::VerificationKey;
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, EpochId, InitialReplacementData};
|
||||
use nym_coconut_dkg_common::dealer::DealerRegistrationDetails;
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, EpochId};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
|
||||
use nym_dkg::{NodeIndex, Threshold};
|
||||
@@ -81,7 +82,6 @@ pub struct TestingDkgControllerBuilder {
|
||||
threshold: Option<Threshold>,
|
||||
self_dealer: Option<DealerDetails>,
|
||||
dealers: Vec<DealerDetails>,
|
||||
initial_dealers: Option<InitialReplacementData>,
|
||||
}
|
||||
|
||||
impl TestingDkgControllerBuilder {
|
||||
@@ -127,11 +127,6 @@ impl TestingDkgControllerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_initial_dealers(mut self, initial_dealers: InitialReplacementData) -> Self {
|
||||
self.initial_dealers = Some(initial_dealers);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn with_address(mut self, address: impl Into<String>) -> Self {
|
||||
let addr = address.into();
|
||||
@@ -181,20 +176,34 @@ impl TestingDkgControllerBuilder {
|
||||
// insert initial data into the chain state
|
||||
{
|
||||
let mut state_guard = chain_state.lock().unwrap();
|
||||
if let Some(threshold) = self.threshold {
|
||||
state_guard.dkg_contract.threshold = Some(threshold)
|
||||
}
|
||||
for dealer in self.dealers {
|
||||
state_guard
|
||||
.dkg_contract
|
||||
.dealers
|
||||
.insert(dealer.assigned_index, dealer);
|
||||
}
|
||||
if let Some(epoch_id) = self.epoch_id {
|
||||
state_guard.dkg_contract.epoch.epoch_id = epoch_id;
|
||||
}
|
||||
if let Some(initial_dealers) = self.initial_dealers {
|
||||
state_guard.dkg_contract.initial_dealers = Some(initial_dealers)
|
||||
if let Some(threshold) = self.threshold {
|
||||
state_guard.dkg_contract.threshold = Some(threshold)
|
||||
}
|
||||
let epoch_id = state_guard.dkg_contract.epoch.epoch_id;
|
||||
|
||||
for dealer in self.dealers {
|
||||
let epoch_dealers = state_guard
|
||||
.dkg_contract
|
||||
.dealers
|
||||
.entry(epoch_id)
|
||||
.or_default();
|
||||
|
||||
epoch_dealers.insert(
|
||||
dealer.address.to_string(),
|
||||
DealerRegistrationDetails {
|
||||
bte_public_key_with_proof: dealer.bte_public_key_with_proof,
|
||||
ed25519_identity: dealer.ed25519_identity,
|
||||
announce_address: dealer.announce_address,
|
||||
},
|
||||
);
|
||||
|
||||
state_guard
|
||||
.dkg_contract
|
||||
.dealer_indices
|
||||
.insert(dealer.address.to_string(), dealer.assigned_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+343
-289
@@ -21,15 +21,17 @@ use nym_coconut_bandwidth_contract_common::events::{
|
||||
DEPOSIT_VALUE,
|
||||
};
|
||||
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
|
||||
use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse, DealerType};
|
||||
use nym_coconut_dkg_common::dealer::{
|
||||
DealerDetails, DealerDetailsResponse, DealerType, RegisteredDealerDetails,
|
||||
};
|
||||
use nym_coconut_dkg_common::dealing::{
|
||||
DealerDealingsStatusResponse, DealingChunkInfo, DealingMetadata, DealingStatus,
|
||||
DealingStatusResponse, PartialContractDealing,
|
||||
};
|
||||
use nym_coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX};
|
||||
use nym_coconut_dkg_common::types::{
|
||||
ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch, EpochId, EpochState,
|
||||
InitialReplacementData, PartialContractDealingData, State as ContractState,
|
||||
ChunkIndex, DealerRegistrationDetails, DealingIndex, EncodedBTEPublicKeyWithProof, Epoch,
|
||||
EpochId, EpochState, PartialContractDealingData, State as ContractState,
|
||||
};
|
||||
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
|
||||
use nym_coconut_interface::VerificationKey;
|
||||
@@ -38,7 +40,6 @@ use nym_contracts_common::IdentityKey;
|
||||
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_dkg::{NodeIndex, Threshold};
|
||||
use nym_mixnet_contract_common::BlockHeight;
|
||||
use nym_validator_client::nym_api::routes::{
|
||||
API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_ROUTES,
|
||||
};
|
||||
@@ -53,7 +54,6 @@ use rand_07::RngCore;
|
||||
use rocket::http::Status;
|
||||
use rocket::local::asynchronous::Client;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
@@ -135,9 +135,13 @@ impl Dealing {
|
||||
pub(crate) struct FakeDkgContractState {
|
||||
pub(crate) address: AccountId,
|
||||
|
||||
pub(crate) dealers: HashMap<NodeIndex, DealerDetails>,
|
||||
pub(crate) past_dealers: HashMap<NodeIndex, DealerDetails>,
|
||||
pub(crate) initial_dealers: Option<InitialReplacementData>,
|
||||
// pub(crate) dealers: HashMap<NodeIndex, DealerDetails>,
|
||||
// pub(crate) past_dealers: HashMap<NodeIndex, DealerDetails>,
|
||||
// pub(crate) initial_dealers: Option<InitialReplacementData>,
|
||||
pub(crate) dealer_indices: HashMap<String, NodeIndex>,
|
||||
|
||||
// map of epoch id -> dealer -> info
|
||||
pub(crate) dealers: HashMap<EpochId, HashMap<String, DealerRegistrationDetails>>,
|
||||
|
||||
// map of epoch id -> dealer -> dealings
|
||||
pub(crate) dealings: HashMap<EpochId, HashMap<String, HashMap<DealingIndex, Dealing>>>,
|
||||
@@ -151,41 +155,84 @@ pub(crate) struct FakeDkgContractState {
|
||||
}
|
||||
|
||||
impl FakeDkgContractState {
|
||||
pub(crate) fn verified_dealers(&self) -> Vec<Addr> {
|
||||
let epoch_id = self.epoch.epoch_id;
|
||||
let Some(shares) = self.verification_shares.get(&epoch_id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
shares
|
||||
.values()
|
||||
.filter(|s| s.verified)
|
||||
.map(|s| s.owner.clone())
|
||||
.collect()
|
||||
}
|
||||
// pub(crate) fn verified_dealers(&self) -> Vec<Addr> {
|
||||
// let epoch_id = self.epoch.epoch_id;
|
||||
// let Some(shares) = self.verification_shares.get(&epoch_id) else {
|
||||
// return Vec::new();
|
||||
// };
|
||||
//
|
||||
// shares
|
||||
// .values()
|
||||
// .filter(|s| s.verified)
|
||||
// .map(|s| s.owner.clone())
|
||||
// .collect()
|
||||
// }
|
||||
|
||||
fn reset_dkg_state(&mut self) {
|
||||
self.threshold = None;
|
||||
let dealers = mem::take(&mut self.dealers);
|
||||
for (index, details) in dealers {
|
||||
self.past_dealers.insert(index, details);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn reset_epoch_in_reshare_mode(&mut self, block_height: BlockHeight) {
|
||||
if let Some(initial_dealers) = self.initial_dealers.as_mut() {
|
||||
initial_dealers.initial_height = block_height;
|
||||
} else {
|
||||
self.initial_dealers = Some(InitialReplacementData {
|
||||
initial_dealers: self.verified_dealers(),
|
||||
initial_height: block_height,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn reset_epoch_in_reshare_mode(&mut self) {
|
||||
self.reset_dkg_state();
|
||||
self.epoch.state = EpochState::PublicKeySubmission { resharing: true };
|
||||
self.epoch.epoch_id += 1;
|
||||
}
|
||||
|
||||
pub(crate) fn reset_dkg(&mut self) {
|
||||
self.reset_dkg_state();
|
||||
self.epoch.state = EpochState::PublicKeySubmission { resharing: false };
|
||||
self.epoch.epoch_id += 1;
|
||||
}
|
||||
|
||||
pub(crate) fn get_registration_details(
|
||||
&self,
|
||||
addr: &str,
|
||||
epoch_id: EpochId,
|
||||
) -> Option<DealerRegistrationDetails> {
|
||||
self.dealers.get(&epoch_id)?.get(addr).cloned()
|
||||
}
|
||||
|
||||
pub(crate) fn get_dealer_details(
|
||||
&self,
|
||||
addr: &str,
|
||||
epoch_id: EpochId,
|
||||
) -> Option<DealerDetails> {
|
||||
let registration_details = self.get_registration_details(addr, epoch_id)?;
|
||||
let assigned_index = self.get_dealer_index(addr)?;
|
||||
|
||||
Some(DealerDetails {
|
||||
address: Addr::unchecked(addr),
|
||||
bte_public_key_with_proof: registration_details.bte_public_key_with_proof,
|
||||
ed25519_identity: registration_details.ed25519_identity,
|
||||
announce_address: registration_details.announce_address,
|
||||
assigned_index,
|
||||
})
|
||||
}
|
||||
|
||||
// implementation copied from our contract
|
||||
pub(crate) fn query_dealer_details(&self, addr: &str) -> DealerDetailsResponse {
|
||||
let current_epoch_id = self.epoch.epoch_id;
|
||||
|
||||
// if the address has registration data for the current epoch, it means it's an active dealer
|
||||
if let Some(dealer_details) = self.get_dealer_details(addr, current_epoch_id) {
|
||||
let assigned_index = dealer_details.assigned_index;
|
||||
return DealerDetailsResponse::new(
|
||||
Some(dealer_details),
|
||||
DealerType::Current { assigned_index },
|
||||
);
|
||||
}
|
||||
|
||||
// and if has had an assigned index it must have been a dealer at some point in the past
|
||||
if let Some(assigned_index) = self.get_dealer_index(addr) {
|
||||
return DealerDetailsResponse::new(None, DealerType::Past { assigned_index });
|
||||
}
|
||||
|
||||
DealerDetailsResponse::new(None, DealerType::Unknown)
|
||||
}
|
||||
|
||||
pub(crate) fn get_dealer_index(&self, addr: &str) -> Option<NodeIndex> {
|
||||
self.dealer_indices.get(addr).copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -274,8 +321,8 @@ impl Default for FakeChainState {
|
||||
|
||||
dkg_contract: FakeDkgContractState {
|
||||
address: dkg_contract.as_ref().parse().unwrap(),
|
||||
dealer_indices: Default::default(),
|
||||
dealers: HashMap::new(),
|
||||
past_dealers: Default::default(),
|
||||
|
||||
epoch: Epoch::default(),
|
||||
contract_state: ContractState {
|
||||
@@ -287,7 +334,6 @@ impl Default for FakeChainState {
|
||||
dealings: HashMap::new(),
|
||||
verification_shares: HashMap::new(),
|
||||
threshold: None,
|
||||
initial_dealers: None,
|
||||
},
|
||||
group_contract: FakeGroupContractState {
|
||||
address: group_contract,
|
||||
@@ -307,6 +353,18 @@ impl Default for FakeChainState {
|
||||
}
|
||||
|
||||
impl FakeChainState {
|
||||
pub(crate) fn get_or_assign_dealer(&mut self, addr: &str) -> NodeIndex {
|
||||
if let Some(index) = self.dkg_contract.dealer_indices.get(addr) {
|
||||
*index
|
||||
} else {
|
||||
let new = self._counters.next_node_index();
|
||||
self.dkg_contract
|
||||
.dealer_indices
|
||||
.insert(addr.to_string(), new);
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn total_group_weight(&self) -> u64 {
|
||||
self.group_contract.total_weight()
|
||||
}
|
||||
@@ -320,8 +378,12 @@ impl FakeChainState {
|
||||
}
|
||||
|
||||
pub(crate) fn advance_epoch_in_reshare_mode(&mut self) {
|
||||
self.dkg_contract
|
||||
.reset_epoch_in_reshare_mode(self.block_info.height)
|
||||
self.dkg_contract.reset_epoch_in_reshare_mode()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn advance_epoch_in_reset_mode(&mut self) {
|
||||
self.dkg_contract.reset_dkg()
|
||||
}
|
||||
|
||||
// TODO: make it return a result
|
||||
@@ -519,25 +581,25 @@ impl DummyClient {
|
||||
// // self
|
||||
// }
|
||||
|
||||
async fn get_dealer_by_address(&self, address: &str) -> Option<DealerDetails> {
|
||||
let guard = self.state.lock().unwrap();
|
||||
for dealer in guard.dkg_contract.dealers.values() {
|
||||
if dealer.address.as_str() == address {
|
||||
return Some(dealer.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
async fn get_past_dealer_by_address(&self, address: &str) -> Option<DealerDetails> {
|
||||
let guard = self.state.lock().unwrap();
|
||||
for dealer in guard.dkg_contract.past_dealers.values() {
|
||||
if dealer.address.as_str() == address {
|
||||
return Some(dealer.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
// async fn get_dealer_by_address(&self, address: &str) -> Option<DealerDetails> {
|
||||
// let guard = self.state.lock().unwrap();
|
||||
// for dealer in guard.dkg_contract.dealers.values() {
|
||||
// if dealer.address.as_str() == address {
|
||||
// return Some(dealer.clone());
|
||||
// }
|
||||
// }
|
||||
// None
|
||||
// }
|
||||
//
|
||||
// async fn get_past_dealer_by_address(&self, address: &str) -> Option<DealerDetails> {
|
||||
// let guard = self.state.lock().unwrap();
|
||||
// for dealer in guard.dkg_contract.past_dealers.values() {
|
||||
// if dealer.address.as_str() == address {
|
||||
// return Some(dealer.clone());
|
||||
// }
|
||||
// }
|
||||
// None
|
||||
// }
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -651,36 +713,76 @@ impl super::client::Client for DummyClient {
|
||||
Ok(self.state.lock().unwrap().dkg_contract.threshold)
|
||||
}
|
||||
|
||||
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>> {
|
||||
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
|
||||
let address = self.validator_address.as_ref();
|
||||
Ok(self
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.dkg_contract
|
||||
.initial_dealers
|
||||
.clone())
|
||||
.query_dealer_details(address))
|
||||
}
|
||||
|
||||
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
|
||||
let address = self.validator_address.as_ref();
|
||||
async fn get_registered_dealer_details(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
) -> Result<RegisteredDealerDetails> {
|
||||
let details = self
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.dkg_contract
|
||||
.dealers
|
||||
.get(&epoch_id)
|
||||
.and_then(|dealers| dealers.get(&dealer))
|
||||
.cloned();
|
||||
Ok(RegisteredDealerDetails { details })
|
||||
}
|
||||
|
||||
if let Some(details) = self.get_dealer_by_address(address).await {
|
||||
return Ok(DealerDetailsResponse {
|
||||
details: Some(details),
|
||||
dealer_type: DealerType::Current,
|
||||
async fn get_dealer_dealings_status(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
) -> Result<DealerDealingsStatusResponse> {
|
||||
let guard = self.state.lock().unwrap();
|
||||
let key_size = guard.dkg_contract.contract_state.key_size;
|
||||
|
||||
let dealer_addr = Addr::unchecked(&dealer);
|
||||
|
||||
let Some(epoch_dealings) = guard.dkg_contract.dealings.get(&epoch_id) else {
|
||||
return Ok(DealerDealingsStatusResponse {
|
||||
epoch_id,
|
||||
dealer: dealer_addr,
|
||||
all_dealings_fully_submitted: false,
|
||||
dealing_submission_status: Default::default(),
|
||||
});
|
||||
};
|
||||
|
||||
let Some(dealer_dealings) = epoch_dealings.get(&dealer) else {
|
||||
return Ok(DealerDealingsStatusResponse {
|
||||
epoch_id,
|
||||
dealer: dealer_addr,
|
||||
all_dealings_fully_submitted: false,
|
||||
dealing_submission_status: Default::default(),
|
||||
});
|
||||
};
|
||||
|
||||
let mut dealing_submission_status: BTreeMap<DealingIndex, DealingStatus> = BTreeMap::new();
|
||||
for dealing_index in 0..key_size {
|
||||
let metadata = dealer_dealings
|
||||
.get(&dealing_index)
|
||||
.map(|d| d.metadata.clone());
|
||||
dealing_submission_status.insert(dealing_index, metadata.into());
|
||||
}
|
||||
|
||||
if let Some(details) = self.get_past_dealer_by_address(address).await {
|
||||
return Ok(DealerDetailsResponse {
|
||||
details: Some(details),
|
||||
dealer_type: DealerType::Past,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(DealerDetailsResponse {
|
||||
details: None,
|
||||
dealer_type: DealerType::Unknown,
|
||||
Ok(DealerDealingsStatusResponse {
|
||||
epoch_id,
|
||||
dealer: Addr::unchecked(&dealer),
|
||||
all_dealings_fully_submitted: dealing_submission_status
|
||||
.values()
|
||||
.all(|d| d.fully_submitted),
|
||||
dealing_submission_status,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -709,17 +811,75 @@ impl super::client::Client for DummyClient {
|
||||
}
|
||||
|
||||
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>> {
|
||||
Ok(self
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.dkg_contract
|
||||
.dealers
|
||||
.values()
|
||||
.cloned()
|
||||
let chain = self.state.lock().unwrap();
|
||||
let current_epoch_id = chain.dkg_contract.epoch.epoch_id;
|
||||
|
||||
let Some(epoch_dealers) = chain.dkg_contract.dealers.get(¤t_epoch_id) else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
Ok(epoch_dealers
|
||||
.iter()
|
||||
.map(|(address, details)| {
|
||||
let assigned_index = chain.dkg_contract.get_dealer_index(address).unwrap();
|
||||
DealerDetails {
|
||||
address: Addr::unchecked(address),
|
||||
bte_public_key_with_proof: details.bte_public_key_with_proof.clone(),
|
||||
ed25519_identity: details.ed25519_identity.clone(),
|
||||
announce_address: details.announce_address.clone(),
|
||||
assigned_index,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn get_dealing_metadata(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
) -> Result<Option<DealingMetadata>> {
|
||||
let guard = self.state.lock().unwrap();
|
||||
|
||||
let Some(epoch_dealings) = guard.dkg_contract.dealings.get(&epoch_id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(dealer_dealings) = epoch_dealings.get(&dealer) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(dealing) = dealer_dealings.get(&dealing_index) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(dealing.metadata.clone()))
|
||||
}
|
||||
|
||||
async fn get_dealing_chunk(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: &str,
|
||||
dealing_index: DealingIndex,
|
||||
chunk_index: ChunkIndex,
|
||||
) -> Result<Option<PartialContractDealingData>> {
|
||||
let guard = self.state.lock().unwrap();
|
||||
|
||||
let Some(epoch_dealings) = guard.dkg_contract.dealings.get(&epoch_id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(dealer_dealings) = epoch_dealings.get(dealer) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(dealing) = dealer_dealings.get(&dealing_index) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(dealing.chunks.get(&chunk_index).cloned())
|
||||
}
|
||||
|
||||
async fn get_verification_key_share(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
@@ -795,7 +955,6 @@ impl super::client::Client for DummyClient {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_proposal(&self, proposal_id: u64) -> Result<()> {
|
||||
let mut chain = self.state.lock().unwrap();
|
||||
let multisig_address: AccountId = chain.multisig_contract.address.as_str().parse().unwrap();
|
||||
@@ -818,6 +977,17 @@ impl super::client::Client for DummyClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn can_advance_epoch_state(&self) -> Result<bool> {
|
||||
// TODO: incorporate the short-circuiting logic in here
|
||||
let chain = self.state.lock().unwrap();
|
||||
let epoch = chain.dkg_contract.epoch;
|
||||
Ok(if let Some(finish_timestamp) = epoch.deadline {
|
||||
finish_timestamp <= chain.block_info.time
|
||||
} else {
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
async fn advance_epoch_state(&self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
@@ -829,43 +999,23 @@ impl super::client::Client for DummyClient {
|
||||
announce_address: String,
|
||||
_resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
let assigned_index = if let Some(already_registered) = self
|
||||
.get_dealer_by_address(self.validator_address.as_ref())
|
||||
.await
|
||||
{
|
||||
// current dealer
|
||||
already_registered.assigned_index
|
||||
} else if let Some(registered_in_the_past) = self
|
||||
.get_past_dealer_by_address(self.validator_address.as_ref())
|
||||
.await
|
||||
{
|
||||
// past dealer
|
||||
let index = registered_in_the_past.assigned_index;
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
guard
|
||||
.dkg_contract
|
||||
.dealers
|
||||
.insert(index, registered_in_the_past);
|
||||
|
||||
index
|
||||
} else {
|
||||
// new dealer
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
let assigned_index = guard._counters.next_node_index();
|
||||
|
||||
guard.dkg_contract.dealers.insert(
|
||||
assigned_index,
|
||||
DealerDetails {
|
||||
address: Addr::unchecked(self.validator_address.to_string()),
|
||||
bte_public_key_with_proof,
|
||||
ed25519_identity: identity_key,
|
||||
announce_address,
|
||||
assigned_index,
|
||||
},
|
||||
);
|
||||
assigned_index
|
||||
};
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
let assigned_index = guard.get_or_assign_dealer(self.validator_address.as_ref());
|
||||
let epoch = guard.dkg_contract.epoch.epoch_id;
|
||||
|
||||
let dealer_details = DealerRegistrationDetails {
|
||||
bte_public_key_with_proof,
|
||||
ed25519_identity: identity_key,
|
||||
announce_address,
|
||||
};
|
||||
|
||||
let epoch_dealers = guard.dkg_contract.dealers.entry(epoch).or_default();
|
||||
if !epoch_dealers.contains_key(self.validator_address.as_ref()) {
|
||||
epoch_dealers.insert(self.validator_address.to_string(), dealer_details);
|
||||
} else {
|
||||
unimplemented!("already registered")
|
||||
}
|
||||
|
||||
let transaction_hash = guard._counters.next_tx_hash();
|
||||
|
||||
Ok(ExecuteResult {
|
||||
@@ -879,21 +1029,89 @@ impl super::client::Client for DummyClient {
|
||||
gas_info: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn submit_dealing_metadata(
|
||||
&self,
|
||||
dealing_index: DealingIndex,
|
||||
chunks: Vec<DealingChunkInfo>,
|
||||
_resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
let current_epoch = guard.dkg_contract.epoch.epoch_id;
|
||||
|
||||
let epoch_dealings = guard
|
||||
.dkg_contract
|
||||
.dealings
|
||||
.entry(current_epoch)
|
||||
.or_default();
|
||||
|
||||
let dealer_dealings = epoch_dealings
|
||||
.entry(self.validator_address.to_string())
|
||||
.or_default();
|
||||
dealer_dealings.insert(
|
||||
dealing_index,
|
||||
Dealing::new_metadata_submission(dealing_index, chunks),
|
||||
);
|
||||
|
||||
let transaction_hash = guard._counters.next_tx_hash();
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: vec![],
|
||||
data: Default::default(),
|
||||
transaction_hash,
|
||||
gas_info: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn submit_dealing_chunk(&self, chunk: PartialContractDealing) -> Result<ExecuteResult> {
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
let current_epoch = guard.dkg_contract.epoch.epoch_id;
|
||||
let current_height = guard.block_info.height;
|
||||
|
||||
// normally we should do checks for existence, etc.
|
||||
// but since this is a testing code, we assume everything is sent in order and the appropriate entries exist
|
||||
let epoch_dealings = guard.dkg_contract.dealings.get_mut(¤t_epoch).unwrap();
|
||||
|
||||
let dealer_dealings = epoch_dealings
|
||||
.get_mut(self.validator_address.as_ref())
|
||||
.unwrap();
|
||||
|
||||
let dealing_chunks = dealer_dealings.get_mut(&chunk.dealing_index).unwrap();
|
||||
dealing_chunks.chunks.insert(chunk.chunk_index, chunk.data);
|
||||
|
||||
dealing_chunks
|
||||
.metadata
|
||||
.submitted_chunks
|
||||
.get_mut(&chunk.chunk_index)
|
||||
.unwrap()
|
||||
.status
|
||||
.submission_height = Some(current_height);
|
||||
|
||||
let transaction_hash = guard._counters.next_tx_hash();
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: vec![],
|
||||
data: Default::default(),
|
||||
transaction_hash,
|
||||
gas_info: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn submit_verification_key_share(
|
||||
&self,
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
let address = self.validator_address.to_string();
|
||||
let mut chain = self.state.lock().unwrap();
|
||||
|
||||
let Some(dealer_details) = self.get_dealer_by_address(&address).await else {
|
||||
let address = self.validator_address.to_string();
|
||||
let epoch_id = chain.dkg_contract.epoch.epoch_id;
|
||||
let Some(dealer_details) = chain.dkg_contract.get_dealer_details(&address, epoch_id) else {
|
||||
// Just throw some error, not really the correct one
|
||||
return Err(CoconutError::DepositEncrKeyNotFound);
|
||||
};
|
||||
|
||||
let mut chain = self.state.lock().unwrap();
|
||||
let dkg_contract = chain.dkg_contract.address.clone();
|
||||
let epoch_id = chain.dkg_contract.epoch.epoch_id;
|
||||
|
||||
chain
|
||||
.dkg_contract
|
||||
@@ -954,170 +1172,6 @@ impl super::client::Client for DummyClient {
|
||||
gas_info: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_dealer_dealings_status(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
) -> Result<DealerDealingsStatusResponse> {
|
||||
let guard = self.state.lock().unwrap();
|
||||
let key_size = guard.dkg_contract.contract_state.key_size;
|
||||
|
||||
let dealer_addr = Addr::unchecked(&dealer);
|
||||
|
||||
let Some(epoch_dealings) = guard.dkg_contract.dealings.get(&epoch_id) else {
|
||||
return Ok(DealerDealingsStatusResponse {
|
||||
epoch_id,
|
||||
dealer: dealer_addr,
|
||||
all_dealings_fully_submitted: false,
|
||||
dealing_submission_status: Default::default(),
|
||||
});
|
||||
};
|
||||
|
||||
let Some(dealer_dealings) = epoch_dealings.get(&dealer) else {
|
||||
return Ok(DealerDealingsStatusResponse {
|
||||
epoch_id,
|
||||
dealer: dealer_addr,
|
||||
all_dealings_fully_submitted: false,
|
||||
dealing_submission_status: Default::default(),
|
||||
});
|
||||
};
|
||||
|
||||
let mut dealing_submission_status: BTreeMap<DealingIndex, DealingStatus> = BTreeMap::new();
|
||||
for dealing_index in 0..key_size {
|
||||
let metadata = dealer_dealings
|
||||
.get(&dealing_index)
|
||||
.map(|d| d.metadata.clone());
|
||||
dealing_submission_status.insert(dealing_index, metadata.into());
|
||||
}
|
||||
|
||||
Ok(DealerDealingsStatusResponse {
|
||||
epoch_id,
|
||||
dealer: Addr::unchecked(&dealer),
|
||||
all_dealings_fully_submitted: dealing_submission_status
|
||||
.values()
|
||||
.all(|d| d.fully_submitted),
|
||||
dealing_submission_status,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_dealing_metadata(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
) -> Result<Option<DealingMetadata>> {
|
||||
let guard = self.state.lock().unwrap();
|
||||
|
||||
let Some(epoch_dealings) = guard.dkg_contract.dealings.get(&epoch_id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(dealer_dealings) = epoch_dealings.get(&dealer) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(dealing) = dealer_dealings.get(&dealing_index) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(dealing.metadata.clone()))
|
||||
}
|
||||
|
||||
async fn get_dealing_chunk(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: &str,
|
||||
dealing_index: DealingIndex,
|
||||
chunk_index: ChunkIndex,
|
||||
) -> Result<Option<PartialContractDealingData>> {
|
||||
let guard = self.state.lock().unwrap();
|
||||
|
||||
let Some(epoch_dealings) = guard.dkg_contract.dealings.get(&epoch_id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(dealer_dealings) = epoch_dealings.get(dealer) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(dealing) = dealer_dealings.get(&dealing_index) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(dealing.chunks.get(&chunk_index).cloned())
|
||||
}
|
||||
|
||||
async fn submit_dealing_metadata(
|
||||
&self,
|
||||
dealing_index: DealingIndex,
|
||||
chunks: Vec<DealingChunkInfo>,
|
||||
_resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
let current_epoch = guard.dkg_contract.epoch.epoch_id;
|
||||
|
||||
let epoch_dealings = guard
|
||||
.dkg_contract
|
||||
.dealings
|
||||
.entry(current_epoch)
|
||||
.or_default();
|
||||
|
||||
let dealer_dealings = epoch_dealings
|
||||
.entry(self.validator_address.to_string())
|
||||
.or_default();
|
||||
dealer_dealings.insert(
|
||||
dealing_index,
|
||||
Dealing::new_metadata_submission(dealing_index, chunks),
|
||||
);
|
||||
|
||||
let transaction_hash = guard._counters.next_tx_hash();
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: vec![],
|
||||
data: Default::default(),
|
||||
transaction_hash,
|
||||
gas_info: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn submit_dealing_chunk(
|
||||
&self,
|
||||
chunk: PartialContractDealing,
|
||||
_resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
let current_epoch = guard.dkg_contract.epoch.epoch_id;
|
||||
let current_height = guard.block_info.height;
|
||||
|
||||
// normally we should do checks for existence, etc.
|
||||
// but since this is a testing code, we assume everything is sent in order and the appropriate entries exist
|
||||
let epoch_dealings = guard.dkg_contract.dealings.get_mut(¤t_epoch).unwrap();
|
||||
|
||||
let dealer_dealings = epoch_dealings
|
||||
.get_mut(self.validator_address.as_ref())
|
||||
.unwrap();
|
||||
|
||||
let dealing_chunks = dealer_dealings.get_mut(&chunk.dealing_index).unwrap();
|
||||
dealing_chunks.chunks.insert(chunk.chunk_index, chunk.data);
|
||||
|
||||
dealing_chunks
|
||||
.metadata
|
||||
.submitted_chunks
|
||||
.get_mut(&chunk.chunk_index)
|
||||
.unwrap()
|
||||
.status
|
||||
.submission_height = Some(current_height);
|
||||
|
||||
let transaction_hash = guard._counters.next_tx_hash();
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: vec![],
|
||||
data: Default::default(),
|
||||
transaction_hash,
|
||||
gas_info: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -30,7 +30,7 @@ mod upgrade_helpers;
|
||||
|
||||
pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657";
|
||||
|
||||
pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10);
|
||||
pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(30);
|
||||
|
||||
const DEFAULT_GATEWAY_SENDING_RATE: usize = 200;
|
||||
const DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS: usize = 50;
|
||||
|
||||
@@ -14,9 +14,7 @@ use nym_coconut_dkg_common::dealing::{
|
||||
PartialContractDealing,
|
||||
};
|
||||
use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
|
||||
use nym_coconut_dkg_common::types::{
|
||||
ChunkIndex, DealingIndex, InitialReplacementData, PartialContractDealingData, State,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, PartialContractDealingData, State};
|
||||
use nym_coconut_dkg_common::{
|
||||
dealer::{DealerDetails, DealerDetailsResponse},
|
||||
types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId},
|
||||
@@ -24,6 +22,7 @@ use nym_coconut_dkg_common::{
|
||||
};
|
||||
use nym_config::defaults::{ChainDetails, NymNetworkDetails};
|
||||
|
||||
use nym_coconut_dkg_common::dealer::RegisteredDealerDetails;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::reward_params::RewardingParams;
|
||||
@@ -412,12 +411,6 @@ impl crate::coconut::client::Client for Client {
|
||||
Ok(nyxd_query!(self, get_current_epoch_threshold().await?))
|
||||
}
|
||||
|
||||
async fn get_initial_dealers(
|
||||
&self,
|
||||
) -> crate::coconut::error::Result<Option<InitialReplacementData>> {
|
||||
Ok(nyxd_query!(self, get_initial_dealers().await?))
|
||||
}
|
||||
|
||||
async fn get_self_registered_dealer_details(
|
||||
&self,
|
||||
) -> crate::coconut::error::Result<DealerDetailsResponse> {
|
||||
@@ -425,6 +418,21 @@ impl crate::coconut::client::Client for Client {
|
||||
Ok(nyxd_query!(self, get_dealer_details(self_address).await?))
|
||||
}
|
||||
|
||||
async fn get_registered_dealer_details(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
) -> crate::coconut::error::Result<RegisteredDealerDetails> {
|
||||
let dealer = dealer
|
||||
.as_str()
|
||||
.parse()
|
||||
.map_err(|_| NyxdError::MalformedAccountAddress(dealer))?;
|
||||
Ok(nyxd_query!(
|
||||
self,
|
||||
get_registered_dealer_details(&dealer, Some(epoch_id)).await?
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_dealer_dealings_status(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
@@ -514,6 +522,10 @@ impl crate::coconut::client::Client for Client {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn can_advance_epoch_state(&self) -> crate::coconut::error::Result<bool> {
|
||||
Ok(nyxd_query!(self, can_advance_state().await?.can_advance()))
|
||||
}
|
||||
|
||||
async fn advance_epoch_state(&self) -> crate::coconut::error::Result<()> {
|
||||
nyxd_signing!(self, advance_dkg_epoch_state(None).await?);
|
||||
Ok(())
|
||||
@@ -547,11 +559,10 @@ impl crate::coconut::client::Client for Client {
|
||||
async fn submit_dealing_chunk(
|
||||
&self,
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult, CoconutError> {
|
||||
Ok(nyxd_signing!(
|
||||
self,
|
||||
submit_dealing_chunk(chunk, resharing, None).await?
|
||||
submit_dealing_chunk(chunk, None).await?
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user