Feature/dkg reshare (#2936)
* Add resharing parameter * Fix equality of dealers and members * Contract resharing handling * Dealer verification unit test * Dealing commit unit test * Epoch state unit tests * Fix clippy * Fmt * Query initial dealer data * Resharing nym-api changes * Implement the mockups for nym-api dkg tests * Dealing test * Vk unit test * Fix skipping vk submission * Fix clippy * Missing dealing for noninitial resharing dealer * Check master vk holds after resharing on nym-apis * Update changelog * Fix clippy
This commit is contained in:
committed by
Jon Häggblad
parent
25762900fa
commit
d89081d8a1
@@ -8,9 +8,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
- remove coconut feature and unify builds ([#2890])
|
||||
- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2939]). This can be specified via `--host` flag during `init` or `run`. Alternatively a custom `host` can be set in `config.toml` file under `socket` section.
|
||||
- dkg resharing mode ([#2936])
|
||||
|
||||
[#2890]: https://github.com/nymtech/nym/pull/2890
|
||||
[#2939]: https://github.com/nymtech/nym/pull/2939
|
||||
[#2936]: https://github.com/nymtech/nym/pull/2936
|
||||
|
||||
# [v1.1.8] (2023-01-31)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use coconut_dkg_common::dealer::{
|
||||
DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse,
|
||||
};
|
||||
use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
|
||||
use coconut_dkg_common::types::{Epoch, EpochId};
|
||||
use coconut_dkg_common::types::{Epoch, EpochId, InitialReplacementData};
|
||||
use coconut_dkg_common::verification_key::PagedVKSharesResponse;
|
||||
use cosmrs::AccountId;
|
||||
|
||||
@@ -16,6 +16,7 @@ use cosmrs::AccountId;
|
||||
pub trait DkgQueryClient {
|
||||
async fn get_current_epoch(&self) -> Result<Epoch, NyxdError>;
|
||||
async fn get_current_epoch_threshold(&self) -> Result<Option<u64>, NyxdError>;
|
||||
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>, NyxdError>;
|
||||
async fn get_dealer_details(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
@@ -62,6 +63,14 @@ where
|
||||
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>, NyxdError> {
|
||||
let request = DkgQueryMsg::GetInitialDealers {};
|
||||
self.client
|
||||
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_dealer_details(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
|
||||
@@ -17,18 +17,21 @@ pub trait DkgSigningClient {
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
|
||||
async fn submit_dealing_bytes(
|
||||
&self,
|
||||
commitment: ContractSafeBytes,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
|
||||
async fn submit_verification_key_share(
|
||||
&self,
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
}
|
||||
@@ -57,11 +60,13 @@ where
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::RegisterDealer {
|
||||
bte_key_with_proof: bte_key,
|
||||
announce_address,
|
||||
resharing,
|
||||
};
|
||||
|
||||
self.client
|
||||
@@ -79,9 +84,13 @@ where
|
||||
async fn submit_dealing_bytes(
|
||||
&self,
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::CommitDealing { dealing_bytes };
|
||||
let req = DkgExecuteMsg::CommitDealing {
|
||||
dealing_bytes,
|
||||
resharing,
|
||||
};
|
||||
|
||||
self.client
|
||||
.execute(
|
||||
@@ -98,9 +107,10 @@ where
|
||||
async fn submit_verification_key_share(
|
||||
&self,
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::CommitVerificationKeyShare { share };
|
||||
let req = DkgExecuteMsg::CommitVerificationKeyShare { share, resharing };
|
||||
|
||||
self.client
|
||||
.execute(
|
||||
|
||||
@@ -21,18 +21,22 @@ pub enum ExecuteMsg {
|
||||
RegisterDealer {
|
||||
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
CommitDealing {
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
CommitVerificationKeyShare {
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
VerifyVerificationKeyShare {
|
||||
owner: Addr,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
SurpassedThreshold {},
|
||||
@@ -45,6 +49,7 @@ pub enum ExecuteMsg {
|
||||
pub enum QueryMsg {
|
||||
GetCurrentEpochState {},
|
||||
GetCurrentEpochThreshold {},
|
||||
GetInitialDealers {},
|
||||
GetDealerDetails {
|
||||
dealer_address: String,
|
||||
},
|
||||
|
||||
@@ -18,6 +18,12 @@ pub type EpochId = u64;
|
||||
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
|
||||
pub const TOTAL_DEALINGS: usize = 2 + 2 + 1;
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub struct InitialReplacementData {
|
||||
pub initial_dealers: Vec<Addr>,
|
||||
pub initial_height: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, JsonSchema,
|
||||
)]
|
||||
@@ -86,15 +92,17 @@ impl Epoch {
|
||||
current_timestamp: Timestamp,
|
||||
) -> Self {
|
||||
let duration = match state {
|
||||
EpochState::PublicKeySubmission => time_configuration.public_key_submission_time_secs,
|
||||
EpochState::DealingExchange => time_configuration.dealing_exchange_time_secs,
|
||||
EpochState::VerificationKeySubmission => {
|
||||
EpochState::PublicKeySubmission { .. } => {
|
||||
time_configuration.public_key_submission_time_secs
|
||||
}
|
||||
EpochState::DealingExchange { .. } => time_configuration.dealing_exchange_time_secs,
|
||||
EpochState::VerificationKeySubmission { .. } => {
|
||||
time_configuration.verification_key_submission_time_secs
|
||||
}
|
||||
EpochState::VerificationKeyValidation => {
|
||||
EpochState::VerificationKeyValidation { .. } => {
|
||||
time_configuration.verification_key_validation_time_secs
|
||||
}
|
||||
EpochState::VerificationKeyFinalization => {
|
||||
EpochState::VerificationKeyFinalization { .. } => {
|
||||
time_configuration.verification_key_finalization_time_secs
|
||||
}
|
||||
EpochState::InProgress => time_configuration.in_progress_time_secs,
|
||||
@@ -123,28 +131,36 @@ impl Epoch {
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EpochState {
|
||||
PublicKeySubmission,
|
||||
DealingExchange,
|
||||
VerificationKeySubmission,
|
||||
VerificationKeyValidation,
|
||||
VerificationKeyFinalization,
|
||||
PublicKeySubmission { resharing: bool },
|
||||
DealingExchange { resharing: bool },
|
||||
VerificationKeySubmission { resharing: bool },
|
||||
VerificationKeyValidation { resharing: bool },
|
||||
VerificationKeyFinalization { resharing: bool },
|
||||
InProgress,
|
||||
}
|
||||
|
||||
impl Default for EpochState {
|
||||
fn default() -> Self {
|
||||
Self::PublicKeySubmission
|
||||
Self::PublicKeySubmission { resharing: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EpochState {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EpochState::PublicKeySubmission => write!(f, "PublicKeySubmission"),
|
||||
EpochState::DealingExchange => write!(f, "DealingExchange"),
|
||||
EpochState::VerificationKeySubmission => write!(f, "VerificationKeySubmission"),
|
||||
EpochState::VerificationKeyValidation => write!(f, "VerificationKeyValidation"),
|
||||
EpochState::VerificationKeyFinalization => write!(f, "VerificationKeyFinalization"),
|
||||
EpochState::PublicKeySubmission { resharing } => {
|
||||
write!(f, "PublicKeySubmission with resharing {resharing}")
|
||||
}
|
||||
EpochState::DealingExchange { resharing } => write!(f, "DealingExchange {resharing}"),
|
||||
EpochState::VerificationKeySubmission { resharing } => {
|
||||
write!(f, "VerificationKeySubmission with resharing {resharing}")
|
||||
}
|
||||
EpochState::VerificationKeyValidation { resharing } => {
|
||||
write!(f, "VerificationKeyValidation with resharing {resharing}")
|
||||
}
|
||||
EpochState::VerificationKeyFinalization { resharing } => {
|
||||
write!(f, "VerificationKeyFinalization with resharing {resharing}")
|
||||
}
|
||||
EpochState::InProgress => write!(f, "InProgress"),
|
||||
}
|
||||
}
|
||||
@@ -153,11 +169,19 @@ impl Display for EpochState {
|
||||
impl EpochState {
|
||||
pub fn next(self) -> Option<Self> {
|
||||
match self {
|
||||
EpochState::PublicKeySubmission => Some(EpochState::DealingExchange),
|
||||
EpochState::DealingExchange => Some(EpochState::VerificationKeySubmission),
|
||||
EpochState::VerificationKeySubmission => Some(EpochState::VerificationKeyValidation),
|
||||
EpochState::VerificationKeyValidation => Some(EpochState::VerificationKeyFinalization),
|
||||
EpochState::VerificationKeyFinalization => Some(EpochState::InProgress),
|
||||
EpochState::PublicKeySubmission { resharing } => {
|
||||
Some(EpochState::DealingExchange { resharing })
|
||||
}
|
||||
EpochState::DealingExchange { resharing } => {
|
||||
Some(EpochState::VerificationKeySubmission { resharing })
|
||||
}
|
||||
EpochState::VerificationKeySubmission { resharing } => {
|
||||
Some(EpochState::VerificationKeyValidation { resharing })
|
||||
}
|
||||
EpochState::VerificationKeyValidation { resharing } => {
|
||||
Some(EpochState::VerificationKeyFinalization { resharing })
|
||||
}
|
||||
EpochState::VerificationKeyFinalization { .. } => Some(EpochState::InProgress),
|
||||
EpochState::InProgress => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,12 @@ pub struct PagedVKSharesResponse {
|
||||
|
||||
pub fn to_cosmos_msg(
|
||||
owner: Addr,
|
||||
resharing: bool,
|
||||
coconut_dkg_addr: String,
|
||||
multisig_addr: String,
|
||||
expiration_time: Timestamp,
|
||||
) -> StdResult<CosmosMsg> {
|
||||
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner };
|
||||
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner, resharing };
|
||||
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: coconut_dkg_addr,
|
||||
msg: to_binary(&verify_vk_share_req)?,
|
||||
@@ -62,7 +63,8 @@ pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<Addr> {
|
||||
funds: _,
|
||||
})) = msgs.get(0)
|
||||
{
|
||||
if let Ok(ExecuteMsg::VerifyVerificationKeyShare { owner }) = from_binary::<ExecuteMsg>(msg)
|
||||
if let Ok(ExecuteMsg::VerifyVerificationKeyShare { owner, .. }) =
|
||||
from_binary::<ExecuteMsg>(msg)
|
||||
{
|
||||
return Some(owner);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,10 @@ impl SecretKey {
|
||||
Self { x, ys }
|
||||
}
|
||||
|
||||
pub fn into_raw(&self) -> (Scalar, Vec<Scalar>) {
|
||||
(self.x, self.ys.clone())
|
||||
}
|
||||
|
||||
/// Derive verification key using this secret key.
|
||||
pub fn verification_key(&self, params: &Parameters) -> VerificationKey {
|
||||
let g1 = params.gen1();
|
||||
|
||||
@@ -7,7 +7,9 @@ use crate::dealers::queries::{
|
||||
use crate::dealers::transactions::try_add_dealer;
|
||||
use crate::dealings::queries::query_dealings_paged;
|
||||
use crate::dealings::transactions::try_commit_dealings;
|
||||
use crate::epoch_state::queries::{query_current_epoch, query_current_epoch_threshold};
|
||||
use crate::epoch_state::queries::{
|
||||
query_current_epoch, query_current_epoch_threshold, query_initial_dealers,
|
||||
};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::transactions::{advance_epoch_state, try_surpassed_threshold};
|
||||
use crate::error::ContractError;
|
||||
@@ -75,15 +77,17 @@ pub fn execute(
|
||||
ExecuteMsg::RegisterDealer {
|
||||
bte_key_with_proof,
|
||||
announce_address,
|
||||
} => try_add_dealer(deps, info, bte_key_with_proof, announce_address),
|
||||
ExecuteMsg::CommitDealing { dealing_bytes } => {
|
||||
try_commit_dealings(deps, info, dealing_bytes)
|
||||
resharing,
|
||||
} => try_add_dealer(deps, info, bte_key_with_proof, announce_address, resharing),
|
||||
ExecuteMsg::CommitDealing {
|
||||
dealing_bytes,
|
||||
resharing,
|
||||
} => try_commit_dealings(deps, info, dealing_bytes, resharing),
|
||||
ExecuteMsg::CommitVerificationKeyShare { share, resharing } => {
|
||||
try_commit_verification_key_share(deps, env, info, share, resharing)
|
||||
}
|
||||
ExecuteMsg::CommitVerificationKeyShare { share } => {
|
||||
try_commit_verification_key_share(deps, env, info, share)
|
||||
}
|
||||
ExecuteMsg::VerifyVerificationKeyShare { owner } => {
|
||||
try_verify_verification_key_share(deps, info, owner)
|
||||
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),
|
||||
@@ -97,6 +101,7 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
|
||||
QueryMsg::GetCurrentEpochThreshold {} => {
|
||||
to_binary(&query_current_epoch_threshold(deps.storage)?)?
|
||||
}
|
||||
QueryMsg::GetInitialDealers {} => to_binary(&query_initial_dealers(deps.storage)?)?,
|
||||
QueryMsg::GetDealerDetails { dealer_address } => {
|
||||
to_binary(&query_dealer_details(deps, dealer_address)?)?
|
||||
}
|
||||
@@ -238,6 +243,7 @@ mod tests {
|
||||
&RegisterDealer {
|
||||
bte_key_with_proof: "bte_key_with_proof".to_string(),
|
||||
announce_address: "127.0.0.1:8000".to_string(),
|
||||
resharing: false,
|
||||
},
|
||||
&vec![],
|
||||
)
|
||||
@@ -251,6 +257,7 @@ mod tests {
|
||||
&RegisterDealer {
|
||||
bte_key_with_proof: "bte_key_with_proof".to_string(),
|
||||
announce_address: "127.0.0.1:8000".to_string(),
|
||||
resharing: false,
|
||||
},
|
||||
&vec![],
|
||||
)
|
||||
@@ -266,6 +273,7 @@ mod tests {
|
||||
&RegisterDealer {
|
||||
bte_key_with_proof: "bte_key_with_proof".to_string(),
|
||||
announce_address: "127.0.0.1:8000".to_string(),
|
||||
resharing: false,
|
||||
},
|
||||
&vec![],
|
||||
)
|
||||
|
||||
@@ -2,26 +2,33 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::dealers::storage as dealers_storage;
|
||||
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::{State, STATE};
|
||||
use crate::state::STATE;
|
||||
use coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState};
|
||||
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
|
||||
|
||||
// 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<'_>, state: &State, dealer: &Addr) -> Result<(), ContractError> {
|
||||
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);
|
||||
}
|
||||
let state = STATE.load(deps.storage)?;
|
||||
|
||||
let height = if resharing {
|
||||
INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state
|
||||
.group_addr
|
||||
.is_voting_member(&deps.querier, dealer, None)?
|
||||
.is_voting_member(&deps.querier, dealer, height)?
|
||||
.ok_or(ContractError::Unauthorized {})?;
|
||||
|
||||
Ok(())
|
||||
@@ -32,11 +39,11 @@ pub fn try_add_dealer(
|
||||
info: MessageInfo,
|
||||
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
check_epoch_state(deps.storage, EpochState::PublicKeySubmission)?;
|
||||
let state = STATE.load(deps.storage)?;
|
||||
check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?;
|
||||
|
||||
verify_dealer(deps.branch(), &state, &info.sender)?;
|
||||
verify_dealer(deps.branch(), &info.sender, resharing)?;
|
||||
|
||||
// if it was already a dealer in the past, assign the same node index
|
||||
let node_index = if let Some(prior_details) =
|
||||
@@ -69,10 +76,63 @@ 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;
|
||||
use crate::support::tests::fixtures::dealer_details_fixture;
|
||||
use crate::support::tests::helpers;
|
||||
use coconut_dkg_common::types::TimeConfiguration;
|
||||
use crate::support::tests::helpers::GROUP_MEMBERS;
|
||||
use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cw4::Member;
|
||||
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: Some(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 = Some(2);
|
||||
Ok(data)
|
||||
})
|
||||
.unwrap();
|
||||
verify_dealer(deps.as_mut(), &new_dealer, true).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_state() {
|
||||
@@ -94,12 +154,13 @@ pub(crate) mod tests {
|
||||
info.clone(),
|
||||
bte_key_with_proof.clone(),
|
||||
announce_address.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::IncorrectEpochState {
|
||||
current_state: EpochState::DealingExchange.to_string(),
|
||||
current_state: EpochState::DealingExchange { resharing: false }.to_string(),
|
||||
expected_state: EpochState::default().to_string(),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::dealers::storage as dealers_storage;
|
||||
use crate::dealings::storage::DEALINGS_BYTES;
|
||||
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use coconut_dkg_common::types::{ContractSafeBytes, EpochState};
|
||||
@@ -12,8 +13,9 @@ pub fn try_commit_dealings(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
resharing: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
check_epoch_state(deps.storage, EpochState::DealingExchange)?;
|
||||
check_epoch_state(deps.storage, EpochState::DealingExchange { resharing })?;
|
||||
// ensure the sender is a dealer
|
||||
if dealers_storage::current_dealers()
|
||||
.may_load(deps.storage, &info.sender)?
|
||||
@@ -21,6 +23,14 @@ pub fn try_commit_dealings(
|
||||
{
|
||||
return Err(ContractError::NotADealer);
|
||||
}
|
||||
if resharing
|
||||
&& !INITIAL_REPLACEMENT_DATA
|
||||
.load(deps.storage)?
|
||||
.initial_dealers
|
||||
.contains(&info.sender)
|
||||
{
|
||||
return Err(ContractError::NotAnInitialDealer);
|
||||
}
|
||||
|
||||
// check if this dealer has already committed to all dealings
|
||||
// (we don't want to allow overwriting anything)
|
||||
@@ -39,29 +49,30 @@ pub fn try_commit_dealings(
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::transactions::advance_epoch_state;
|
||||
use crate::support::tests::fixtures::dealing_bytes_fixture;
|
||||
use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture};
|
||||
use crate::support::tests::helpers;
|
||||
use coconut_dkg_common::dealer::DealerDetails;
|
||||
use coconut_dkg_common::types::TimeConfiguration;
|
||||
use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
#[test]
|
||||
fn invalid_commit_dealing() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let owner = Addr::unchecked("owner");
|
||||
let owner = Addr::unchecked("owner1");
|
||||
let mut env = mock_env();
|
||||
let info = mock_info(owner.as_str(), &[]);
|
||||
let dealing_bytes = dealing_bytes_fixture();
|
||||
|
||||
let ret =
|
||||
try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err();
|
||||
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::IncorrectEpochState {
|
||||
current_state: EpochState::default().to_string(),
|
||||
expected_state: EpochState::DealingExchange.to_string()
|
||||
expected_state: EpochState::DealingExchange { resharing: false }.to_string()
|
||||
}
|
||||
);
|
||||
|
||||
@@ -71,8 +82,8 @@ pub(crate) mod tests {
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
|
||||
let ret =
|
||||
try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err();
|
||||
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
|
||||
.unwrap_err();
|
||||
assert_eq!(ret, ContractError::NotADealer);
|
||||
|
||||
let dealer_details = DealerDetails {
|
||||
@@ -85,14 +96,41 @@ pub(crate) mod tests {
|
||||
.save(deps.as_mut().storage, &owner, &dealer_details)
|
||||
.unwrap();
|
||||
|
||||
// assume we're in resharing mode
|
||||
CURRENT_EPOCH
|
||||
.update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
|
||||
epoch.state = EpochState::DealingExchange { resharing: true };
|
||||
Ok(epoch)
|
||||
})
|
||||
.unwrap();
|
||||
INITIAL_REPLACEMENT_DATA
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
&InitialReplacementData {
|
||||
initial_dealers: vec![],
|
||||
initial_height: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.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();
|
||||
|
||||
for dealings in DEALINGS_BYTES {
|
||||
assert!(!dealings.has(deps.as_mut().storage, &owner));
|
||||
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone());
|
||||
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true);
|
||||
assert!(ret.is_ok());
|
||||
assert!(dealings.has(deps.as_mut().storage, &owner));
|
||||
}
|
||||
let ret =
|
||||
try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err();
|
||||
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::AlreadyCommitted {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
|
||||
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD};
|
||||
use crate::error::ContractError;
|
||||
use coconut_dkg_common::types::Epoch;
|
||||
use coconut_dkg_common::types::{Epoch, InitialReplacementData};
|
||||
use cosmwasm_std::Storage;
|
||||
|
||||
pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
|
||||
@@ -18,6 +18,12 @@ 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::*;
|
||||
@@ -29,7 +35,10 @@ pub(crate) mod test {
|
||||
fn query_state() {
|
||||
let mut deps = init_contract();
|
||||
let epoch = query_current_epoch(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(epoch.state, EpochState::PublicKeySubmission);
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp,
|
||||
mock_env()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_dkg_common::types::Epoch;
|
||||
use coconut_dkg_common::types::{Epoch, InitialReplacementData};
|
||||
use cw_storage_plus::Item;
|
||||
|
||||
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");
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
use crate::dealers::storage::{current_dealers, past_dealers};
|
||||
use crate::dealings::storage::DEALINGS_BYTES;
|
||||
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
|
||||
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD};
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::STATE;
|
||||
use coconut_dkg_common::types::{Epoch, EpochState};
|
||||
use cosmwasm_std::{DepsMut, Env, Order, Response, Storage};
|
||||
use coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
|
||||
use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage};
|
||||
|
||||
fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
THRESHOLD.remove(storage);
|
||||
@@ -27,13 +27,13 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dealers_still_active(deps: &DepsMut<'_>) -> Result<usize, ContractError> {
|
||||
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 current_dealers()
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.flatten()
|
||||
{
|
||||
for dealer_addr in dealers {
|
||||
if state
|
||||
.group_addr
|
||||
.is_voting_member(&deps.querier, &dealer_addr, None)?
|
||||
@@ -45,6 +45,36 @@ fn dealers_still_active(deps: &DepsMut<'_>) -> Result<usize, ContractError> {
|
||||
Ok(still_active)
|
||||
}
|
||||
|
||||
fn dealers_eq_members(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
|
||||
let dealers_still_active = dealers_still_active(
|
||||
&deps.as_ref(),
|
||||
current_dealers()
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.flatten(),
|
||||
)?;
|
||||
let all_dealers = current_dealers()
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.count();
|
||||
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 = INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_dealers;
|
||||
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 advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response, ContractError> {
|
||||
let epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
if epoch.finish_timestamp > env.block.time {
|
||||
@@ -59,13 +89,20 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
|
||||
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
let next_epoch = if let Some(state) = current_epoch.state.next() {
|
||||
// We are during DKG process
|
||||
if state == EpochState::DealingExchange {
|
||||
let current_dealer_count = current_dealers()
|
||||
if let EpochState::DealingExchange { resharing } = state {
|
||||
let current_dealers = current_dealers()
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.count();
|
||||
.collect::<Result<Vec<Addr>, _>>()?;
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
let threshold = (2 * current_dealer_count as u64 + 3 - 1) / 3;
|
||||
let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
|
||||
THRESHOLD.save(deps.storage, &threshold)?;
|
||||
if !resharing {
|
||||
let replacement_data = InitialReplacementData {
|
||||
initial_dealers: current_dealers,
|
||||
initial_height: None,
|
||||
};
|
||||
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
|
||||
}
|
||||
}
|
||||
Epoch::new(
|
||||
state,
|
||||
@@ -73,14 +110,9 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
|
||||
current_epoch.time_configuration,
|
||||
env.block.time,
|
||||
)
|
||||
} else if dealers_still_active(&deps)?
|
||||
== STATE
|
||||
.load(deps.storage)?
|
||||
.group_addr
|
||||
.list_members(&deps.querier, None, None)?
|
||||
.len()
|
||||
{
|
||||
} 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
|
||||
Epoch::new(
|
||||
current_epoch.state,
|
||||
current_epoch.epoch_id,
|
||||
@@ -88,10 +120,21 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
|
||||
env.block.time,
|
||||
)
|
||||
} else {
|
||||
// Dealer set changed, we need to redo DKG from scratch
|
||||
// Dealer set changed, we need to redo DKG...
|
||||
let state = if replacement_threshold_surpassed(&deps)? {
|
||||
// ... in reset mode
|
||||
EpochState::default()
|
||||
} else {
|
||||
// ... in reshare mode
|
||||
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
|
||||
data.initial_height = Some(env.block.height);
|
||||
Ok(data)
|
||||
})?;
|
||||
EpochState::PublicKeySubmission { resharing: true }
|
||||
};
|
||||
reset_epoch_state(deps.storage)?;
|
||||
Epoch::new(
|
||||
EpochState::default(),
|
||||
state,
|
||||
current_epoch.epoch_id + 1,
|
||||
current_epoch.time_configuration,
|
||||
env.block.time,
|
||||
@@ -109,7 +152,10 @@ pub(crate) fn try_surpassed_threshold(
|
||||
check_epoch_state(deps.storage, EpochState::InProgress)?;
|
||||
|
||||
let threshold = THRESHOLD.load(deps.storage)?;
|
||||
if dealers_still_active(&deps)? < threshold as usize {
|
||||
let dealers = current_dealers()
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.flatten();
|
||||
if dealers_still_active(&deps.as_ref(), dealers)? < threshold as usize {
|
||||
reset_epoch_state(deps.storage)?;
|
||||
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
|
||||
Ok(Epoch::new(
|
||||
@@ -139,27 +185,137 @@ pub(crate) mod tests {
|
||||
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 initial_dealers = dealers.iter().map(|d| d.address.clone()).collect();
|
||||
let data = InitialReplacementData {
|
||||
initial_dealers,
|
||||
initial_height: None,
|
||||
};
|
||||
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 i in 0..n as usize {
|
||||
group_members.push((
|
||||
Member {
|
||||
addr: dealers[i].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 details = dealer_details_fixture(1);
|
||||
let different_details = dealer_details_fixture(2);
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details.address, &details)
|
||||
.unwrap();
|
||||
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
current_dealers()
|
||||
.remove(deps.as_mut().storage, &details.address)
|
||||
.unwrap();
|
||||
GROUP_MEMBERS.lock().unwrap().push((
|
||||
Member {
|
||||
addr: "owner1".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
current_dealers()
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
&different_details.address,
|
||||
&different_details,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
current_dealers()
|
||||
.remove(deps.as_mut().storage, &different_details.address)
|
||||
.unwrap();
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details.address, &details)
|
||||
.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,
|
||||
});
|
||||
group.push(Member {
|
||||
addr: "owner2".to_string(),
|
||||
weight: 10,
|
||||
});
|
||||
group.push(Member {
|
||||
addr: "owner3".to_string(),
|
||||
weight: 10,
|
||||
});
|
||||
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_mut()).unwrap());
|
||||
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 as u64 {
|
||||
let details = dealer_details_fixture(i + 1);
|
||||
current_dealers()
|
||||
@@ -167,7 +323,13 @@ pub(crate) mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
i as usize + 1,
|
||||
dealers_still_active(&deps.as_mut()).unwrap()
|
||||
dealers_still_active(
|
||||
&deps.as_ref(),
|
||||
current_dealers()
|
||||
.keys(&deps.storage, None, None, Order::Ascending)
|
||||
.flatten()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -179,22 +341,41 @@ pub(crate) mod tests {
|
||||
{
|
||||
let mut group = GROUP_MEMBERS.lock().unwrap();
|
||||
|
||||
group.push(Member {
|
||||
addr: "owner1".to_string(),
|
||||
weight: 10,
|
||||
});
|
||||
group.push(Member {
|
||||
addr: "owner2".to_string(),
|
||||
weight: 10,
|
||||
});
|
||||
group.push(Member {
|
||||
addr: "owner3".to_string(),
|
||||
weight: 10,
|
||||
});
|
||||
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,
|
||||
));
|
||||
}
|
||||
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(epoch.state, EpochState::PublicKeySubmission);
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp,
|
||||
env.block
|
||||
@@ -211,16 +392,37 @@ pub(crate) mod tests {
|
||||
EarlyEpochStateAdvancement(1)
|
||||
);
|
||||
|
||||
// setup dealer details
|
||||
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(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::DealingExchange);
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::DealingExchange { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp,
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(epoch.time_configuration.dealing_exchange_time_secs)
|
||||
);
|
||||
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: None,
|
||||
};
|
||||
assert_eq!(replacement_data, expected_replacement_data);
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
@@ -234,7 +436,10 @@ pub(crate) mod tests {
|
||||
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);
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::VerificationKeySubmission { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp,
|
||||
env.block.time.plus_seconds(
|
||||
@@ -258,7 +463,10 @@ pub(crate) mod tests {
|
||||
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);
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::VerificationKeyValidation { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp,
|
||||
env.block.time.plus_seconds(
|
||||
@@ -282,7 +490,10 @@ pub(crate) mod tests {
|
||||
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);
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::VerificationKeyFinalization { resharing: false }
|
||||
);
|
||||
assert_eq!(
|
||||
epoch.finish_timestamp,
|
||||
env.block.time.plus_seconds(
|
||||
@@ -327,14 +538,6 @@ pub(crate) mod tests {
|
||||
EarlyEpochStateAdvancement(50)
|
||||
);
|
||||
|
||||
// setup dealer details
|
||||
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();
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -348,11 +551,14 @@ pub(crate) mod tests {
|
||||
);
|
||||
assert_eq!(curr_epoch, expected_epoch);
|
||||
|
||||
// Group changed, slightly, so reset dkg state
|
||||
*GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = Member {
|
||||
addr: "owner4".to_string(),
|
||||
weight: 10,
|
||||
};
|
||||
// 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
|
||||
@@ -361,7 +567,49 @@ pub(crate) mod tests {
|
||||
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::default(),
|
||||
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 all_details: [_; 2] = 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();
|
||||
}
|
||||
|
||||
// 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: false },
|
||||
prev_epoch.epoch_id + 1,
|
||||
prev_epoch.time_configuration,
|
||||
env.block.time,
|
||||
@@ -370,89 +618,103 @@ pub(crate) mod tests {
|
||||
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn surpass_threshold() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
let time_configuration = TimeConfiguration::default();
|
||||
{
|
||||
let mut group = GROUP_MEMBERS.lock().unwrap();
|
||||
|
||||
#[test]
|
||||
fn surpass_threshold() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
let time_configuration = TimeConfiguration::default();
|
||||
{
|
||||
let mut group = GROUP_MEMBERS.lock().unwrap();
|
||||
|
||||
group.push(Member {
|
||||
addr: "owner1".to_string(),
|
||||
weight: 10,
|
||||
});
|
||||
group.push(Member {
|
||||
addr: "owner2".to_string(),
|
||||
weight: 10,
|
||||
});
|
||||
group.push(Member {
|
||||
addr: "owner3".to_string(),
|
||||
weight: 10,
|
||||
});
|
||||
}
|
||||
|
||||
let ret = try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::IncorrectEpochState {
|
||||
current_state: EpochState::default().to_string(),
|
||||
expected_state: EpochState::InProgress.to_string()
|
||||
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 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 ret = try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::IncorrectEpochState {
|
||||
current_state: EpochState::default().to_string(),
|
||||
expected_state: EpochState::InProgress.to_string()
|
||||
}
|
||||
);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
// 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,
|
||||
};
|
||||
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]
|
||||
|
||||
@@ -40,6 +40,9 @@ pub enum ContractError {
|
||||
#[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 dealer has already committed {commitment}")]
|
||||
AlreadyCommitted { commitment: String },
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ pub const GROUP_CONTRACT: &str = "group contract address";
|
||||
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref GROUP_MEMBERS: Mutex<Vec<Member>> = Mutex::new(vec![]);
|
||||
pub static ref GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(vec![]);
|
||||
}
|
||||
|
||||
fn querier_handler(query: &WasmQuery) -> QuerierResult {
|
||||
@@ -29,9 +29,14 @@ fn querier_handler(query: &WasmQuery) -> QuerierResult {
|
||||
panic!("Not supported");
|
||||
}
|
||||
match from_binary(msg) {
|
||||
Ok(Cw4QueryMsg::Member { addr, .. }) => {
|
||||
let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|m| {
|
||||
Ok(Cw4QueryMsg::Member { addr, at_height }) => {
|
||||
let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|(m, h)| {
|
||||
if m.addr == addr {
|
||||
if let Some(height) = at_height {
|
||||
if height != *h {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(m.weight)
|
||||
} else {
|
||||
None
|
||||
@@ -40,7 +45,12 @@ fn querier_handler(query: &WasmQuery) -> QuerierResult {
|
||||
to_binary(&MemberResponse { weight }).unwrap()
|
||||
}
|
||||
Ok(Cw4QueryMsg::ListMembers { .. }) => {
|
||||
let members = GROUP_MEMBERS.lock().unwrap().to_vec();
|
||||
let members = GROUP_MEMBERS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|m| m.0.clone())
|
||||
.collect();
|
||||
to_binary(&MemberListResponse { members }).unwrap()
|
||||
}
|
||||
_ => panic!("Not supported"),
|
||||
|
||||
@@ -17,8 +17,12 @@ pub fn try_commit_verification_key_share(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
check_epoch_state(deps.storage, EpochState::VerificationKeySubmission)?;
|
||||
check_epoch_state(
|
||||
deps.storage,
|
||||
EpochState::VerificationKeySubmission { resharing },
|
||||
)?;
|
||||
// ensure the sender is a dealer
|
||||
let details = dealers_storage::current_dealers()
|
||||
.load(deps.storage, &info.sender)
|
||||
@@ -45,6 +49,7 @@ pub fn try_commit_verification_key_share(
|
||||
|
||||
let msg = to_cosmos_msg(
|
||||
info.sender,
|
||||
resharing,
|
||||
env.contract.address.to_string(),
|
||||
STATE.load(deps.storage)?.multisig_addr.to_string(),
|
||||
env.block
|
||||
@@ -59,8 +64,12 @@ pub fn try_verify_verification_key_share(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
owner: Addr,
|
||||
resharing: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
check_epoch_state(deps.storage, EpochState::VerificationKeyFinalization)?;
|
||||
check_epoch_state(
|
||||
deps.storage,
|
||||
EpochState::VerificationKeyFinalization { resharing },
|
||||
)?;
|
||||
let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
|
||||
MULTISIG.assert_admin(deps.as_ref(), &info.sender)?;
|
||||
vk_shares().update(deps.storage, (&owner, epoch_id), |vk_share| {
|
||||
@@ -117,8 +126,14 @@ mod tests {
|
||||
.save(deps.as_mut().storage, &dealer, &dealer_details)
|
||||
.unwrap();
|
||||
|
||||
try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone())
|
||||
.unwrap();
|
||||
try_commit_verification_key_share(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
share.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap();
|
||||
assert_eq!(
|
||||
vk_share,
|
||||
@@ -145,13 +160,15 @@ mod tests {
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
share.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::IncorrectEpochState {
|
||||
current_state: EpochState::default().to_string(),
|
||||
expected_state: EpochState::VerificationKeySubmission.to_string()
|
||||
expected_state: EpochState::VerificationKeySubmission { resharing: false }
|
||||
.to_string()
|
||||
}
|
||||
);
|
||||
env.block.time = env
|
||||
@@ -169,6 +186,7 @@ mod tests {
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
share.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(ret, ContractError::NotADealer);
|
||||
@@ -184,14 +202,21 @@ mod tests {
|
||||
.save(deps.as_mut().storage, &dealer, &dealer_details)
|
||||
.unwrap();
|
||||
|
||||
try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone())
|
||||
.unwrap();
|
||||
try_commit_verification_key_share(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
share.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ret = try_commit_verification_key_share(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
share.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
@@ -210,13 +235,15 @@ mod tests {
|
||||
let owner = Addr::unchecked("owner");
|
||||
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
|
||||
|
||||
let ret = try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone())
|
||||
.unwrap_err();
|
||||
let ret =
|
||||
try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone(), false)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::IncorrectEpochState {
|
||||
current_state: EpochState::default().to_string(),
|
||||
expected_state: EpochState::VerificationKeyFinalization.to_string()
|
||||
expected_state: EpochState::VerificationKeyFinalization { resharing: false }
|
||||
.to_string()
|
||||
}
|
||||
);
|
||||
|
||||
@@ -241,12 +268,13 @@ mod tests {
|
||||
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
|
||||
let ret =
|
||||
try_verify_verification_key_share(deps.as_mut(), info, owner.clone()).unwrap_err();
|
||||
let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone(), false)
|
||||
.unwrap_err();
|
||||
assert_eq!(ret, ContractError::Admin(AdminError::NotAdmin {}));
|
||||
|
||||
let ret = try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone())
|
||||
.unwrap_err();
|
||||
let ret =
|
||||
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::NoCommitForOwner {
|
||||
@@ -284,8 +312,14 @@ mod tests {
|
||||
dealers_storage::current_dealers()
|
||||
.save(deps.as_mut().storage, &owner, &dealer_details)
|
||||
.unwrap();
|
||||
try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone())
|
||||
.unwrap();
|
||||
try_commit_verification_key_share(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
share.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
env.block.time = env
|
||||
.block
|
||||
@@ -298,6 +332,7 @@ mod tests {
|
||||
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
|
||||
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone()).unwrap();
|
||||
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ fn dkg_proposal() {
|
||||
&RegisterDealer {
|
||||
bte_key_with_proof: "bte_key_with_proof".to_string(),
|
||||
announce_address: "127.0.0.1:8000".to_string(),
|
||||
resharing: false,
|
||||
},
|
||||
&vec![],
|
||||
)
|
||||
@@ -124,6 +125,7 @@ fn dkg_proposal() {
|
||||
|
||||
let msg = CommitVerificationKeyShare {
|
||||
share: "share".to_string(),
|
||||
resharing: false,
|
||||
};
|
||||
let res = app
|
||||
.execute_contract(
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
use crate::coconut::error::Result;
|
||||
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
|
||||
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
|
||||
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId};
|
||||
use coconut_dkg_common::types::{
|
||||
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData,
|
||||
};
|
||||
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
|
||||
use contracts_common::dealings::ContractSafeBytes;
|
||||
use cw3::ProposalResponse;
|
||||
@@ -26,6 +28,7 @@ pub trait Client {
|
||||
async fn get_current_epoch(&self) -> Result<Epoch>;
|
||||
async fn group_member(&self, addr: String) -> Result<MemberResponse>;
|
||||
async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>>;
|
||||
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>>;
|
||||
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse>;
|
||||
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>>;
|
||||
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>>;
|
||||
@@ -38,10 +41,16 @@ pub trait Client {
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult>;
|
||||
async fn submit_dealing(
|
||||
&self,
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult>;
|
||||
async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult>;
|
||||
async fn submit_verification_key_share(
|
||||
&self,
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult>;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
use crate::coconut::client::Client;
|
||||
use crate::coconut::error::CoconutError;
|
||||
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
|
||||
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId, NodeIndex};
|
||||
use coconut_dkg_common::types::{
|
||||
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, NodeIndex,
|
||||
};
|
||||
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
|
||||
use contracts_common::dealings::ContractSafeBytes;
|
||||
use cw3::ProposalResponse;
|
||||
@@ -59,6 +61,12 @@ 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> {
|
||||
@@ -102,10 +110,11 @@ impl DkgClient {
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
) -> Result<NodeIndex, CoconutError> {
|
||||
let res = self
|
||||
.inner
|
||||
.register_dealer(bte_key, announce_address)
|
||||
.register_dealer(bte_key, announce_address, resharing)
|
||||
.await?;
|
||||
let node_index = find_attribute(&res.logs, "wasm", NODE_INDEX)
|
||||
.ok_or(CoconutError::NodeIndexRecoveryError {
|
||||
@@ -123,18 +132,20 @@ impl DkgClient {
|
||||
pub(crate) async fn submit_dealing(
|
||||
&self,
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
self.inner.submit_dealing(dealing_bytes).await?;
|
||||
self.inner.submit_dealing(dealing_bytes, resharing).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn submit_verification_key_share(
|
||||
&self,
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult, CoconutError> {
|
||||
let mut ret = self
|
||||
.inner
|
||||
.submit_verification_key_share(share.clone())
|
||||
.submit_verification_key_share(share.clone(), resharing)
|
||||
.await;
|
||||
for _ in 0..Self::RETRIES {
|
||||
if let Ok(res) = ret {
|
||||
@@ -142,7 +153,7 @@ impl DkgClient {
|
||||
}
|
||||
ret = self
|
||||
.inner
|
||||
.submit_verification_key_share(share.clone())
|
||||
.submit_verification_key_share(share.clone(), resharing)
|
||||
.await;
|
||||
}
|
||||
ret
|
||||
|
||||
@@ -101,13 +101,19 @@ impl<R: RngCore + Clone> DkgController<R> {
|
||||
return;
|
||||
}
|
||||
let ret = match epoch.state {
|
||||
EpochState::PublicKeySubmission => {
|
||||
public_key_submission(&self.dkg_client, &mut self.state).await
|
||||
EpochState::PublicKeySubmission { resharing } => {
|
||||
public_key_submission(&self.dkg_client, &mut self.state, resharing).await
|
||||
}
|
||||
EpochState::DealingExchange => {
|
||||
dealing_exchange(&self.dkg_client, &mut self.state, self.rng.clone()).await
|
||||
EpochState::DealingExchange { resharing } => {
|
||||
dealing_exchange(
|
||||
&self.dkg_client,
|
||||
&mut self.state,
|
||||
self.rng.clone(),
|
||||
resharing,
|
||||
)
|
||||
.await
|
||||
}
|
||||
EpochState::VerificationKeySubmission => {
|
||||
EpochState::VerificationKeySubmission { resharing } => {
|
||||
let keypair_path = pemstore::KeyPairPath::new(
|
||||
self.secret_key_path.clone(),
|
||||
self.verification_key_path.clone(),
|
||||
@@ -116,14 +122,17 @@ impl<R: RngCore + Clone> DkgController<R> {
|
||||
&self.dkg_client,
|
||||
&mut self.state,
|
||||
&keypair_path,
|
||||
resharing,
|
||||
)
|
||||
.await
|
||||
}
|
||||
EpochState::VerificationKeyValidation => {
|
||||
verification_key_validation(&self.dkg_client, &mut self.state).await
|
||||
EpochState::VerificationKeyValidation { resharing } => {
|
||||
verification_key_validation(&self.dkg_client, &mut self.state, resharing)
|
||||
.await
|
||||
}
|
||||
EpochState::VerificationKeyFinalization => {
|
||||
verification_key_finalization(&self.dkg_client, &mut self.state).await
|
||||
EpochState::VerificationKeyFinalization { resharing } => {
|
||||
verification_key_finalization(&self.dkg_client, &mut self.state, resharing)
|
||||
.await
|
||||
}
|
||||
// Just wait, in case we need to redo dkg at some point
|
||||
EpochState::InProgress => {
|
||||
|
||||
@@ -9,11 +9,13 @@ use contracts_common::dealings::ContractSafeBytes;
|
||||
use dkg::bte::setup;
|
||||
use dkg::Dealing;
|
||||
use rand::RngCore;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub(crate) async fn dealing_exchange(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
rng: impl RngCore + Clone,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.receiver_index().is_some() {
|
||||
return Ok(());
|
||||
@@ -21,26 +23,54 @@ pub(crate) async fn dealing_exchange(
|
||||
|
||||
let dealers = dkg_client.get_current_dealers().await?;
|
||||
let threshold = dkg_client.get_current_epoch_threshold().await?;
|
||||
let initial_dealers = dkg_client
|
||||
.get_initial_dealers()
|
||||
.await?
|
||||
.map(|d| d.initial_dealers)
|
||||
.unwrap_or_default();
|
||||
let own_address = dkg_client.get_address().await.as_ref().to_string();
|
||||
state.set_dealers(dealers);
|
||||
state.set_threshold(threshold);
|
||||
let receivers = state.current_dealers_by_idx();
|
||||
let params = setup();
|
||||
let dealer_index = state.node_index_value()?;
|
||||
let receiver_index = receivers
|
||||
.keys()
|
||||
.position(|node_index| *node_index == dealer_index);
|
||||
for _ in 0..TOTAL_DEALINGS {
|
||||
let (dealing, _) = Dealing::create(
|
||||
rng.clone(),
|
||||
¶ms,
|
||||
dealer_index,
|
||||
state.threshold()?,
|
||||
&receivers,
|
||||
None,
|
||||
);
|
||||
dkg_client
|
||||
.submit_dealing(ContractSafeBytes::from(&dealing))
|
||||
.await?;
|
||||
|
||||
let prior_resharing_secrets = if let Some(sk) = state.coconut_secret_key().await {
|
||||
// Double check that we are in resharing mode
|
||||
if resharing {
|
||||
let (x, mut scalars) = sk.into_raw();
|
||||
if scalars.len() + 1 != TOTAL_DEALINGS {
|
||||
return Err(CoconutError::CorruptedCoconutKeyPair);
|
||||
}
|
||||
// We can now erase the keypair from memory
|
||||
state.set_coconut_keypair(None).await;
|
||||
scalars.push(x);
|
||||
scalars
|
||||
} else {
|
||||
log::warn!("Coconut key hasn't been reset in memory. The state might be corrupt");
|
||||
vec![]
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let mut prior_resharing_secrets = VecDeque::from(prior_resharing_secrets);
|
||||
if !resharing || initial_dealers.iter().any(|d| *d == own_address) {
|
||||
let params = setup();
|
||||
for _ in 0..TOTAL_DEALINGS {
|
||||
let (dealing, _) = Dealing::create(
|
||||
rng.clone(),
|
||||
¶ms,
|
||||
dealer_index,
|
||||
state.threshold()?,
|
||||
&receivers,
|
||||
prior_resharing_secrets.pop_front(),
|
||||
);
|
||||
dkg_client
|
||||
.submit_dealing(ContractSafeBytes::from(&dealing), resharing)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
info!("DKG: Finished submitting dealing");
|
||||
@@ -57,9 +87,11 @@ pub(crate) mod tests {
|
||||
use crate::coconut::tests::DummyClient;
|
||||
use crate::coconut::KeyPair;
|
||||
use coconut_dkg_common::dealer::DealerDetails;
|
||||
use coconut_dkg_common::types::InitialReplacementData;
|
||||
use cosmwasm_std::Addr;
|
||||
use dkg::bte::keys::KeyPair as DkgKeyPair;
|
||||
use dkg::bte::{Params, PublicKeyWithProof};
|
||||
use nymcoconut::{ttp_keygen, Parameters};
|
||||
use rand::rngs::OsRng;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
@@ -68,10 +100,11 @@ pub(crate) mod tests {
|
||||
use url::Url;
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
const TEST_VALIDATORS_ADDRESS: [&str; 3] = [
|
||||
const TEST_VALIDATORS_ADDRESS: [&str; 4] = [
|
||||
"n1aq9kakfgwqcufr23lsv644apavcntrsqsk4yus",
|
||||
"n1s9l3xr4g0rglvk4yctktmck3h4eq0gp6z2e20v",
|
||||
"n19kl4py32vsk297dm93ezem992cdyzdy4zuc2x6",
|
||||
"n1jfrs6cmw9t7dv0x8cgny6geunzjh56n2s89fkv",
|
||||
];
|
||||
|
||||
fn insert_dealers(
|
||||
@@ -121,7 +154,7 @@ pub(crate) mod tests {
|
||||
state.set_node_index(Some(self_index));
|
||||
let keypairs = insert_dealers(¶ms, &dealer_details_db);
|
||||
|
||||
dealing_exchange(&dkg_client, &mut state, OsRng)
|
||||
dealing_exchange(&dkg_client, &mut state, OsRng, false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -142,7 +175,7 @@ pub(crate) mod tests {
|
||||
.clone();
|
||||
assert_eq!(dealings.len(), TOTAL_DEALINGS);
|
||||
|
||||
dealing_exchange(&dkg_client, &mut state, OsRng)
|
||||
dealing_exchange(&dkg_client, &mut state, OsRng, false)
|
||||
.await
|
||||
.unwrap();
|
||||
let new_dealings = dealings_db
|
||||
@@ -201,7 +234,7 @@ pub(crate) mod tests {
|
||||
details.bte_public_key_with_proof = bs58::encode(&bytes).into_string();
|
||||
});
|
||||
|
||||
dealing_exchange(&dkg_client, &mut state, OsRng)
|
||||
dealing_exchange(&dkg_client, &mut state, OsRng, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -214,4 +247,85 @@ pub(crate) mod tests {
|
||||
ComplaintReason::InvalidBTEPublicKey
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn resharing_exchange_dealing() {
|
||||
let self_index = 2;
|
||||
let dealer_details_db = Arc::new(RwLock::new(HashMap::new()));
|
||||
let dealings_db = Arc::new(RwLock::new(HashMap::new()));
|
||||
let threshold_db = Arc::new(RwLock::new(Some(3)));
|
||||
let initial_dealers_db = Arc::new(RwLock::new(Some(InitialReplacementData {
|
||||
initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])],
|
||||
initial_height: Some(100),
|
||||
})));
|
||||
let dkg_client = DkgClient::new(
|
||||
DummyClient::new(
|
||||
AccountId::from_str("n1vxkywf9g4cg0k2dehanzwzz64jw782qm0kuynf").unwrap(),
|
||||
)
|
||||
.with_dealer_details(&dealer_details_db)
|
||||
.with_dealings(&dealings_db)
|
||||
.with_threshold(&threshold_db)
|
||||
.with_initial_dealers_db(&initial_dealers_db),
|
||||
);
|
||||
let params = setup();
|
||||
let mut keys = ttp_keygen(&Parameters::new(4).unwrap(), 3, 4).unwrap();
|
||||
let coconut_keypair = KeyPair::new();
|
||||
coconut_keypair.set(Some(keys.pop().unwrap())).await;
|
||||
|
||||
let mut state = State::new(
|
||||
PathBuf::default(),
|
||||
PersistentState::default(),
|
||||
Url::parse("localhost:8000").unwrap(),
|
||||
DkgKeyPair::new(¶ms, OsRng),
|
||||
coconut_keypair.clone(),
|
||||
);
|
||||
state.set_node_index(Some(self_index));
|
||||
let keypairs = insert_dealers(¶ms, &dealer_details_db);
|
||||
|
||||
dealing_exchange(&dkg_client, &mut state, OsRng, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.current_dealers_by_idx().values().collect::<Vec<_>>(),
|
||||
keypairs
|
||||
.iter()
|
||||
.map(|k| k.public_key().public_key())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(state.threshold().unwrap(), 3);
|
||||
assert_eq!(state.receiver_index().unwrap(), 1);
|
||||
let addr = dkg_client.get_address().await;
|
||||
assert!(dealings_db.read().unwrap().get(addr.as_ref()).is_none());
|
||||
|
||||
let mut state = State::new(
|
||||
PathBuf::default(),
|
||||
PersistentState::default(),
|
||||
Url::parse("localhost:8000").unwrap(),
|
||||
DkgKeyPair::new(¶ms, OsRng),
|
||||
coconut_keypair,
|
||||
);
|
||||
state.set_node_index(Some(self_index));
|
||||
// Use a client that is in the initial dealers set
|
||||
let dkg_client = DkgClient::new(
|
||||
DummyClient::new(AccountId::from_str(TEST_VALIDATORS_ADDRESS[0]).unwrap())
|
||||
.with_dealer_details(&dealer_details_db)
|
||||
.with_dealings(&dealings_db)
|
||||
.with_threshold(&threshold_db)
|
||||
.with_initial_dealers_db(&initial_dealers_db),
|
||||
);
|
||||
|
||||
dealing_exchange(&dkg_client, &mut state, OsRng, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let dealings = dealings_db
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(TEST_VALIDATORS_ADDRESS[0])
|
||||
.unwrap()
|
||||
.clone();
|
||||
assert_eq!(dealings.len(), TOTAL_DEALINGS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ use coconut_dkg_common::dealer::DealerType;
|
||||
pub(crate) async fn public_key_submission(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.was_in_progress() {
|
||||
state.reset_persistent().await;
|
||||
state.reset_persistent(resharing).await;
|
||||
}
|
||||
if state.node_index().is_some() {
|
||||
return Ok(());
|
||||
@@ -23,14 +24,14 @@ pub(crate) async fn public_key_submission(
|
||||
if dealer_details.dealer_type == DealerType::Past {
|
||||
// If it was a dealer in a previous epoch, re-register it for this epoch
|
||||
dkg_client
|
||||
.register_dealer(bte_key, state.announce_address().to_string())
|
||||
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
|
||||
.await?;
|
||||
}
|
||||
details.assigned_index
|
||||
} else {
|
||||
// First time registration
|
||||
dkg_client
|
||||
.register_dealer(bte_key, state.announce_address().to_string())
|
||||
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
|
||||
.await?
|
||||
};
|
||||
state.set_node_index(Some(index));
|
||||
@@ -74,7 +75,7 @@ pub(crate) mod tests {
|
||||
.unwrap()
|
||||
.details
|
||||
.is_none());
|
||||
public_key_submission(&dkg_client, &mut state)
|
||||
public_key_submission(&dkg_client, &mut state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
let client_idx = dkg_client
|
||||
@@ -88,7 +89,7 @@ pub(crate) mod tests {
|
||||
|
||||
// keeps the same index from chain, not calling register_dealer again
|
||||
state.set_node_index(None);
|
||||
public_key_submission(&dkg_client, &mut state)
|
||||
public_key_submission(&dkg_client, &mut state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(state.node_index().unwrap(), client_idx);
|
||||
|
||||
@@ -9,6 +9,7 @@ use coconut_dkg_common::types::EpochState;
|
||||
use cosmwasm_std::Addr;
|
||||
use dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof};
|
||||
use dkg::{NodeIndex, RecoveredVerificationKeys, Threshold};
|
||||
use nymcoconut::SecretKey;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::collections::BTreeMap;
|
||||
@@ -71,18 +72,18 @@ pub(crate) trait ConsistentState {
|
||||
fn proposal_id_value(&self) -> Result<u64, CoconutError>;
|
||||
async fn is_consistent(&self, epoch_state: EpochState) -> Result<(), CoconutError> {
|
||||
match epoch_state {
|
||||
EpochState::PublicKeySubmission => {}
|
||||
EpochState::DealingExchange => {
|
||||
EpochState::PublicKeySubmission { .. } => {}
|
||||
EpochState::DealingExchange { .. } => {
|
||||
self.node_index_value()?;
|
||||
}
|
||||
EpochState::VerificationKeySubmission => {
|
||||
EpochState::VerificationKeySubmission { .. } => {
|
||||
self.receiver_index_value()?;
|
||||
self.threshold()?;
|
||||
}
|
||||
EpochState::VerificationKeyValidation => {
|
||||
EpochState::VerificationKeyValidation { .. } => {
|
||||
self.coconut_keypair_is_some().await?;
|
||||
}
|
||||
EpochState::VerificationKeyFinalization => {
|
||||
EpochState::VerificationKeyFinalization { .. } => {
|
||||
self.proposal_id_value()?;
|
||||
}
|
||||
EpochState::InProgress => {}
|
||||
@@ -241,8 +242,10 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reset_persistent(&mut self) {
|
||||
self.coconut_keypair.set(None).await;
|
||||
pub async fn reset_persistent(&mut self, resharing: bool) {
|
||||
if !resharing {
|
||||
self.coconut_keypair.set(None).await;
|
||||
}
|
||||
self.node_index = Default::default();
|
||||
self.dealers = Default::default();
|
||||
self.receiver_index = Default::default();
|
||||
@@ -270,6 +273,14 @@ impl State {
|
||||
self.coconut_keypair.get().await.is_some()
|
||||
}
|
||||
|
||||
pub async fn coconut_secret_key(&self) -> Option<SecretKey> {
|
||||
self.coconut_keypair
|
||||
.get()
|
||||
.await
|
||||
.as_ref()
|
||||
.map(|kp| kp.secret_key())
|
||||
}
|
||||
|
||||
pub fn node_index(&self) -> Option<NodeIndex> {
|
||||
self.node_index
|
||||
}
|
||||
@@ -324,8 +335,11 @@ impl State {
|
||||
self.recovered_vks = recovered_vks;
|
||||
}
|
||||
|
||||
pub async fn set_coconut_keypair(&mut self, coconut_keypair: coconut_interface::KeyPair) {
|
||||
self.coconut_keypair.set(Some(coconut_keypair)).await
|
||||
pub async fn set_coconut_keypair(
|
||||
&mut self,
|
||||
coconut_keypair: Option<coconut_interface::KeyPair>,
|
||||
) {
|
||||
self.coconut_keypair.set(coconut_keypair).await
|
||||
}
|
||||
|
||||
pub fn set_node_index(&mut self, node_index: Option<NodeIndex>) {
|
||||
|
||||
@@ -14,6 +14,7 @@ use cosmwasm_std::Addr;
|
||||
use credentials::coconut::bandwidth::{PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
|
||||
use cw3::{ProposalResponse, Status};
|
||||
use dkg::bte::{decrypt_share, setup};
|
||||
use dkg::error::DkgError;
|
||||
use dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold};
|
||||
use nymcoconut::tests::helpers::transpose_matrix;
|
||||
use nymcoconut::{check_vk_pairing, Base58, KeyPair, Parameters, SecretKey, VerificationKey};
|
||||
@@ -26,10 +27,21 @@ async fn deterministic_filter_dealers(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
threshold: Threshold,
|
||||
resharing: bool,
|
||||
) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> {
|
||||
let mut dealings_maps = vec![];
|
||||
let initial_dealers_by_addr = state.current_dealers_by_addr();
|
||||
let initial_receivers = state.current_dealers_by_idx();
|
||||
let initial_resharing_dealers = if resharing {
|
||||
dkg_client
|
||||
.get_initial_dealers()
|
||||
.await?
|
||||
.map(|d| d.initial_dealers)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let params = setup();
|
||||
|
||||
for idx in 0..TOTAL_DEALINGS {
|
||||
@@ -66,11 +78,15 @@ async fn deterministic_filter_dealers(
|
||||
}));
|
||||
dealings_maps.push(dealings_map);
|
||||
}
|
||||
|
||||
for (addr, _) in initial_dealers_by_addr.iter() {
|
||||
for dealings_map in dealings_maps.iter() {
|
||||
if !dealings_map.iter().any(|(_, (address, _))| address == addr) {
|
||||
state.mark_bad_dealer(addr, ComplaintReason::MissingDealing);
|
||||
break;
|
||||
// in resharing mode, we don't commit dealings from dealers outside the initial set
|
||||
if !resharing || initial_resharing_dealers.contains(addr) {
|
||||
for dealings_map in dealings_maps.iter() {
|
||||
if !dealings_map.iter().any(|(_, (address, _))| address == addr) {
|
||||
state.mark_bad_dealer(addr, ComplaintReason::MissingDealing);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,16 +106,16 @@ fn derive_partial_keypair(
|
||||
let mut scalars = vec![];
|
||||
let mut recovered_vks = vec![];
|
||||
for dealings_map in dealings_maps.into_iter() {
|
||||
let filtered_dealings: Vec<_> = dealings_map
|
||||
let (filtered_dealers, filtered_dealings): (Vec<_>, Vec<_>) = dealings_map
|
||||
.into_iter()
|
||||
.filter_map(|(_, (addr, dealing))| {
|
||||
.filter_map(|(idx, (addr, dealing))| {
|
||||
if filtered_dealers_by_addr.keys().any(|a| addr == *a) {
|
||||
Some(dealing)
|
||||
Some((idx, dealing))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.unzip();
|
||||
let recovered = try_recover_verification_keys(
|
||||
&filtered_dealings,
|
||||
threshold,
|
||||
@@ -111,19 +127,18 @@ fn derive_partial_keypair(
|
||||
.iter()
|
||||
.map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None))
|
||||
.collect::<Result<_, _>>()?;
|
||||
let scalar = combine_shares(
|
||||
shares,
|
||||
&filtered_receivers_by_idx
|
||||
.keys()
|
||||
.copied()
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
let scalar = combine_shares(shares, &filtered_dealers)?;
|
||||
scalars.push(scalar);
|
||||
}
|
||||
state.set_recovered_vks(recovered_vks);
|
||||
|
||||
let params = Parameters::new(PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES)?;
|
||||
let x = scalars.pop().unwrap();
|
||||
let x = scalars.pop().ok_or(CoconutError::DkgError(
|
||||
DkgError::NotEnoughDealingsAvailable {
|
||||
available: 0,
|
||||
required: 1,
|
||||
},
|
||||
))?;
|
||||
let sk = SecretKey::create_from_raw(x, scalars);
|
||||
let vk = sk.verification_key(¶ms);
|
||||
|
||||
@@ -134,17 +149,21 @@ pub(crate) async fn verification_key_submission(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
keypair_path: &KeyPairPath,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.coconut_keypair_is_some().await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let threshold = state.threshold()?;
|
||||
let dealings_maps = deterministic_filter_dealers(dkg_client, state, threshold).await?;
|
||||
let dealings_maps =
|
||||
deterministic_filter_dealers(dkg_client, state, threshold, resharing).await?;
|
||||
let coconut_keypair = derive_partial_keypair(state, threshold, dealings_maps)?;
|
||||
let vk_share = coconut_keypair.verification_key().to_bs58();
|
||||
pemstore::store_keypair(&coconut_keypair, keypair_path)?;
|
||||
let res = dkg_client.submit_verification_key_share(vk_share).await?;
|
||||
let res = dkg_client
|
||||
.submit_verification_key_share(vk_share, resharing)
|
||||
.await?;
|
||||
let proposal_id = find_attribute(&res.logs, "wasm", DKG_PROPOSAL_ID)
|
||||
.ok_or(CoconutError::ProposalIdError {
|
||||
reason: String::from("proposal id not found"),
|
||||
@@ -155,7 +174,7 @@ pub(crate) async fn verification_key_submission(
|
||||
reason: String::from("proposal id could not be parsed to u64"),
|
||||
})?;
|
||||
state.set_proposal_id(proposal_id);
|
||||
state.set_coconut_keypair(coconut_keypair).await;
|
||||
state.set_coconut_keypair(Some(coconut_keypair)).await;
|
||||
info!("DKG: Submitted own verification key");
|
||||
|
||||
Ok(())
|
||||
@@ -173,6 +192,7 @@ fn validate_proposal(proposal: &ProposalResponse) -> Option<(Addr, u64)> {
|
||||
pub(crate) async fn verification_key_validation(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
_resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.voted_vks() {
|
||||
return Ok(());
|
||||
@@ -233,6 +253,7 @@ pub(crate) async fn verification_key_validation(
|
||||
pub(crate) async fn verification_key_finalization(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
_resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.executed_proposal() {
|
||||
return Ok(());
|
||||
@@ -257,9 +278,11 @@ pub(crate) mod tests {
|
||||
use crate::coconut::tests::DummyClient;
|
||||
use crate::coconut::KeyPair;
|
||||
use coconut_dkg_common::dealer::DealerDetails;
|
||||
use coconut_dkg_common::types::InitialReplacementData;
|
||||
use coconut_dkg_common::verification_key::ContractVKShare;
|
||||
use contracts_common::dealings::ContractSafeBytes;
|
||||
use dkg::bte::keys::KeyPair as DkgKeyPair;
|
||||
use nymcoconut::aggregate_verification_keys;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::Rng;
|
||||
use std::collections::HashMap;
|
||||
@@ -276,6 +299,7 @@ pub(crate) mod tests {
|
||||
proposal_db: Arc<RwLock<HashMap<u64, ProposalResponse>>>,
|
||||
verification_share_db: Arc<RwLock<HashMap<String, ContractVKShare>>>,
|
||||
threshold_db: Arc<RwLock<Option<Threshold>>>,
|
||||
initial_dealers_db: Arc<RwLock<Option<InitialReplacementData>>>,
|
||||
}
|
||||
|
||||
impl MockContractDb {
|
||||
@@ -286,6 +310,7 @@ pub(crate) mod tests {
|
||||
proposal_db: Arc::new(Default::default()),
|
||||
verification_share_db: Arc::new(Default::default()),
|
||||
threshold_db: Arc::new(RwLock::new(Some(2))),
|
||||
initial_dealers_db: Arc::new(RwLock::new(Default::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,7 +332,8 @@ pub(crate) mod tests {
|
||||
.with_dealings(&db.dealings_db)
|
||||
.with_proposal_db(&db.proposal_db)
|
||||
.with_verification_share(&db.verification_share_db)
|
||||
.with_threshold(&db.threshold_db),
|
||||
.with_threshold(&db.threshold_db)
|
||||
.with_initial_dealers_db(&db.initial_dealers_db),
|
||||
);
|
||||
let keypair = DkgKeyPair::new(¶ms, OsRng);
|
||||
let state = State::new(
|
||||
@@ -320,10 +346,21 @@ pub(crate) mod tests {
|
||||
clients_and_states.push((dkg_client, state));
|
||||
}
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
public_key_submission(dkg_client, state).await.unwrap();
|
||||
public_key_submission(dkg_client, state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
clients_and_states
|
||||
}
|
||||
|
||||
async fn prepare_clients_and_states_with_dealing(
|
||||
db: &MockContractDb,
|
||||
) -> Vec<(DkgClient, State)> {
|
||||
let mut clients_and_states = prepare_clients_and_states(db).await;
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
dealing_exchange(dkg_client, state, OsRng).await.unwrap();
|
||||
dealing_exchange(dkg_client, state, OsRng, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
clients_and_states
|
||||
}
|
||||
@@ -331,13 +368,13 @@ pub(crate) mod tests {
|
||||
async fn prepare_clients_and_states_with_submission(
|
||||
db: &MockContractDb,
|
||||
) -> Vec<(DkgClient, State)> {
|
||||
let mut clients_and_states = prepare_clients_and_states(db).await;
|
||||
let mut clients_and_states = prepare_clients_and_states_with_dealing(db).await;
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
let random_file: usize = OsRng.gen();
|
||||
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
|
||||
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
|
||||
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
|
||||
verification_key_submission(dkg_client, state, &keypair_path)
|
||||
verification_key_submission(dkg_client, state, &keypair_path, false)
|
||||
.await
|
||||
.unwrap();
|
||||
std::fs::remove_file(private_key_path).unwrap();
|
||||
@@ -351,7 +388,7 @@ pub(crate) mod tests {
|
||||
) -> Vec<(DkgClient, State)> {
|
||||
let mut clients_and_states = prepare_clients_and_states_with_submission(db).await;
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_validation(dkg_client, state)
|
||||
verification_key_validation(dkg_client, state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -363,7 +400,7 @@ pub(crate) mod tests {
|
||||
) -> Vec<(DkgClient, State)> {
|
||||
let mut clients_and_states = prepare_clients_and_states_with_validation(db).await;
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_finalization(dkg_client, state)
|
||||
verification_key_finalization(dkg_client, state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -374,9 +411,9 @@ pub(crate) mod tests {
|
||||
#[ignore] // expensive test
|
||||
async fn check_dealers_filter_all_good() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states(&db).await;
|
||||
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), TOTAL_DEALINGS);
|
||||
@@ -390,7 +427,7 @@ pub(crate) mod tests {
|
||||
#[ignore] // expensive test
|
||||
async fn check_dealers_filter_one_bad_dealing() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states(&db).await;
|
||||
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
|
||||
|
||||
// corrupt just one dealing
|
||||
db.dealings_db
|
||||
@@ -404,7 +441,7 @@ pub(crate) mod tests {
|
||||
});
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), TOTAL_DEALINGS);
|
||||
@@ -420,10 +457,74 @@ pub(crate) mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn check_dealers_filter_all_bad_dealings() {
|
||||
async fn check_dealers_resharing_filter_one_missing_dealing() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states(&db).await;
|
||||
|
||||
// add all but the first dealing
|
||||
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
|
||||
dealing_exchange(dkg_client, state, OsRng, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
|
||||
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
|
||||
initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])],
|
||||
initial_height: None,
|
||||
});
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), TOTAL_DEALINGS);
|
||||
let corrupted_status = state
|
||||
.all_dealers()
|
||||
.get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0]))
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(*corrupted_status, ComplaintReason::MissingDealing);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn check_dealers_resharing_filter_one_noninitial_missing_dealing() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states(&db).await;
|
||||
|
||||
// add all but the first dealing
|
||||
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
|
||||
dealing_exchange(dkg_client, state, OsRng, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
|
||||
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
|
||||
initial_dealers: vec![],
|
||||
initial_height: None,
|
||||
});
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), TOTAL_DEALINGS);
|
||||
assert!(state
|
||||
.all_dealers()
|
||||
.get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0]))
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.is_ok(),);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn check_dealers_filter_all_bad_dealings() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
|
||||
|
||||
// corrupt all dealings of one address
|
||||
db.dealings_db
|
||||
.write()
|
||||
@@ -436,7 +537,7 @@ pub(crate) mod tests {
|
||||
});
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), TOTAL_DEALINGS);
|
||||
@@ -457,7 +558,7 @@ pub(crate) mod tests {
|
||||
#[ignore] // expensive test
|
||||
async fn check_dealers_filter_malformed_dealing() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states(&db).await;
|
||||
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
|
||||
|
||||
// corrupt just one dealing
|
||||
db.dealings_db
|
||||
@@ -471,12 +572,12 @@ pub(crate) mod tests {
|
||||
});
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
|
||||
deterministic_filter_dealers(dkg_client, state, 2)
|
||||
deterministic_filter_dealers(dkg_client, state, 2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
// second filter will leave behind the bad dealer and surface why it was left out
|
||||
// in the first place
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), TOTAL_DEALINGS);
|
||||
@@ -494,7 +595,7 @@ pub(crate) mod tests {
|
||||
#[ignore] // expensive test
|
||||
async fn check_dealers_filter_dealing_verification_error() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states(&db).await;
|
||||
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
|
||||
|
||||
// corrupt just one dealing
|
||||
db.dealings_db
|
||||
@@ -513,12 +614,12 @@ pub(crate) mod tests {
|
||||
});
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
|
||||
deterministic_filter_dealers(dkg_client, state, 2)
|
||||
deterministic_filter_dealers(dkg_client, state, 2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
// second filter will leave behind the bad dealer and surface why it was left out
|
||||
// in the first place
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), TOTAL_DEALINGS);
|
||||
@@ -536,9 +637,9 @@ pub(crate) mod tests {
|
||||
#[ignore] // expensive test
|
||||
async fn partial_keypair_derivation() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states(&db).await;
|
||||
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(derive_partial_keypair(state, 2, filtered).is_ok());
|
||||
@@ -549,7 +650,7 @@ pub(crate) mod tests {
|
||||
#[ignore] // expensive test
|
||||
async fn partial_keypair_derivation_with_threshold() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states(&db).await;
|
||||
let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await;
|
||||
|
||||
// corrupt just one dealing
|
||||
db.dealings_db
|
||||
@@ -563,7 +664,7 @@ pub(crate) mod tests {
|
||||
});
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2)
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(derive_partial_keypair(state, 2, filtered).is_ok());
|
||||
@@ -616,7 +717,7 @@ pub(crate) mod tests {
|
||||
.and_modify(|share| share.share.push('x'));
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_validation(dkg_client, state)
|
||||
verification_key_validation(dkg_client, state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -658,7 +759,7 @@ pub(crate) mod tests {
|
||||
.and_modify(|share| share.share = second_share);
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_validation(dkg_client, state)
|
||||
verification_key_validation(dkg_client, state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -696,4 +797,102 @@ pub(crate) mod tests {
|
||||
assert_eq!(proposal.status, Status::Executed);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn reshare_preserves_keys() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
|
||||
let params = Parameters::new(4).unwrap();
|
||||
|
||||
let mut vks = vec![];
|
||||
let mut indices = vec![];
|
||||
for (_, state) in clients_and_states.iter() {
|
||||
let vk = state
|
||||
.coconut_secret_key()
|
||||
.await
|
||||
.unwrap()
|
||||
.verification_key(¶ms);
|
||||
let index = state.node_index().unwrap();
|
||||
vks.push(vk);
|
||||
indices.push(index);
|
||||
}
|
||||
let initial_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap();
|
||||
|
||||
let new_dkg_client = DkgClient::new(
|
||||
DummyClient::new(
|
||||
AccountId::from_str("n1sqkxzh7nl6kgndr4ew9795t2nkwmd8tpql67q7").unwrap(),
|
||||
)
|
||||
.with_dealer_details(&db.dealer_details_db)
|
||||
.with_dealings(&db.dealings_db)
|
||||
.with_proposal_db(&db.proposal_db)
|
||||
.with_verification_share(&db.verification_share_db)
|
||||
.with_threshold(&db.threshold_db)
|
||||
.with_initial_dealers_db(&db.initial_dealers_db),
|
||||
);
|
||||
let keypair = DkgKeyPair::new(&setup(), OsRng);
|
||||
let state = State::new(
|
||||
PathBuf::default(),
|
||||
PersistentState::default(),
|
||||
Url::parse("localhost:8000").unwrap(),
|
||||
keypair,
|
||||
KeyPair::new(),
|
||||
);
|
||||
|
||||
let removed_dealer = clients_and_states.first().unwrap().0.get_address().await;
|
||||
db.dealer_details_db
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(removed_dealer.as_ref());
|
||||
*db.dealings_db.write().unwrap() = Default::default();
|
||||
*db.verification_share_db.write().unwrap() = Default::default();
|
||||
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
|
||||
initial_dealers: vec![
|
||||
Addr::unchecked(clients_and_states[1].0.get_address().await.as_ref()),
|
||||
Addr::unchecked(clients_and_states[2].0.get_address().await.as_ref()),
|
||||
],
|
||||
initial_height: None,
|
||||
});
|
||||
*clients_and_states.first_mut().unwrap() = (new_dkg_client, state);
|
||||
clients_and_states[1].1.set_was_in_progress();
|
||||
clients_and_states[2].1.set_was_in_progress();
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
public_key_submission(dkg_client, state, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
dealing_exchange(dkg_client, state, OsRng, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
let random_file: usize = OsRng.gen();
|
||||
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
|
||||
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
|
||||
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
|
||||
verification_key_submission(dkg_client, state, &keypair_path, true)
|
||||
.await
|
||||
.unwrap();
|
||||
std::fs::remove_file(private_key_path).unwrap();
|
||||
std::fs::remove_file(public_key_path).unwrap();
|
||||
}
|
||||
let mut vks = vec![];
|
||||
let mut indices = vec![];
|
||||
for (_, state) in clients_and_states.iter() {
|
||||
let vk = state
|
||||
.coconut_secret_key()
|
||||
.await
|
||||
.unwrap()
|
||||
.verification_key(¶ms);
|
||||
let index = state.node_index().unwrap();
|
||||
vks.push(vk);
|
||||
indices.push(index);
|
||||
}
|
||||
let reshared_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap();
|
||||
assert_eq!(initial_master_vk, reshared_master_vk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,9 @@ pub enum CoconutError {
|
||||
#[error("DKG has not finished yet in order to derive the coconut key")]
|
||||
KeyPairNotDerivedYet,
|
||||
|
||||
#[error("The coconut keypair is corrupted")]
|
||||
CorruptedCoconutKeyPair,
|
||||
|
||||
#[error("There was a problem with the proposal id: {reason}")]
|
||||
ProposalIdError { reason: String },
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@ use coconut_dkg_common::dealer::{
|
||||
ContractDealing, DealerDetails, DealerDetailsResponse, DealerType,
|
||||
};
|
||||
use coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX};
|
||||
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId, TOTAL_DEALINGS};
|
||||
use coconut_dkg_common::types::{
|
||||
EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, TOTAL_DEALINGS,
|
||||
};
|
||||
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
|
||||
use contracts_common::dealings::ContractSafeBytes;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
@@ -72,6 +74,8 @@ pub(crate) struct DummyClient {
|
||||
threshold: Arc<RwLock<Option<Threshold>>>,
|
||||
dealings: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
|
||||
verification_share: Arc<RwLock<HashMap<String, ContractVKShare>>>,
|
||||
group_db: Arc<RwLock<HashMap<String, MemberResponse>>>,
|
||||
initial_dealers_db: Arc<RwLock<Option<InitialReplacementData>>>,
|
||||
}
|
||||
|
||||
impl DummyClient {
|
||||
@@ -86,6 +90,8 @@ impl DummyClient {
|
||||
threshold: Arc::new(RwLock::new(None)),
|
||||
dealings: Arc::new(RwLock::new(HashMap::new())),
|
||||
verification_share: Arc::new(RwLock::new(HashMap::new())),
|
||||
group_db: Arc::new(RwLock::new(HashMap::new())),
|
||||
initial_dealers_db: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +149,22 @@ impl DummyClient {
|
||||
self.verification_share = Arc::clone(verification_share);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn _with_group_db(
|
||||
mut self,
|
||||
group_db: &Arc<RwLock<HashMap<String, MemberResponse>>>,
|
||||
) -> Self {
|
||||
self.group_db = Arc::clone(group_db);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_initial_dealers_db(
|
||||
mut self,
|
||||
initial_dealers: &Arc<RwLock<Option<InitialReplacementData>>>,
|
||||
) -> Self {
|
||||
self.initial_dealers_db = Arc::clone(initial_dealers);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -193,14 +215,24 @@ impl super::client::Client for DummyClient {
|
||||
Ok(*self.epoch.read().unwrap())
|
||||
}
|
||||
|
||||
async fn group_member(&self, _addr: String) -> Result<MemberResponse> {
|
||||
todo!()
|
||||
async fn group_member(&self, addr: String) -> Result<MemberResponse> {
|
||||
Ok(self
|
||||
.group_db
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&addr)
|
||||
.cloned()
|
||||
.unwrap_or(MemberResponse { weight: None }))
|
||||
}
|
||||
|
||||
async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>> {
|
||||
Ok(*self.threshold.read().unwrap())
|
||||
}
|
||||
|
||||
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>> {
|
||||
Ok(self.initial_dealers_db.read().unwrap().clone())
|
||||
}
|
||||
|
||||
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
|
||||
Ok(DealerDetailsResponse {
|
||||
details: self
|
||||
@@ -289,17 +321,25 @@ impl super::client::Client for DummyClient {
|
||||
&self,
|
||||
bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||
announce_address: String,
|
||||
_resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
let assigned_index = OsRng.gen();
|
||||
self.dealer_details.write().unwrap().insert(
|
||||
self.validator_address.to_string(),
|
||||
DealerDetails {
|
||||
address: Addr::unchecked(self.validator_address.to_string()),
|
||||
bte_public_key_with_proof,
|
||||
announce_address,
|
||||
assigned_index,
|
||||
},
|
||||
);
|
||||
let mut dealer_details = self.dealer_details.write().unwrap();
|
||||
let assigned_index =
|
||||
if let Some(details) = dealer_details.get(self.validator_address.as_ref()) {
|
||||
details.assigned_index
|
||||
} else {
|
||||
let assigned_index = OsRng.gen();
|
||||
dealer_details.insert(
|
||||
self.validator_address.to_string(),
|
||||
DealerDetails {
|
||||
address: Addr::unchecked(self.validator_address.to_string()),
|
||||
bte_public_key_with_proof,
|
||||
announce_address,
|
||||
assigned_index,
|
||||
},
|
||||
);
|
||||
assigned_index
|
||||
};
|
||||
Ok(ExecuteResult {
|
||||
logs: vec![Log {
|
||||
msg_index: 0,
|
||||
@@ -312,7 +352,11 @@ impl super::client::Client for DummyClient {
|
||||
})
|
||||
}
|
||||
|
||||
async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult> {
|
||||
async fn submit_dealing(
|
||||
&self,
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
_resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
self.dealings
|
||||
.write()
|
||||
.unwrap()
|
||||
@@ -335,6 +379,7 @@ impl super::client::Client for DummyClient {
|
||||
async fn submit_verification_key_share(
|
||||
&self,
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
let dealer_details = self
|
||||
.dealer_details
|
||||
@@ -357,6 +402,7 @@ impl super::client::Client for DummyClient {
|
||||
let proposal_id = OsRng.gen();
|
||||
let verify_vk_share_req = coconut_dkg_common::msg::ExecuteMsg::VerifyVerificationKeyShare {
|
||||
owner: Addr::unchecked(self.validator_address.as_ref()),
|
||||
resharing,
|
||||
};
|
||||
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: String::new(),
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::support::config::Config;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
|
||||
use coconut_dkg_common::types::InitialReplacementData;
|
||||
use coconut_dkg_common::{
|
||||
dealer::{ContractDealing, DealerDetails, DealerDetailsResponse},
|
||||
types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId},
|
||||
@@ -338,6 +339,12 @@ impl crate::coconut::client::Client for Client {
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn get_initial_dealers(
|
||||
&self,
|
||||
) -> crate::coconut::error::Result<Option<InitialReplacementData>> {
|
||||
Ok(self.0.read().await.nyxd.get_initial_dealers().await?)
|
||||
}
|
||||
|
||||
async fn get_self_registered_dealer_details(
|
||||
&self,
|
||||
) -> crate::coconut::error::Result<DealerDetailsResponse> {
|
||||
@@ -413,39 +420,42 @@ impl crate::coconut::client::Client for Client {
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult, CoconutError> {
|
||||
Ok(self
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.nyxd
|
||||
.register_dealer(bte_key, announce_address, None)
|
||||
.register_dealer(bte_key, announce_address, resharing, None)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn submit_dealing(
|
||||
&self,
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult, CoconutError> {
|
||||
Ok(self
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.nyxd
|
||||
.submit_dealing_bytes(dealing_bytes, None)
|
||||
.submit_dealing_bytes(dealing_bytes, resharing, None)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn submit_verification_key_share(
|
||||
&self,
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
) -> crate::coconut::error::Result<ExecuteResult> {
|
||||
Ok(self
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.nyxd
|
||||
.submit_verification_key_share(share, None)
|
||||
.submit_verification_key_share(share, resharing, None)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user