Feature/fix resharing (#3139)
* Compare verified vks against current group instead of initial dealers * Fix various dkg logs * API auto-advance epoch even on corrupt states * Use verified vks as ultimate truth for dealers * Set initial dealers based of verified vk * Extend register period even more * Fix test * Use shares from current epoch * Save initial dealers only when triggering resharing * Fix tests * Backup the last InProgress state too * Reset previous signers that are not initial dealers * Add unit test for bug reproduction * More verbose debug logging * Handle edge case for coconut keypair removal * Update dkg api test * Remove dealings directly for each key * Replacement data is saved only on the first reshare start * More debug logging * On failed DKG, just reset * Clippy fix
This commit is contained in:
committed by
GitHub
parent
8e96318478
commit
9eaf9cf491
@@ -21,7 +21,7 @@ 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>,
|
||||
pub initial_height: u64,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
||||
@@ -22,7 +22,7 @@ fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<()
|
||||
let state = STATE.load(deps.storage)?;
|
||||
|
||||
let height = if resharing {
|
||||
INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height
|
||||
Some(INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -105,7 +105,7 @@ pub(crate) mod tests {
|
||||
deps.as_mut().storage,
|
||||
&InitialReplacementData {
|
||||
initial_dealers: vec![details1.address, details2.address, details3.address],
|
||||
initial_height: Some(1),
|
||||
initial_height: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -126,7 +126,7 @@ pub(crate) mod tests {
|
||||
|
||||
INITIAL_REPLACEMENT_DATA
|
||||
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
|
||||
data.initial_height = Some(2);
|
||||
data.initial_height = 2;
|
||||
Ok(data)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -110,7 +110,7 @@ pub(crate) mod tests {
|
||||
deps.as_mut().storage,
|
||||
&InitialReplacementData {
|
||||
initial_dealers: vec![],
|
||||
initial_height: None,
|
||||
initial_height: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRES
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::STATE;
|
||||
use crate::verification_key_shares::storage::verified_dealers;
|
||||
use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage};
|
||||
use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
|
||||
|
||||
@@ -19,7 +20,13 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
for dealer_addr in dealers {
|
||||
let details = current_dealers().load(storage, &dealer_addr)?;
|
||||
for dealings in DEALINGS_BYTES {
|
||||
dealings.remove(storage, &details.address);
|
||||
let dealing_keys: Vec<_> = dealings
|
||||
.keys(storage, None, None, Order::Ascending)
|
||||
.flatten()
|
||||
.collect();
|
||||
for key in dealing_keys {
|
||||
dealings.remove(storage, &key);
|
||||
}
|
||||
}
|
||||
current_dealers().remove(storage, &dealer_addr)?;
|
||||
past_dealers().save(storage, &dealer_addr, &details)?;
|
||||
@@ -46,15 +53,9 @@ fn dealers_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 verified_dealers = verified_dealers(deps.storage)?;
|
||||
let all_dealers = verified_dealers.len();
|
||||
let dealers_still_active = dealers_still_active(&deps.as_ref(), verified_dealers.into_iter())?;
|
||||
let group_members = STATE
|
||||
.load(deps.storage)?
|
||||
.group_addr
|
||||
@@ -66,7 +67,11 @@ fn dealers_eq_members(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
|
||||
|
||||
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_dealers = verified_dealers(deps.storage)?;
|
||||
if initial_dealers.is_empty() {
|
||||
// possibly failed DKG, just reset and start again
|
||||
return Ok(true);
|
||||
}
|
||||
let initial_dealer_count = initial_dealers.len();
|
||||
let replacement_threshold = threshold - (initial_dealers.len() + 2 - 1) / 2 + 1;
|
||||
let removed_dealer_count =
|
||||
@@ -90,24 +95,23 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
|
||||
let next_epoch = if let Some(state) = current_epoch.state.next() {
|
||||
// We are during DKG process
|
||||
let mut new_state = state;
|
||||
if let EpochState::DealingExchange { resharing } = state {
|
||||
if let EpochState::DealingExchange { .. } = state {
|
||||
let current_dealers = current_dealers()
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.collect::<Result<Vec<Addr>, _>>()?;
|
||||
if current_dealers.is_empty() {
|
||||
// If no dealer registered yet, we just stay in the same state until there's at least one
|
||||
let group_members =
|
||||
STATE
|
||||
.load(deps.storage)?
|
||||
.group_addr
|
||||
.list_members(&deps.querier, None, None)?;
|
||||
if current_dealers.len() < group_members.len() {
|
||||
// If not all group members registered yet, we just stay in the same state until
|
||||
// they either register or they get kicked out of the group
|
||||
new_state = current_epoch.state;
|
||||
} else {
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
|
||||
THRESHOLD.save(deps.storage, &threshold)?;
|
||||
if !resharing {
|
||||
let replacement_data = InitialReplacementData {
|
||||
initial_dealers: current_dealers,
|
||||
initial_height: None,
|
||||
};
|
||||
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
|
||||
}
|
||||
}
|
||||
};
|
||||
Epoch::new(
|
||||
@@ -129,13 +133,23 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
|
||||
// Dealer set changed, we need to redo DKG...
|
||||
let state = if replacement_threshold_surpassed(&deps)? {
|
||||
// ... in reset mode
|
||||
INITIAL_REPLACEMENT_DATA.remove(deps.storage);
|
||||
EpochState::default()
|
||||
} else {
|
||||
// ... in reshare mode
|
||||
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
|
||||
data.initial_height = Some(env.block.height);
|
||||
Ok(data)
|
||||
})?;
|
||||
if INITIAL_REPLACEMENT_DATA.may_load(deps.storage)?.is_some() {
|
||||
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
|
||||
data.initial_height = env.block.height;
|
||||
Ok(data)
|
||||
})?;
|
||||
} else {
|
||||
let replacement_data = InitialReplacementData {
|
||||
initial_dealers: verified_dealers(deps.storage)?,
|
||||
initial_height: env.block.height,
|
||||
};
|
||||
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
|
||||
}
|
||||
|
||||
EpochState::PublicKeySubmission { resharing: true }
|
||||
};
|
||||
reset_epoch_state(deps.storage)?;
|
||||
@@ -158,10 +172,8 @@ pub(crate) fn try_surpassed_threshold(
|
||||
check_epoch_state(deps.storage, EpochState::InProgress)?;
|
||||
|
||||
let threshold = THRESHOLD.load(deps.storage)?;
|
||||
let dealers = current_dealers()
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.flatten();
|
||||
if dealers_still_active(&deps.as_ref(), dealers)? < threshold as usize {
|
||||
let dealers = verified_dealers(deps.storage)?;
|
||||
if dealers_still_active(&deps.as_ref(), dealers.into_iter())? < threshold as usize {
|
||||
reset_epoch_state(deps.storage)?;
|
||||
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
|
||||
Ok(Epoch::new(
|
||||
@@ -180,8 +192,9 @@ pub(crate) fn try_surpassed_threshold(
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::error::ContractError::EarlyEpochStateAdvancement;
|
||||
use crate::support::tests::fixtures::dealer_details_fixture;
|
||||
use crate::support::tests::fixtures::{dealer_details_fixture, vk_share_fixture};
|
||||
use crate::support::tests::helpers::{init_contract, GROUP_MEMBERS};
|
||||
use crate::verification_key_shares::storage::vk_shares;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::Addr;
|
||||
use cw4::Member;
|
||||
@@ -204,11 +217,15 @@ pub(crate) mod tests {
|
||||
|
||||
for n in [10, 25, 50, 100] {
|
||||
let dealers: Vec<_> = (0..n).map(dealer_details_fixture).collect();
|
||||
let shares: Vec<_> = (0..n).map(|idx| vk_share_fixture(&format!("owner{}", idx), 0)).collect();
|
||||
let initial_dealers = dealers.iter().map(|d| d.address.clone()).collect();
|
||||
let data = InitialReplacementData {
|
||||
initial_dealers,
|
||||
initial_height: None,
|
||||
initial_height: 1,
|
||||
};
|
||||
for share in shares {
|
||||
vk_shares().save(deps.as_mut().storage, (&share.owner, 0), &share).unwrap();
|
||||
}
|
||||
for f in [two_thirds, three_fourths, ninty_pc] {
|
||||
let threshold = f(n);
|
||||
THRESHOLD.save(deps.as_mut().storage, &threshold).unwrap();
|
||||
@@ -247,39 +264,39 @@ pub(crate) mod tests {
|
||||
|
||||
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)
|
||||
let share = vk_share_fixture("owner2", 0);
|
||||
let different_share = vk_share_fixture("owner4", 0);
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), &share)
|
||||
.unwrap();
|
||||
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
current_dealers()
|
||||
.remove(deps.as_mut().storage, &details.address)
|
||||
vk_shares()
|
||||
.remove(deps.as_mut().storage, (&share.owner, 0))
|
||||
.unwrap();
|
||||
GROUP_MEMBERS.lock().unwrap().push((
|
||||
Member {
|
||||
addr: "owner1".to_string(),
|
||||
addr: "owner2".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
current_dealers()
|
||||
vk_shares()
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
&different_details.address,
|
||||
&different_details,
|
||||
(&different_share.owner, 0),
|
||||
&different_share,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
|
||||
current_dealers()
|
||||
.remove(deps.as_mut().storage, &different_details.address)
|
||||
vk_shares()
|
||||
.remove(deps.as_mut().storage, (&different_share.owner, 0))
|
||||
.unwrap();
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details.address, &details)
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), &share)
|
||||
.unwrap();
|
||||
assert!(dealers_eq_members(&deps.as_mut()).unwrap());
|
||||
}
|
||||
@@ -407,6 +424,12 @@ pub(crate) mod tests {
|
||||
);
|
||||
|
||||
// setup dealer details
|
||||
let all_shares: [_; 4] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
|
||||
for share in all_shares.iter() {
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), share)
|
||||
.unwrap();
|
||||
}
|
||||
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
|
||||
for details in all_details.iter() {
|
||||
current_dealers()
|
||||
@@ -431,12 +454,6 @@ pub(crate) mod tests {
|
||||
.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
|
||||
@@ -588,8 +605,14 @@ pub(crate) mod tests {
|
||||
);
|
||||
assert_eq!(curr_epoch, expected_epoch);
|
||||
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
|
||||
let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap();
|
||||
let expected_replacement_data = InitialReplacementData {
|
||||
initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(),
|
||||
initial_height: 12345,
|
||||
};
|
||||
assert_eq!(replacement_data, expected_replacement_data);
|
||||
|
||||
let all_details: [_; 2] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2));
|
||||
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2));
|
||||
for details in all_details.iter() {
|
||||
past_dealers().remove(deps.as_mut().storage, &details.address).unwrap();
|
||||
current_dealers()
|
||||
@@ -607,6 +630,17 @@ pub(crate) mod tests {
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
}
|
||||
|
||||
let all_shares: [_; 4] = std::array::from_fn(|i| {
|
||||
let mut share = vk_share_fixture(&format!("owner{}", i + 1), 1);
|
||||
share.verified = i % 2 == 0;
|
||||
share
|
||||
});
|
||||
for share in all_shares.iter() {
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), share)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Group changed even more, surpassing threshold, so re-run dkg in reset mode
|
||||
*GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = (
|
||||
Member {
|
||||
@@ -623,7 +657,7 @@ 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::PublicKeySubmission { resharing: false },
|
||||
EpochState::PublicKeySubmission { resharing: true },
|
||||
prev_epoch.epoch_id + 1,
|
||||
prev_epoch.time_configuration,
|
||||
env.block.time,
|
||||
@@ -672,12 +706,25 @@ pub(crate) mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
|
||||
for share in all_shares.iter() {
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, 0), share)
|
||||
.unwrap();
|
||||
}
|
||||
let all_details: [_; 3] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
|
||||
for details in all_details.iter() {
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details.address, details)
|
||||
.unwrap();
|
||||
}
|
||||
let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
|
||||
for share in all_shares.iter() {
|
||||
vk_shares()
|
||||
.save(deps.as_mut().storage, (&share.owner, share.epoch_id), share)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for times in [
|
||||
time_configuration.public_key_submission_time_secs,
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE};
|
||||
use cosmwasm_std::Addr;
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{Addr, Order, Storage};
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_coconut_dkg_common::verification_key::ContractVKShare;
|
||||
@@ -35,3 +37,21 @@ pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare,
|
||||
};
|
||||
IndexedMap::new(VK_SHARES_PK_NAMESPACE, indexes)
|
||||
}
|
||||
|
||||
pub(crate) fn verified_dealers(storage: &dyn Storage) -> Result<Vec<Addr>, ContractError> {
|
||||
let epoch_id = CURRENT_EPOCH.load(storage)?.epoch_id;
|
||||
Ok(vk_shares()
|
||||
.idx
|
||||
.epoch_id
|
||||
.prefix(epoch_id)
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.flatten()
|
||||
.filter_map(|(_, share)| {
|
||||
if share.verified {
|
||||
Some(share.owner)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -84,6 +84,18 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
})
|
||||
}
|
||||
|
||||
async fn dump_persistent_state(&self) {
|
||||
if !self.state.coconut_keypair_is_some().await {
|
||||
// Delete the files just in case the process is killed before the new keys are generated
|
||||
std::fs::remove_file(&self.secret_key_path).ok();
|
||||
std::fs::remove_file(&self.verification_key_path).ok();
|
||||
}
|
||||
let persistent_state = PersistentState::from(&self.state);
|
||||
if let Err(err) = persistent_state.save_to_file(self.state.persistent_state_path()) {
|
||||
warn!("Could not backup the state for this iteration: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_epoch_state(&mut self) {
|
||||
match self.dkg_client.get_current_epoch().await {
|
||||
Err(err) => warn!("Could not get current epoch state {err}"),
|
||||
@@ -99,57 +111,64 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
return;
|
||||
}
|
||||
if let Err(err) = self.state.is_consistent(epoch.state).await {
|
||||
error!("Epoch state is corrupted - {err}, the process should be terminated");
|
||||
return;
|
||||
}
|
||||
let ret = match epoch.state {
|
||||
EpochState::PublicKeySubmission { resharing } => {
|
||||
public_key_submission(&self.dkg_client, &mut self.state, resharing).await
|
||||
}
|
||||
EpochState::DealingExchange { resharing } => {
|
||||
dealing_exchange(
|
||||
&self.dkg_client,
|
||||
&mut self.state,
|
||||
self.rng.clone(),
|
||||
resharing,
|
||||
)
|
||||
.await
|
||||
}
|
||||
EpochState::VerificationKeySubmission { resharing } => {
|
||||
let keypair_path = nym_pemstore::KeyPairPath::new(
|
||||
self.secret_key_path.clone(),
|
||||
self.verification_key_path.clone(),
|
||||
);
|
||||
verification_key_submission(
|
||||
&self.dkg_client,
|
||||
&mut self.state,
|
||||
&keypair_path,
|
||||
resharing,
|
||||
)
|
||||
.await
|
||||
}
|
||||
EpochState::VerificationKeyValidation { resharing } => {
|
||||
verification_key_validation(&self.dkg_client, &mut self.state, resharing)
|
||||
debug!("Epoch state is corrupted - {err}. Awaiting for a DKG restart.");
|
||||
} else {
|
||||
let ret = match epoch.state {
|
||||
EpochState::PublicKeySubmission { resharing } => {
|
||||
public_key_submission(&self.dkg_client, &mut self.state, resharing)
|
||||
.await
|
||||
}
|
||||
EpochState::DealingExchange { resharing } => {
|
||||
dealing_exchange(
|
||||
&self.dkg_client,
|
||||
&mut self.state,
|
||||
self.rng.clone(),
|
||||
resharing,
|
||||
)
|
||||
.await
|
||||
}
|
||||
EpochState::VerificationKeyFinalization { resharing } => {
|
||||
verification_key_finalization(&self.dkg_client, &mut self.state, resharing)
|
||||
}
|
||||
EpochState::VerificationKeySubmission { resharing } => {
|
||||
let keypair_path = nym_pemstore::KeyPairPath::new(
|
||||
self.secret_key_path.clone(),
|
||||
self.verification_key_path.clone(),
|
||||
);
|
||||
verification_key_submission(
|
||||
&self.dkg_client,
|
||||
&mut self.state,
|
||||
&keypair_path,
|
||||
resharing,
|
||||
)
|
||||
.await
|
||||
}
|
||||
// Just wait, in case we need to redo dkg at some point
|
||||
EpochState::InProgress => {
|
||||
self.state.set_was_in_progress();
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
if let Err(err) = ret {
|
||||
warn!("Could not handle this iteration for the epoch state: {err}");
|
||||
} else if epoch.state != EpochState::InProgress {
|
||||
let persistent_state = PersistentState::from(&self.state);
|
||||
if let Err(err) =
|
||||
persistent_state.save_to_file(self.state.persistent_state_path())
|
||||
{
|
||||
warn!("Could not backup the state for this iteration: {err}");
|
||||
}
|
||||
EpochState::VerificationKeyValidation { resharing } => {
|
||||
verification_key_validation(
|
||||
&self.dkg_client,
|
||||
&mut self.state,
|
||||
resharing,
|
||||
)
|
||||
.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 => {
|
||||
self.state.set_was_in_progress();
|
||||
// We're dumping state here so that we don't do it uselessly during the
|
||||
// long InProgress state
|
||||
self.dump_persistent_state().await;
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
if let Err(err) = ret {
|
||||
warn!("Could not handle this iteration for the epoch state: {err}");
|
||||
} else if epoch.state != EpochState::InProgress {
|
||||
self.dump_persistent_state().await;
|
||||
}
|
||||
}
|
||||
if let Ok(current_timestamp) =
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::state::{ConsistentState, State};
|
||||
use crate::coconut::error::CoconutError;
|
||||
use log::debug;
|
||||
use nym_coconut_dkg_common::types::TOTAL_DEALINGS;
|
||||
use nym_contracts_common::dealings::ContractSafeBytes;
|
||||
use nym_dkg::bte::setup;
|
||||
@@ -18,6 +19,7 @@ pub(crate) async fn dealing_exchange(
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.receiver_index().is_some() {
|
||||
debug!("Receiver index was set previously, nothing to do");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -45,6 +47,7 @@ pub(crate) async fn dealing_exchange(
|
||||
return Err(CoconutError::CorruptedCoconutKeyPair);
|
||||
}
|
||||
// We can now erase the keypair from memory
|
||||
debug!("Removing coconut keypair from memory");
|
||||
state.set_coconut_keypair(None).await;
|
||||
scalars.push(x);
|
||||
scalars
|
||||
@@ -59,6 +62,11 @@ pub(crate) async fn dealing_exchange(
|
||||
if !resharing || initial_dealers.iter().any(|d| *d == own_address) {
|
||||
let params = setup();
|
||||
for _ in 0..TOTAL_DEALINGS {
|
||||
debug!(
|
||||
"Submitting dealing for indexes {:?} with resharing: {}",
|
||||
receivers.keys().collect::<Vec<_>>(),
|
||||
prior_resharing_secrets.front().is_some()
|
||||
);
|
||||
let (dealing, _) = Dealing::create(
|
||||
rng.clone(),
|
||||
¶ms,
|
||||
@@ -71,9 +79,11 @@ pub(crate) async fn dealing_exchange(
|
||||
.submit_dealing(ContractSafeBytes::from(&dealing), resharing)
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
debug!("Nothing to do, waiting for initial dealers to submit dealings");
|
||||
}
|
||||
|
||||
info!("DKG: Finished submitting dealing");
|
||||
info!("DKG: Finished dealing exchange");
|
||||
state.set_receiver_index(receiver_index);
|
||||
|
||||
Ok(())
|
||||
@@ -109,7 +119,7 @@ pub(crate) mod tests {
|
||||
|
||||
fn insert_dealers(
|
||||
params: &Params,
|
||||
dealer_details_db: &Arc<RwLock<HashMap<String, DealerDetails>>>,
|
||||
dealer_details_db: &Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
|
||||
) -> Vec<DkgKeyPair> {
|
||||
let mut keypairs = vec![];
|
||||
for (idx, addr) in TEST_VALIDATORS_ADDRESS.iter().enumerate() {
|
||||
@@ -119,12 +129,15 @@ pub(crate) mod tests {
|
||||
keypairs.push(keypair);
|
||||
dealer_details_db.write().unwrap().insert(
|
||||
addr.to_string(),
|
||||
DealerDetails {
|
||||
address: Addr::unchecked(*addr),
|
||||
bte_public_key_with_proof,
|
||||
announce_address: format!("localhost:80{}", idx),
|
||||
assigned_index: (idx + 1) as u64,
|
||||
},
|
||||
(
|
||||
DealerDetails {
|
||||
address: Addr::unchecked(*addr),
|
||||
bte_public_key_with_proof,
|
||||
announce_address: format!("localhost:80{}", idx),
|
||||
assigned_index: (idx + 1) as u64,
|
||||
},
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
keypairs
|
||||
@@ -216,7 +229,7 @@ pub(crate) mod tests {
|
||||
.unwrap()
|
||||
.entry(TEST_VALIDATORS_ADDRESS[1].to_string())
|
||||
.and_modify(|details| {
|
||||
let mut bytes = bs58::decode(details.bte_public_key_with_proof.clone())
|
||||
let mut bytes = bs58::decode(details.0.bte_public_key_with_proof.clone())
|
||||
.into_vec()
|
||||
.unwrap();
|
||||
// Find another value for last byte that still deserializes to a public key with proof
|
||||
@@ -231,7 +244,7 @@ pub(crate) mod tests {
|
||||
break;
|
||||
}
|
||||
}
|
||||
details.bte_public_key_with_proof = bs58::encode(&bytes).into_string();
|
||||
details.0.bte_public_key_with_proof = bs58::encode(&bytes).into_string();
|
||||
});
|
||||
|
||||
dealing_exchange(&dkg_client, &mut state, OsRng, false)
|
||||
@@ -257,7 +270,7 @@ pub(crate) mod tests {
|
||||
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),
|
||||
initial_height: 100,
|
||||
})));
|
||||
let dkg_client = DkgClient::new(
|
||||
DummyClient::new(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::state::State;
|
||||
use crate::coconut::error::CoconutError;
|
||||
use log::debug;
|
||||
use nym_coconut_dkg_common::dealer::DealerType;
|
||||
|
||||
pub(crate) async fn public_key_submission(
|
||||
@@ -12,9 +13,21 @@ pub(crate) async fn public_key_submission(
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.was_in_progress() {
|
||||
state.reset_persistent(resharing).await;
|
||||
let own_address = dkg_client.get_address().await.as_ref().to_string();
|
||||
let is_initial_dealer = dkg_client
|
||||
.get_initial_dealers()
|
||||
.await?
|
||||
.map(|data| data.initial_dealers.iter().any(|d| *d == own_address))
|
||||
.unwrap_or(false);
|
||||
let reset_coconut_keypair = !resharing || !is_initial_dealer;
|
||||
debug!(
|
||||
"Resetting state, with coconut keypair reset: {}",
|
||||
reset_coconut_keypair
|
||||
);
|
||||
state.reset_persistent(reset_coconut_keypair).await;
|
||||
}
|
||||
if state.node_index().is_some() {
|
||||
debug!("Node index was set previously, nothing to do");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -23,12 +36,14 @@ pub(crate) async fn public_key_submission(
|
||||
let index = if let Some(details) = dealer_details.details {
|
||||
if dealer_details.dealer_type == DealerType::Past {
|
||||
// If it was a dealer in a previous epoch, re-register it for this epoch
|
||||
debug!("Registering for the current DKG round, with keys from a previous epoch");
|
||||
dkg_client
|
||||
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
|
||||
.await?;
|
||||
}
|
||||
details.assigned_index
|
||||
} else {
|
||||
debug!("Registering for the first time to be a dealer");
|
||||
// First time registration
|
||||
dkg_client
|
||||
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::coconut::dkg::complaints::ComplaintReason;
|
||||
use crate::coconut::error::CoconutError;
|
||||
use crate::coconut::keypair::KeyPair as CoconutKeyPair;
|
||||
use cosmwasm_std::Addr;
|
||||
use log::debug;
|
||||
use nym_coconut_dkg_common::dealer::DealerDetails;
|
||||
use nym_coconut_dkg_common::types::EpochState;
|
||||
use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof};
|
||||
@@ -133,7 +134,7 @@ impl ConsistentState for State {
|
||||
|
||||
fn proposal_id_value(&self) -> Result<u64, CoconutError> {
|
||||
self.proposal_id.ok_or(CoconutError::UnrecoverableState {
|
||||
reason: String::from("Proposal id should have benn set"),
|
||||
reason: String::from("Proposal id should have been set"),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -242,8 +243,8 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reset_persistent(&mut self, resharing: bool) {
|
||||
if !resharing {
|
||||
pub async fn reset_persistent(&mut self, reset_coconut_keypair: bool) {
|
||||
if reset_coconut_keypair {
|
||||
self.coconut_keypair.set(None).await;
|
||||
}
|
||||
self.node_index = Default::default();
|
||||
@@ -360,6 +361,10 @@ impl State {
|
||||
.iter_mut()
|
||||
.find(|(addr, _)| *addr == dealer_addr)
|
||||
{
|
||||
debug!(
|
||||
"Dealer {} misbehaved: {:?}. It will be marked locally as bad dealer and ignored",
|
||||
dealer_addr, reason
|
||||
);
|
||||
*value = Err(reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::coconut::error::CoconutError;
|
||||
use crate::coconut::helpers::accepted_vote_err;
|
||||
use cosmwasm_std::Addr;
|
||||
use cw3::{ProposalResponse, Status};
|
||||
use log::debug;
|
||||
use nym_coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID;
|
||||
use nym_coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS};
|
||||
use nym_coconut_dkg_common::verification_key::owner_from_cosmos_msgs;
|
||||
@@ -116,6 +117,11 @@ fn derive_partial_keypair(
|
||||
}
|
||||
})
|
||||
.unzip();
|
||||
debug!(
|
||||
"Recovering verification keys from dealings of dealers {:?} with receivers {:?}",
|
||||
filtered_dealers,
|
||||
filtered_receivers_by_idx.keys().collect::<Vec<_>>()
|
||||
);
|
||||
let recovered = try_recover_verification_keys(
|
||||
&filtered_dealings,
|
||||
threshold,
|
||||
@@ -123,10 +129,12 @@ fn derive_partial_keypair(
|
||||
)?;
|
||||
recovered_vks.push(recovered);
|
||||
|
||||
debug!("Decrypting shares");
|
||||
let shares = filtered_dealings
|
||||
.iter()
|
||||
.map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None))
|
||||
.collect::<Result<_, _>>()?;
|
||||
debug!("Combining shares into one secret");
|
||||
let scalar = combine_shares(shares, &filtered_dealers)?;
|
||||
scalars.push(scalar);
|
||||
}
|
||||
@@ -152,13 +160,19 @@ pub(crate) async fn verification_key_submission(
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.coconut_keypair_is_some().await {
|
||||
debug!("Coconut keypair was set previously, nothing to do");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let threshold = state.threshold()?;
|
||||
let dealings_maps =
|
||||
deterministic_filter_dealers(dkg_client, state, threshold, resharing).await?;
|
||||
debug!(
|
||||
"Filtered dealers to {:?}",
|
||||
dealings_maps[0].keys().collect::<Vec<_>>()
|
||||
);
|
||||
let coconut_keypair = derive_partial_keypair(state, threshold, dealings_maps)?;
|
||||
debug!("Derived own coconut keypair");
|
||||
let vk_share = coconut_keypair.verification_key().to_bs58();
|
||||
nym_pemstore::store_keypair(&coconut_keypair, keypair_path)?;
|
||||
let res = dkg_client
|
||||
@@ -173,6 +187,10 @@ pub(crate) async fn verification_key_submission(
|
||||
.map_err(|_| CoconutError::ProposalIdError {
|
||||
reason: String::from("proposal id could not be parsed to u64"),
|
||||
})?;
|
||||
debug!(
|
||||
"Submitted own verification key share, proposal id {} is attached to it",
|
||||
proposal_id
|
||||
);
|
||||
state.set_proposal_id(proposal_id);
|
||||
state.set_coconut_keypair(Some(coconut_keypair)).await;
|
||||
info!("DKG: Submitted own verification key");
|
||||
@@ -195,6 +213,7 @@ pub(crate) async fn verification_key_validation(
|
||||
_resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.voted_vks() {
|
||||
debug!("Already voted on the verification keys, nothing to do");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -225,10 +244,15 @@ pub(crate) async fn verification_key_validation(
|
||||
.position(|node_index| contract_share.node_index == *node_index)
|
||||
{
|
||||
let ret = if !check_vk_pairing(¶ms, &recovered_partials[idx], &vk) {
|
||||
debug!(
|
||||
"Voting NO to proposal {} because of failed VK pairing",
|
||||
proposal_id
|
||||
);
|
||||
dkg_client
|
||||
.vote_verification_key_share(proposal_id, false)
|
||||
.await
|
||||
} else {
|
||||
debug!("Voting YES to proposal {}", proposal_id);
|
||||
dkg_client
|
||||
.vote_verification_key_share(proposal_id, true)
|
||||
.await
|
||||
@@ -237,6 +261,10 @@ pub(crate) async fn verification_key_validation(
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
debug!(
|
||||
"Voting NO to proposal {} because of failed base 58 deserialization",
|
||||
proposal_id
|
||||
);
|
||||
let ret = dkg_client
|
||||
.vote_verification_key_share(proposal_id, false)
|
||||
.await;
|
||||
@@ -256,6 +284,7 @@ pub(crate) async fn verification_key_finalization(
|
||||
_resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.executed_proposal() {
|
||||
debug!("Already executed the proposal, nothing to do");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -294,7 +323,7 @@ pub(crate) mod tests {
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
struct MockContractDb {
|
||||
dealer_details_db: Arc<RwLock<HashMap<String, DealerDetails>>>,
|
||||
dealer_details_db: Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
|
||||
dealings_db: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
|
||||
proposal_db: Arc<RwLock<HashMap<u64, ProposalResponse>>>,
|
||||
verification_share_db: Arc<RwLock<HashMap<String, ContractVKShare>>>,
|
||||
@@ -315,10 +344,11 @@ pub(crate) mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_VALIDATORS_ADDRESS: [&str; 3] = [
|
||||
const TEST_VALIDATORS_ADDRESS: [&str; 4] = [
|
||||
"n1aq9kakfgwqcufr23lsv644apavcntrsqsk4yus",
|
||||
"n1s9l3xr4g0rglvk4yctktmck3h4eq0gp6z2e20v",
|
||||
"n19kl4py32vsk297dm93ezem992cdyzdy4zuc2x6",
|
||||
"n1jfrs6cmw9t7dv0x8cgny6geunzjh56n2s89fkv",
|
||||
];
|
||||
|
||||
async fn prepare_clients_and_states(db: &MockContractDb) -> Vec<(DkgClient, State)> {
|
||||
@@ -418,7 +448,7 @@ pub(crate) mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), TOTAL_DEALINGS);
|
||||
for mapping in filtered.iter() {
|
||||
assert_eq!(mapping.len(), 3);
|
||||
assert_eq!(mapping.len(), 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,7 +501,7 @@ pub(crate) mod tests {
|
||||
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,
|
||||
initial_height: 1,
|
||||
});
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
|
||||
.await
|
||||
@@ -504,7 +534,7 @@ pub(crate) mod tests {
|
||||
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,
|
||||
initial_height: 1,
|
||||
});
|
||||
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
|
||||
.await
|
||||
@@ -542,7 +572,7 @@ pub(crate) mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(filtered.len(), TOTAL_DEALINGS);
|
||||
for mapping in filtered.iter() {
|
||||
assert_eq!(mapping.len(), 2);
|
||||
assert_eq!(mapping.len(), 3);
|
||||
}
|
||||
let corrupted_status = state
|
||||
.all_dealers()
|
||||
@@ -803,6 +833,9 @@ pub(crate) mod tests {
|
||||
async fn reshare_preserves_keys() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
|
||||
for (_, state) in clients_and_states.iter_mut() {
|
||||
state.set_was_in_progress();
|
||||
}
|
||||
let params = Parameters::new(4).unwrap();
|
||||
|
||||
let mut vks = vec![];
|
||||
@@ -839,23 +872,22 @@ pub(crate) mod tests {
|
||||
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());
|
||||
for (_, active) in db.dealer_details_db.write().unwrap().values_mut() {
|
||||
*active = false;
|
||||
}
|
||||
|
||||
*db.dealings_db.write().unwrap() = Default::default();
|
||||
*db.verification_share_db.write().unwrap() = Default::default();
|
||||
let mut initial_dealers = vec![];
|
||||
for (dkg_client, _) in clients_and_states.iter() {
|
||||
let client_address = Addr::unchecked(dkg_client.get_address().await.as_ref());
|
||||
initial_dealers.push(client_address);
|
||||
}
|
||||
*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,
|
||||
initial_dealers,
|
||||
initial_height: 1,
|
||||
});
|
||||
*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)
|
||||
@@ -880,6 +912,214 @@ pub(crate) mod tests {
|
||||
std::fs::remove_file(private_key_path).unwrap();
|
||||
std::fs::remove_file(public_key_path).unwrap();
|
||||
}
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_validation(dkg_client, state, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_finalization(dkg_client, state, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
assert!(db
|
||||
.proposal_db
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.all(|proposal| { proposal.status == Status::Executed }));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn reshare_after_reset() {
|
||||
let db = MockContractDb::new();
|
||||
let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
|
||||
for (_, state) in clients_and_states.iter_mut() {
|
||||
state.set_was_in_progress();
|
||||
}
|
||||
|
||||
let new_dkg_client = DkgClient::new(
|
||||
DummyClient::new(
|
||||
AccountId::from_str("n1vxkywf9g4cg0k2dehanzwzz64jw782qm0kuynf").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 new_dkg_client2 = 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 state2 = State::new(
|
||||
PathBuf::default(),
|
||||
PersistentState::default(),
|
||||
Url::parse("localhost:8000").unwrap(),
|
||||
keypair,
|
||||
KeyPair::new(),
|
||||
);
|
||||
|
||||
for (_, active) in db.dealer_details_db.write().unwrap().values_mut() {
|
||||
*active = false;
|
||||
}
|
||||
|
||||
*db.dealings_db.write().unwrap() = Default::default();
|
||||
*db.verification_share_db.write().unwrap() = Default::default();
|
||||
clients_and_states.pop().unwrap();
|
||||
let (initial_client2, initial_state2) = clients_and_states.pop().unwrap();
|
||||
clients_and_states.push((new_dkg_client, state));
|
||||
clients_and_states.push((new_dkg_client2, state2));
|
||||
|
||||
// DKG in reset mode
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
public_key_submission(dkg_client, state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
dealing_exchange(dkg_client, state, OsRng, false)
|
||||
.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, false)
|
||||
.await
|
||||
.unwrap();
|
||||
std::fs::remove_file(private_key_path).unwrap();
|
||||
std::fs::remove_file(public_key_path).unwrap();
|
||||
}
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_validation(dkg_client, state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_finalization(dkg_client, state, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
assert!(db
|
||||
.proposal_db
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.all(|proposal| { proposal.status == Status::Executed }));
|
||||
for (_, state) in clients_and_states.iter_mut() {
|
||||
state.set_was_in_progress();
|
||||
}
|
||||
|
||||
// DKG in reshare mode
|
||||
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();
|
||||
|
||||
for (_, active) in db.dealer_details_db.write().unwrap().values_mut() {
|
||||
*active = false;
|
||||
}
|
||||
*db.dealings_db.write().unwrap() = Default::default();
|
||||
*db.verification_share_db.write().unwrap() = Default::default();
|
||||
let mut initial_dealers = vec![];
|
||||
for (dkg_client, _) in clients_and_states.iter() {
|
||||
let client_address = Addr::unchecked(dkg_client.get_address().await.as_ref());
|
||||
initial_dealers.push(client_address);
|
||||
}
|
||||
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
|
||||
initial_dealers,
|
||||
initial_height: 1,
|
||||
});
|
||||
*clients_and_states.last_mut().unwrap() = (initial_client2, initial_state2);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_validation(dkg_client, state, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
for (dkg_client, state) in clients_and_states.iter_mut() {
|
||||
verification_key_finalization(dkg_client, state, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
// assert!(db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .values()
|
||||
// .all(|proposal| { proposal.status == Status::Executed }));
|
||||
|
||||
let mut vks = vec![];
|
||||
let mut indices = vec![];
|
||||
for (_, state) in clients_and_states.iter() {
|
||||
|
||||
@@ -91,7 +91,7 @@ pub enum CoconutError {
|
||||
#[error("Failed to recover assigned node index: {reason}")]
|
||||
NodeIndexRecoveryError { reason: String },
|
||||
|
||||
#[error("Unrecoverable state: {reason}. Process should be restarted")]
|
||||
#[error("Unrecoverable state: {reason}")]
|
||||
UnrecoverableState { reason: String },
|
||||
|
||||
#[error("DKG has not finished yet in order to derive the coconut key")]
|
||||
|
||||
@@ -69,7 +69,7 @@ pub(crate) struct DummyClient {
|
||||
spent_credential_db: Arc<RwLock<HashMap<String, SpendCredentialResponse>>>,
|
||||
|
||||
epoch: Arc<RwLock<Epoch>>,
|
||||
dealer_details: Arc<RwLock<HashMap<String, DealerDetails>>>,
|
||||
dealer_details: Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
|
||||
threshold: Arc<RwLock<Option<Threshold>>>,
|
||||
dealings: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
|
||||
verification_share: Arc<RwLock<HashMap<String, ContractVKShare>>>,
|
||||
@@ -122,7 +122,7 @@ impl DummyClient {
|
||||
|
||||
pub fn with_dealer_details(
|
||||
mut self,
|
||||
dealer_details: &Arc<RwLock<HashMap<String, DealerDetails>>>,
|
||||
dealer_details: &Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
|
||||
) -> Self {
|
||||
self.dealer_details = Arc::clone(dealer_details);
|
||||
self
|
||||
@@ -233,14 +233,25 @@ impl super::client::Client for DummyClient {
|
||||
}
|
||||
|
||||
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
|
||||
let (details, dealer_type) = if let Some((details, current)) = self
|
||||
.dealer_details
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(self.validator_address.as_ref())
|
||||
.cloned()
|
||||
{
|
||||
let dealer_type = if current {
|
||||
DealerType::Current
|
||||
} else {
|
||||
DealerType::Past
|
||||
};
|
||||
(Some(details), dealer_type)
|
||||
} else {
|
||||
(None, DealerType::Unknown)
|
||||
};
|
||||
Ok(DealerDetailsResponse {
|
||||
details: self
|
||||
.dealer_details
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(self.validator_address.as_ref())
|
||||
.cloned(),
|
||||
dealer_type: DealerType::Current,
|
||||
details,
|
||||
dealer_type,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -251,6 +262,7 @@ impl super::client::Client for DummyClient {
|
||||
.unwrap()
|
||||
.values()
|
||||
.cloned()
|
||||
.filter_map(|(d, current)| if current { Some(d) } else { None })
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -287,13 +299,11 @@ impl super::client::Client for DummyClient {
|
||||
_fee: Option<Fee>,
|
||||
) -> Result<()> {
|
||||
if let Some(proposal) = self.proposal_db.write().unwrap().get_mut(&proposal_id) {
|
||||
// for now, just suppose that first vote is honest
|
||||
if proposal.status == cw3::Status::Open {
|
||||
if vote_yes {
|
||||
proposal.status = cw3::Status::Passed;
|
||||
} else {
|
||||
proposal.status = cw3::Status::Rejected;
|
||||
}
|
||||
// for now, just suppose that every vote is honest
|
||||
if !vote_yes {
|
||||
proposal.status = cw3::Status::Rejected;
|
||||
} else if vote_yes && proposal.status == cw3::Status::Open {
|
||||
proposal.status = cw3::Status::Passed;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -323,22 +333,33 @@ impl super::client::Client for DummyClient {
|
||||
_resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
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(),
|
||||
let assigned_index = if let Some((details, active)) =
|
||||
dealer_details.get_mut(self.validator_address.as_ref())
|
||||
{
|
||||
*active = true;
|
||||
details.assigned_index
|
||||
} else {
|
||||
// let assigned_index = OsRng.gen();
|
||||
let assigned_index = dealer_details
|
||||
.values()
|
||||
.map(|(d, _)| d.assigned_index)
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
+ 1;
|
||||
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
|
||||
};
|
||||
true,
|
||||
),
|
||||
);
|
||||
assigned_index
|
||||
};
|
||||
Ok(ExecuteResult {
|
||||
logs: vec![Log {
|
||||
msg_index: 0,
|
||||
@@ -380,13 +401,17 @@ impl super::client::Client for DummyClient {
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
let dealer_details = self
|
||||
let (dealer_details, active) = self
|
||||
.dealer_details
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(self.validator_address.as_ref())
|
||||
.unwrap()
|
||||
.clone();
|
||||
if !active {
|
||||
// Just throw some error, not really the correct one
|
||||
return Err(CoconutError::DepositEncrKeyNotFound);
|
||||
}
|
||||
self.verification_share.write().unwrap().insert(
|
||||
self.validator_address.to_string(),
|
||||
ContractVKShare {
|
||||
|
||||
@@ -38,7 +38,6 @@ pub(crate) async fn execute(
|
||||
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::List(args) => {
|
||||
nym_cli_commands::validator::mixnet::delegators::query_for_delegations::execute(args, create_signing_client_with_nym_api(global_args, network_details)?).await
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user