happy path for key validation
This commit is contained in:
@@ -64,7 +64,14 @@ pub fn to_cosmos_msg(
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
// DKG SAFETY:
|
||||
// each legit verification proposal will only contain a single execute msg,
|
||||
// if they have more than one, we can safely ignore it
|
||||
pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<Addr> {
|
||||
if msgs.len() != 1 {
|
||||
return None
|
||||
}
|
||||
|
||||
if let Some(CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: _,
|
||||
msg,
|
||||
|
||||
@@ -228,10 +228,11 @@ pub fn check_vk_pairing(
|
||||
|
||||
// safety: we made an explicit check for if the length of the slice is 0, thus unwrap here is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
if &vk.alpha != *dkg_values.last().as_ref().unwrap() {
|
||||
if &vk.alpha != *dkg_values.first().as_ref().unwrap() {
|
||||
return false;
|
||||
}
|
||||
if dkg_values
|
||||
let dkg_betas = &dkg_values[1..];
|
||||
if dkg_betas
|
||||
.iter()
|
||||
.zip(vk.beta_g2.iter())
|
||||
.any(|(dkg_beta, vk_beta)| dkg_beta != vk_beta)
|
||||
@@ -329,8 +330,9 @@ mod tests {
|
||||
let params = setup(2).unwrap();
|
||||
let keypair = keygen(¶ms);
|
||||
let vk = keypair.verification_key();
|
||||
let mut dkg_values = vk.beta_g2.clone();
|
||||
dkg_values.push(vk.alpha);
|
||||
|
||||
let mut dkg_values = vec![vk.alpha];
|
||||
dkg_values.append(&mut vk.beta_g2.clone());
|
||||
assert!(check_vk_pairing(¶ms, &dkg_values, vk));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ use nym_validator_client::nyxd::{AccountId, Fee, Hash, TxResponse};
|
||||
#[async_trait]
|
||||
pub trait Client {
|
||||
async fn address(&self) -> AccountId;
|
||||
|
||||
async fn dkg_contract_address(&self) -> Result<AccountId>;
|
||||
async fn get_tx(&self, tx_hash: Hash) -> Result<TxResponse>;
|
||||
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse>;
|
||||
async fn list_proposals(&self) -> Result<Vec<ProposalResponse>>;
|
||||
|
||||
@@ -35,6 +35,10 @@ impl DkgClient {
|
||||
self.inner.address().await
|
||||
}
|
||||
|
||||
pub(crate) async fn dkg_contract_address(&self) -> Result<AccountId, CoconutError> {
|
||||
self.inner.dkg_contract_address().await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_current_epoch(&self) -> Result<Epoch, CoconutError> {
|
||||
self.inner.get_current_epoch().await
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::controller::error::DkgError;
|
||||
use crate::coconut::dkg::key_derivation::{
|
||||
verification_key_finalization, verification_key_validation,
|
||||
};
|
||||
use crate::coconut::dkg::key_finalization::verification_key_finalization;
|
||||
use crate::coconut::dkg::state::{ConsistentState, PersistentState, State};
|
||||
use crate::coconut::keys::KeyPair as CoconutKeyPair;
|
||||
use crate::nyxd;
|
||||
@@ -158,17 +156,19 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
|
||||
async fn handle_verification_key_validation(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), DkgError> {
|
||||
debug!("DKG: verification key validation (resharing: {resharing})");
|
||||
|
||||
verification_key_validation(&self.dkg_client, &mut self.state, resharing)
|
||||
self.verification_key_validation(epoch_id, resharing)
|
||||
.await
|
||||
.map_err(|source| DkgError::VerificationKeyValidationFailure { source })
|
||||
}
|
||||
|
||||
async fn handle_verification_key_finalization(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), DkgError> {
|
||||
debug!("DKG: verification key finalization (resharing: {resharing})");
|
||||
@@ -218,10 +218,12 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
.await?
|
||||
}
|
||||
EpochState::VerificationKeyValidation { resharing } => {
|
||||
self.handle_verification_key_validation(resharing).await?
|
||||
self.handle_verification_key_validation(epoch.epoch_id, resharing)
|
||||
.await?
|
||||
}
|
||||
EpochState::VerificationKeyFinalization { resharing } => {
|
||||
self.handle_verification_key_finalization(resharing).await?
|
||||
self.handle_verification_key_finalization(epoch.epoch_id, resharing)
|
||||
.await?
|
||||
}
|
||||
// Just wait, in case we need to redo dkg at some point
|
||||
EpochState::InProgress => self.handle_in_progress().await?,
|
||||
|
||||
@@ -60,16 +60,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
|
||||
// ASSUMPTION: all dealers see the same contract data, i.e. if one fails to decode and verify the receiver's key,
|
||||
// all of them will
|
||||
let filtered_receivers: BTreeMap<_, _> = self
|
||||
.state
|
||||
.dkg_state(epoch_id)?
|
||||
.dealers
|
||||
.iter()
|
||||
.filter_map(|(index, dealer)| match &dealer.state {
|
||||
ParticipantState::Invalid(_) => None,
|
||||
ParticipantState::VerifiedKey(key) => Some((*index, *key.public_key())),
|
||||
})
|
||||
.collect();
|
||||
let filtered_receivers = self.state.valid_epoch_receivers_keys(epoch_id)?;
|
||||
|
||||
let dbg_receivers = filtered_receivers.keys().collect::<Vec<_>>();
|
||||
debug!("generating dealings with threshold {threshold} for receivers: {dbg_receivers:?} with the following spec: {spec:?}. Our index is {dealer_index}");
|
||||
@@ -283,7 +274,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
.expected_threshold = Some(threshold);
|
||||
|
||||
// establish our receiver index
|
||||
let sorted_dealers = &self.state.dkg_state(epoch_id)?.dealers;
|
||||
let sorted_dealers = &self.state.dkg_state(epoch_id)?.dealing_exchange.dealers;
|
||||
let Some(receiver_index) = sorted_dealers.keys().position(|idx| idx == &dealer_index)
|
||||
else {
|
||||
// this branch should be impossible as `dealing_exchange` should never be called unless we're actually a dealer
|
||||
@@ -452,7 +443,11 @@ pub(crate) mod tests {
|
||||
let key_size = controller.dkg_client.get_contract_state().await?.key_size;
|
||||
|
||||
// initial state
|
||||
assert!(controller.state.dkg_state(epoch)?.dealers.is_empty());
|
||||
assert!(controller
|
||||
.state
|
||||
.dealing_exchange_state(epoch)?
|
||||
.dealers
|
||||
.is_empty());
|
||||
assert!(controller
|
||||
.state
|
||||
.dealing_exchange_state(epoch)?
|
||||
@@ -473,7 +468,7 @@ pub(crate) mod tests {
|
||||
.collect::<Vec<_>>();
|
||||
let dealers = controller
|
||||
.state
|
||||
.dkg_state(epoch)?
|
||||
.dealing_exchange_state(epoch)?
|
||||
.dealers
|
||||
.values()
|
||||
.map(|p| (p.assigned_index, p.unwrap_key()))
|
||||
|
||||
@@ -24,192 +24,194 @@ use nym_dkg::bte::{decrypt_share, PublicKey};
|
||||
use nym_dkg::error::DkgError;
|
||||
use nym_dkg::{bte, combine_shares, try_recover_verification_keys, Dealing, Threshold};
|
||||
use nym_pemstore::KeyPairPath;
|
||||
use nym_validator_client::nyxd::bip32::secp256k1::elliptic_curve::group;
|
||||
use nym_validator_client::nyxd::bip32::secp256k1::elliptic_curve::group::GroupEncoding;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::logs::find_attribute;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Filter the dealers based on what dealing they posted (or not) in the contract
|
||||
|
||||
// TODO: change the return type to make sure that:
|
||||
// - each entry has the same number of dealings
|
||||
// - dealer data is not duplicated
|
||||
// - each dealer has submitted all or nothing
|
||||
async fn deterministic_filter_dealers(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
epoch_id: EpochId,
|
||||
threshold: Threshold,
|
||||
resharing: bool,
|
||||
) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> {
|
||||
// if we're in resharing mode, the contract itself will forbid submission of dealings from
|
||||
// parties that were not "initial" dealers, so we don't have to worry about it
|
||||
|
||||
let mut dealings_maps = Vec::new();
|
||||
let initial_dealers_by_addr = state.current_dealers_by_addr();
|
||||
let initial_receivers = state.current_dealers_by_idx();
|
||||
let initial_resharing_dealers = if resharing {
|
||||
dkg_client
|
||||
.get_initial_dealers()
|
||||
.await?
|
||||
.map(|d| d.initial_dealers)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let params = dkg::params();
|
||||
|
||||
// note: this is a temporary solution to replicate the behaviour of the old code so that I wouldn't need to
|
||||
// fix the filtering in this PR, because the old code is quite buggy and misses few edge cases
|
||||
let mut raw_dealings = HashMap::new();
|
||||
for dealer in state.all_dealers().keys() {
|
||||
let dealer_dealings = dkg_client
|
||||
.get_dealings(epoch_id, dealer.to_string())
|
||||
.await?;
|
||||
for dealing in dealer_dealings {
|
||||
let old_contract_dealing = raw_dealings.entry(dealing.index).or_insert(Vec::new());
|
||||
old_contract_dealing.push((dealer.clone(), dealing.data))
|
||||
}
|
||||
}
|
||||
|
||||
// this is a temporary thing to reintroduce the bug to make sure tests still pass : )
|
||||
// i will fix it properly in next PR
|
||||
for dealing_index in 0..5 {
|
||||
let dealings = raw_dealings.remove(&dealing_index).unwrap_or_default();
|
||||
let dealings_map =
|
||||
BTreeMap::from_iter(dealings.into_iter().filter_map(|(dealer, dealing)| {
|
||||
match Dealing::try_from(&dealing) {
|
||||
Ok(dealing) => {
|
||||
if dealing
|
||||
.verify(params, threshold, &initial_receivers, None)
|
||||
.is_err()
|
||||
{
|
||||
state.mark_bad_dealer(
|
||||
&dealer,
|
||||
ComplaintReason::DealingVerificationError,
|
||||
);
|
||||
None
|
||||
} else {
|
||||
initial_dealers_by_addr
|
||||
.get(&dealer)
|
||||
.map(|idx| (*idx, (dealer, dealing)))
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
state.mark_bad_dealer(&dealer, ComplaintReason::MalformedDealing);
|
||||
None
|
||||
}
|
||||
}
|
||||
}));
|
||||
dealings_maps.push(dealings_map);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// for dealer in initial_dealers_by_addr.keys() {
|
||||
// let Some(dealer_index) = initial_dealers_by_addr.get(dealer) else {
|
||||
// warn!("could not obtain dealer index of {dealer}");
|
||||
// continue;
|
||||
// };
|
||||
//
|
||||
// let dealer_dealings = dkg_client
|
||||
// .get_dealings(epoch_id, dealer.to_string())
|
||||
// .await?;
|
||||
//
|
||||
// for contract_dealing in dealer_dealings {
|
||||
// match Dealing::try_from(&contract_dealing.data) {
|
||||
// // FIXME: bug: this doesn't check resharing
|
||||
// Ok(dealing) => {
|
||||
// if let Err(err) = dealing.verify(params, threshold, &initial_receivers, None) {
|
||||
// println!("dealing verification failure from {dealer}: {err}");
|
||||
// state.mark_bad_dealer(dealer, ComplaintReason::DealingVerificationError);
|
||||
// } else {
|
||||
// let entry = dealings_maps
|
||||
// .entry(contract_dealing.index)
|
||||
// .or_insert(BTreeMap::new());
|
||||
// entry.insert(*dealer_index, (dealer.clone(), dealing));
|
||||
// }
|
||||
// }
|
||||
// Err(err) => {
|
||||
// warn!("malformed dealing from {dealer}: {err}");
|
||||
// state.mark_bad_dealer(dealer, ComplaintReason::MalformedDealing);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
for (addr, _) in initial_dealers_by_addr.iter() {
|
||||
// in resharing mode, we don't commit dealings from dealers outside the initial set
|
||||
if !resharing || initial_resharing_dealers.contains(addr) {
|
||||
for dealings_map in dealings_maps.iter() {
|
||||
if !dealings_map.iter().any(|(_, (address, _))| address == addr) {
|
||||
state.mark_bad_dealer(addr, ComplaintReason::MissingDealing);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(dealings_maps)
|
||||
}
|
||||
|
||||
fn derive_partial_keypair(
|
||||
state: &mut State,
|
||||
threshold: Threshold,
|
||||
dealings_maps: Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>,
|
||||
) -> Result<KeyPair, CoconutError> {
|
||||
let filtered_receivers_by_idx = state.current_dealers_by_idx();
|
||||
let filtered_dealers_by_addr = state.current_dealers_by_addr();
|
||||
let dk = state.dkg_keypair().private_key();
|
||||
let node_index_value = state.receiver_index_value()?;
|
||||
let mut scalars = vec![];
|
||||
let mut recovered_vks = vec![];
|
||||
for dealings_map in dealings_maps.into_iter() {
|
||||
let (filtered_dealers, filtered_dealings): (Vec<_>, Vec<_>) = dealings_map
|
||||
.into_iter()
|
||||
.filter_map(|(idx, (addr, dealing))| {
|
||||
if filtered_dealers_by_addr.keys().any(|a| addr == *a) {
|
||||
Some((idx, dealing))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.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,
|
||||
&filtered_receivers_by_idx,
|
||||
)?;
|
||||
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);
|
||||
}
|
||||
state.set_recovered_vks(recovered_vks);
|
||||
|
||||
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(&BANDWIDTH_CREDENTIAL_PARAMS);
|
||||
|
||||
Ok(CoconutKeyPair::from_keys(sk, vk))
|
||||
}
|
||||
// // Filter the dealers based on what dealing they posted (or not) in the contract
|
||||
//
|
||||
// // TODO: change the return type to make sure that:
|
||||
// // - each entry has the same number of dealings
|
||||
// // - dealer data is not duplicated
|
||||
// // - each dealer has submitted all or nothing
|
||||
// async fn deterministic_filter_dealers(
|
||||
// dkg_client: &DkgClient,
|
||||
// state: &mut State,
|
||||
// epoch_id: EpochId,
|
||||
// threshold: Threshold,
|
||||
// resharing: bool,
|
||||
// ) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> {
|
||||
// // if we're in resharing mode, the contract itself will forbid submission of dealings from
|
||||
// // parties that were not "initial" dealers, so we don't have to worry about it
|
||||
//
|
||||
// let mut dealings_maps = Vec::new();
|
||||
// let initial_dealers_by_addr = state.current_dealers_by_addr();
|
||||
// let initial_receivers = state.current_dealers_by_idx();
|
||||
// let initial_resharing_dealers = if resharing {
|
||||
// dkg_client
|
||||
// .get_initial_dealers()
|
||||
// .await?
|
||||
// .map(|d| d.initial_dealers)
|
||||
// .unwrap_or_default()
|
||||
// } else {
|
||||
// vec![]
|
||||
// };
|
||||
//
|
||||
// let params = dkg::params();
|
||||
//
|
||||
// // note: this is a temporary solution to replicate the behaviour of the old code so that I wouldn't need to
|
||||
// // fix the filtering in this PR, because the old code is quite buggy and misses few edge cases
|
||||
// let mut raw_dealings = HashMap::new();
|
||||
// for dealer in state.all_dealers().keys() {
|
||||
// let dealer_dealings = dkg_client
|
||||
// .get_dealings(epoch_id, dealer.to_string())
|
||||
// .await?;
|
||||
// for dealing in dealer_dealings {
|
||||
// let old_contract_dealing = raw_dealings.entry(dealing.index).or_insert(Vec::new());
|
||||
// old_contract_dealing.push((dealer.clone(), dealing.data))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // this is a temporary thing to reintroduce the bug to make sure tests still pass : )
|
||||
// // i will fix it properly in next PR
|
||||
// for dealing_index in 0..5 {
|
||||
// let dealings = raw_dealings.remove(&dealing_index).unwrap_or_default();
|
||||
// let dealings_map =
|
||||
// BTreeMap::from_iter(dealings.into_iter().filter_map(|(dealer, dealing)| {
|
||||
// match Dealing::try_from(&dealing) {
|
||||
// Ok(dealing) => {
|
||||
// if dealing
|
||||
// .verify(params, threshold, &initial_receivers, None)
|
||||
// .is_err()
|
||||
// {
|
||||
// state.mark_bad_dealer(
|
||||
// &dealer,
|
||||
// ComplaintReason::DealingVerificationError,
|
||||
// );
|
||||
// None
|
||||
// } else {
|
||||
// initial_dealers_by_addr
|
||||
// .get(&dealer)
|
||||
// .map(|idx| (*idx, (dealer, dealing)))
|
||||
// }
|
||||
// }
|
||||
// Err(_) => {
|
||||
// state.mark_bad_dealer(&dealer, ComplaintReason::MalformedDealing);
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// }));
|
||||
// dealings_maps.push(dealings_map);
|
||||
// }
|
||||
//
|
||||
// //
|
||||
// //
|
||||
// // for dealer in initial_dealers_by_addr.keys() {
|
||||
// // let Some(dealer_index) = initial_dealers_by_addr.get(dealer) else {
|
||||
// // warn!("could not obtain dealer index of {dealer}");
|
||||
// // continue;
|
||||
// // };
|
||||
// //
|
||||
// // let dealer_dealings = dkg_client
|
||||
// // .get_dealings(epoch_id, dealer.to_string())
|
||||
// // .await?;
|
||||
// //
|
||||
// // for contract_dealing in dealer_dealings {
|
||||
// // match Dealing::try_from(&contract_dealing.data) {
|
||||
// // // FIXME: bug: this doesn't check resharing
|
||||
// // Ok(dealing) => {
|
||||
// // if let Err(err) = dealing.verify(params, threshold, &initial_receivers, None) {
|
||||
// // println!("dealing verification failure from {dealer}: {err}");
|
||||
// // state.mark_bad_dealer(dealer, ComplaintReason::DealingVerificationError);
|
||||
// // } else {
|
||||
// // let entry = dealings_maps
|
||||
// // .entry(contract_dealing.index)
|
||||
// // .or_insert(BTreeMap::new());
|
||||
// // entry.insert(*dealer_index, (dealer.clone(), dealing));
|
||||
// // }
|
||||
// // }
|
||||
// // Err(err) => {
|
||||
// // warn!("malformed dealing from {dealer}: {err}");
|
||||
// // state.mark_bad_dealer(dealer, ComplaintReason::MalformedDealing);
|
||||
// // }
|
||||
// // }
|
||||
// // }
|
||||
// // }
|
||||
//
|
||||
// for (addr, _) in initial_dealers_by_addr.iter() {
|
||||
// // in resharing mode, we don't commit dealings from dealers outside the initial set
|
||||
// if !resharing || initial_resharing_dealers.contains(addr) {
|
||||
// for dealings_map in dealings_maps.iter() {
|
||||
// if !dealings_map.iter().any(|(_, (address, _))| address == addr) {
|
||||
// state.mark_bad_dealer(addr, ComplaintReason::MissingDealing);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Ok(dealings_maps)
|
||||
// }
|
||||
//
|
||||
// fn derive_partial_keypair(
|
||||
// state: &mut State,
|
||||
// threshold: Threshold,
|
||||
// dealings_maps: Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>,
|
||||
// ) -> Result<KeyPair, CoconutError> {
|
||||
// let filtered_receivers_by_idx = state.current_dealers_by_idx();
|
||||
// let filtered_dealers_by_addr = state.current_dealers_by_addr();
|
||||
// let dk = state.dkg_keypair().private_key();
|
||||
// let node_index_value = state.receiver_index_value()?;
|
||||
// let mut scalars = vec![];
|
||||
// let mut recovered_vks = vec![];
|
||||
// for dealings_map in dealings_maps.into_iter() {
|
||||
// let (filtered_dealers, filtered_dealings): (Vec<_>, Vec<_>) = dealings_map
|
||||
// .into_iter()
|
||||
// .filter_map(|(idx, (addr, dealing))| {
|
||||
// if filtered_dealers_by_addr.keys().any(|a| addr == *a) {
|
||||
// Some((idx, dealing))
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// })
|
||||
// .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,
|
||||
// &filtered_receivers_by_idx,
|
||||
// )?;
|
||||
// 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);
|
||||
// }
|
||||
// state.set_recovered_vks(recovered_vks);
|
||||
//
|
||||
// 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(&BANDWIDTH_CREDENTIAL_PARAMS);
|
||||
//
|
||||
// Ok(CoconutKeyPair::from_keys(sk, vk))
|
||||
// }
|
||||
|
||||
impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
fn verified_dealer_dealings(
|
||||
@@ -380,8 +382,6 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
|
||||
let all_dealers = dealings[&0].keys().copied().collect::<Vec<_>>();
|
||||
|
||||
let decryption_key = self.state.dkg_keypair().private_key();
|
||||
|
||||
let mut derived_x = None;
|
||||
let mut derived_secrets = Vec::new();
|
||||
|
||||
@@ -397,19 +397,19 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
debug!("recovering the partial verification keys");
|
||||
let recovered =
|
||||
try_recover_verification_keys(&dealings_vec, threshold, &epoch_receivers)?;
|
||||
// TODO:
|
||||
|
||||
self.state
|
||||
.key_derivation_state_mut(epoch_id)?
|
||||
.derived_partials
|
||||
.insert(dealing_index, recovered);
|
||||
|
||||
debug!("decrypting received shares");
|
||||
// for every received share of the key
|
||||
let mut shares = Vec::with_capacity(dealings_vec.len());
|
||||
for (i, dealing) in dealings_vec.into_iter().enumerate() {
|
||||
// attempt to decrypt our portion
|
||||
let share = match decrypt_share(
|
||||
decryption_key,
|
||||
receiver_index,
|
||||
&dealing.ciphertexts,
|
||||
None,
|
||||
) {
|
||||
let dk = self.state.dkg_keypair().private_key();
|
||||
let share = match decrypt_share(dk, receiver_index, &dealing.ciphertexts, None) {
|
||||
Ok(share) => share,
|
||||
Err(err) => {
|
||||
let node_index = all_dealers[i];
|
||||
@@ -582,106 +582,6 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_proposal(proposal: &ProposalResponse) -> Option<(Addr, u64)> {
|
||||
if proposal.status == Status::Open {
|
||||
if let Some(owner) = owner_from_cosmos_msgs(&proposal.msgs) {
|
||||
return Some((owner, proposal.id));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) async fn verification_key_validation(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
_resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.voted_vks() {
|
||||
debug!("Already voted on the verification keys, nothing to do");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let epoch_id = dkg_client.get_current_epoch().await?.epoch_id;
|
||||
let vk_shares = dkg_client.get_verification_key_shares(epoch_id).await?;
|
||||
let proposal_ids = BTreeMap::from_iter(
|
||||
dkg_client
|
||||
.list_proposals()
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(validate_proposal),
|
||||
);
|
||||
let filtered_receivers_by_idx: Vec<_> =
|
||||
state.current_dealers_by_idx().keys().copied().collect();
|
||||
let recovered_partials: Vec<_> = state
|
||||
.recovered_vks()
|
||||
.iter()
|
||||
.map(|recovered_vk| recovered_vk.recovered_partials.clone())
|
||||
.collect();
|
||||
let recovered_partials = transpose_matrix(recovered_partials);
|
||||
let params = &BANDWIDTH_CREDENTIAL_PARAMS;
|
||||
for contract_share in vk_shares {
|
||||
if let Some(proposal_id) = proposal_ids.get(&contract_share.owner).copied() {
|
||||
match VerificationKey::try_from_bs58(contract_share.share) {
|
||||
Ok(vk) => {
|
||||
if let Some(idx) = filtered_receivers_by_idx
|
||||
.iter()
|
||||
.position(|node_index| contract_share.node_index == *node_index)
|
||||
{
|
||||
let ret = if !check_vk_pairing(params, &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
|
||||
};
|
||||
accepted_vote_err(ret)?;
|
||||
}
|
||||
}
|
||||
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;
|
||||
accepted_vote_err(ret)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state.set_voted_vks();
|
||||
info!("DKG: Validated the other verification keys");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn verification_key_finalization(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
_resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.executed_proposal() {
|
||||
debug!("Already executed the proposal, nothing to do");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let proposal_id = state.proposal_id_value()?;
|
||||
dkg_client
|
||||
.execute_verification_key_share(proposal_id)
|
||||
.await?;
|
||||
state.set_executed_proposal();
|
||||
info!("DKG: Finalized own verification key on chain");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
@@ -1095,11 +995,10 @@ pub(crate) mod tests {
|
||||
submit_public_keys(&mut controllers, false).await;
|
||||
exchange_dealings(&mut controllers, false).await;
|
||||
|
||||
let chain = controllers[0].chain_state.clone();
|
||||
for controller in controllers.iter_mut() {
|
||||
let res = controller.verification_key_submission(epoch, false).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
derive_keypairs(&mut controllers, false).await;
|
||||
|
||||
for controller in controllers {
|
||||
assert!(controller.state.key_derivation_state(epoch)?.completed);
|
||||
let keys = controller.state.take_coconut_keypair().await;
|
||||
assert!(keys.is_some());
|
||||
@@ -1107,132 +1006,6 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// let db = MockContractDb::new();
|
||||
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
|
||||
//
|
||||
// for controller in clients_and_states.iter_mut() {
|
||||
// assert!(db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .contains_key(&controller.state.proposal_id_value().unwrap()));
|
||||
// assert!(controller.state.coconut_keypair_is_some().await);
|
||||
// }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn validate_verification_key() {
|
||||
todo!()
|
||||
// let db = MockContractDb::new();
|
||||
// let mut clients_and_states = prepare_clients_and_states_with_validation(&db).await;
|
||||
// for controller in clients_and_states.iter_mut() {
|
||||
// let proposal = db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .get(&controller.state.proposal_id_value().unwrap())
|
||||
// .unwrap()
|
||||
// .clone();
|
||||
// assert_eq!(proposal.status, Status::Passed);
|
||||
// }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn validate_verification_key_malformed_share() {
|
||||
todo!()
|
||||
// let db = MockContractDb::new();
|
||||
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
|
||||
//
|
||||
// db.verification_share_db
|
||||
// .write()
|
||||
// .unwrap()
|
||||
// .entry(TEST_VALIDATORS_ADDRESS[0].to_string())
|
||||
// .and_modify(|share| share.share.push('x'));
|
||||
//
|
||||
// for controller in clients_and_states.iter_mut() {
|
||||
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// }
|
||||
//
|
||||
// for (idx, controller) in clients_and_states.iter().enumerate() {
|
||||
// let proposal = db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .get(&controller.state.proposal_id_value().unwrap())
|
||||
// .unwrap()
|
||||
// .clone();
|
||||
// if idx == 0 {
|
||||
// assert_eq!(proposal.status, Status::Rejected);
|
||||
// } else {
|
||||
// assert_eq!(proposal.status, Status::Passed);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn validate_verification_key_unpaired_share() {
|
||||
todo!()
|
||||
// let db = MockContractDb::new();
|
||||
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
|
||||
//
|
||||
// let second_share = db
|
||||
// .verification_share_db
|
||||
// .write()
|
||||
// .unwrap()
|
||||
// .get(TEST_VALIDATORS_ADDRESS[1])
|
||||
// .unwrap()
|
||||
// .share
|
||||
// .clone();
|
||||
// db.verification_share_db
|
||||
// .write()
|
||||
// .unwrap()
|
||||
// .entry(TEST_VALIDATORS_ADDRESS[0].to_string())
|
||||
// .and_modify(|share| share.share = second_share);
|
||||
//
|
||||
// for controller in clients_and_states.iter_mut() {
|
||||
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// }
|
||||
//
|
||||
// for (idx, controller) in clients_and_states.iter().enumerate() {
|
||||
// let proposal = db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .get(&controller.state.proposal_id_value().unwrap())
|
||||
// .unwrap()
|
||||
// .clone();
|
||||
// if idx == 0 {
|
||||
// assert_eq!(proposal.status, Status::Rejected);
|
||||
// } else {
|
||||
// assert_eq!(proposal.status, Status::Passed);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn finalize_verification_key() {
|
||||
todo!()
|
||||
// let db = MockContractDb::new();
|
||||
// let clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
|
||||
//
|
||||
// for controller in clients_and_states.iter() {
|
||||
// let proposal = db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .get(&controller.state.proposal_id_value().unwrap())
|
||||
// .unwrap()
|
||||
// .clone();
|
||||
// assert_eq!(proposal.status, Status::Executed);
|
||||
// }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::controller::DkgController;
|
||||
use crate::coconut::dkg::state::{ConsistentState, State};
|
||||
use crate::coconut::error::CoconutError;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
impl<R: RngCore + CryptoRng> DkgController<R> {}
|
||||
|
||||
pub(crate) async fn verification_key_finalization(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
_resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.executed_proposal() {
|
||||
log::debug!("Already executed the proposal, nothing to do");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let proposal_id = state.proposal_id_value()?;
|
||||
dkg_client
|
||||
.execute_verification_key_share(proposal_id)
|
||||
.await?;
|
||||
state.set_executed_proposal();
|
||||
info!("DKG: Finalized own verification key on chain");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn finalize_verification_key() {
|
||||
todo!()
|
||||
// let db = MockContractDb::new();
|
||||
// let clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
|
||||
//
|
||||
// for controller in clients_and_states.iter() {
|
||||
// let proposal = db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .get(&controller.state.proposal_id_value().unwrap())
|
||||
// .unwrap()
|
||||
// .clone();
|
||||
// assert_eq!(proposal.status, Status::Executed);
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::coconut::api_routes::epoch_credentials;
|
||||
use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::controller::DkgController;
|
||||
use crate::coconut::dkg::state::State;
|
||||
use crate::coconut::error::CoconutError;
|
||||
use crate::coconut::helpers::accepted_vote_err;
|
||||
use crate::coconut::state::BANDWIDTH_CREDENTIAL_PARAMS;
|
||||
use cosmwasm_std::Addr;
|
||||
use cw3::{ProposalResponse, Status};
|
||||
use nym_coconut::tests::helpers::transpose_matrix;
|
||||
use nym_coconut::{check_vk_pairing, Base58, VerificationKey};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_coconut_dkg_common::verification_key::{owner_from_cosmos_msgs, ContractVKShare};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::collections::HashMap;
|
||||
|
||||
impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
fn filter_proposal(
|
||||
&self,
|
||||
dkg_contract: &AccountId,
|
||||
proposal: &ProposalResponse,
|
||||
) -> Option<(Addr, u64)> {
|
||||
// make sure the proposal we're checking is:
|
||||
// - still open (not point in voting for anything that has already expired)
|
||||
// - was proposed by the DKG contract - so that we'd ignore anything from malicious dealers
|
||||
// - contains valid verification request (checked inside `owner_from_cosmos_msgs`)
|
||||
if proposal.status == Status::Open && proposal.proposer.as_str() == dkg_contract.as_ref() {
|
||||
if let Some(owner) = owner_from_cosmos_msgs(&proposal.msgs) {
|
||||
return Some((owner, proposal.id));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
async fn get_validation_proposals(&self) -> Result<HashMap<Addr, u64>, CoconutError> {
|
||||
let dkg_contract = self.dkg_client.dkg_contract_address().await?;
|
||||
|
||||
// FUTURE OPTIMIZATION: don't query for ALL proposals. say if we're in epoch 50,
|
||||
// we don't care about expired proposals from epochs 0-49...
|
||||
// to do it, we'll need to have dkg contract store proposal ids,
|
||||
// which will require usage of submsgs and replies so that might be a future project
|
||||
let all_proposals = self.dkg_client.list_proposals().await?;
|
||||
|
||||
let mut deduped_proposals = HashMap::new();
|
||||
|
||||
// for each proposal, make sure it's a valid validation request;
|
||||
// if for some reason there exist multiple proposals from the same owner, choose the one
|
||||
// with the higher id
|
||||
for proposal in all_proposals {
|
||||
if let Some((owner, id)) = self.filter_proposal(&dkg_contract, &proposal) {
|
||||
if let Some(old_id) = deduped_proposals.get(&owner) {
|
||||
if old_id < &id {
|
||||
deduped_proposals.insert(owner, id);
|
||||
}
|
||||
} else {
|
||||
deduped_proposals.insert(owner, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UNHANDLED EDGE CASE:
|
||||
// since currently proposals are **NOT** tied to epochs,
|
||||
// we might run into proposals from older epochs we don't have to vote on or might not even have data for
|
||||
Ok(deduped_proposals)
|
||||
}
|
||||
|
||||
async fn verify_share(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
share: ContractVKShare,
|
||||
) -> Result<(), CoconutError> {
|
||||
if share.verified {
|
||||
todo!()
|
||||
}
|
||||
|
||||
let Some(receiver_index) = self
|
||||
.state
|
||||
.valid_epoch_receivers(epoch_id)?
|
||||
.iter()
|
||||
.position(|(addr, _)| addr == share.owner)
|
||||
else {
|
||||
todo!()
|
||||
};
|
||||
|
||||
// EDGE CASE:
|
||||
// make sure the receiver index of this receiver/dealer is within the size of the derived keys
|
||||
|
||||
// attempt to recover the underlying key from its bs58 representation
|
||||
let recovered_key = match VerificationKey::try_from_bs58(share.share) {
|
||||
Ok(key) => key,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to decode verification share from {}: {err}",
|
||||
share.owner
|
||||
);
|
||||
todo!()
|
||||
}
|
||||
};
|
||||
|
||||
let Some(self_derived) = self
|
||||
.state
|
||||
.key_derivation_state(epoch_id)?
|
||||
.derived_partials_for(receiver_index)
|
||||
else {
|
||||
todo!()
|
||||
};
|
||||
|
||||
if !check_vk_pairing(&BANDWIDTH_CREDENTIAL_PARAMS, &self_derived, &recovered_key) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn verification_key_validation(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
let key_validation_state = self.state.key_validation_state(epoch_id)?;
|
||||
|
||||
// check if we have already validated and voted for all keys
|
||||
if key_validation_state.completed() {
|
||||
// the only way this could be a false positive is if the chain forked and blocks got reverted,
|
||||
// but I don't think we have to worry about that
|
||||
debug!("we have already voted in all validation proposals");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// FAILURE CASE:
|
||||
// check if we have already verified the keys, but some voting txs either didn't get executed
|
||||
// or got executed without us knowing about it
|
||||
if !key_validation_state.votes.is_empty() {
|
||||
debug!(
|
||||
"we have already validated all keys for this epoch, but might have failed to vote"
|
||||
);
|
||||
// dkg_client.query_vote to check if our vote is there
|
||||
todo!()
|
||||
}
|
||||
|
||||
let proposals = self.get_validation_proposals().await?;
|
||||
let vk_shares = self
|
||||
.dkg_client
|
||||
.get_verification_key_shares(epoch_id)
|
||||
.await?;
|
||||
|
||||
for contract_share in vk_shares {
|
||||
// there's no point in checking anything if there doesn't exist an associated multisig proposal
|
||||
let Some(proposal_id) = proposals.get(&contract_share.owner) else {
|
||||
warn!(
|
||||
"there does not seem to exist proposal for vk share from {}",
|
||||
contract_share.owner
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let vote = if let Err(err) = self.verify_share(epoch_id, contract_share).await {
|
||||
todo!();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
self.state
|
||||
.key_validation_state_mut(epoch_id)?
|
||||
.votes
|
||||
.insert(*proposal_id, vote);
|
||||
}
|
||||
|
||||
// do vote
|
||||
for (&proposal, &vote) in &self.state.key_validation_state(epoch_id)?.votes {
|
||||
// FUTURE OPTIMIZATION: we could batch them in a single tx
|
||||
self.dkg_client
|
||||
.vote_verification_key_share(proposal, vote)
|
||||
.await?;
|
||||
}
|
||||
|
||||
self.state.key_validation_state_mut(epoch_id)?.completed = true;
|
||||
|
||||
// if self.state.voted_vks() {
|
||||
// log::debug!("Already voted on the verification keys, nothing to do");
|
||||
// return Ok(());
|
||||
// }
|
||||
//
|
||||
// let epoch_id = self.dkg_client.get_current_epoch().await?.epoch_id;
|
||||
// let vk_shares = self
|
||||
// .dkg_client
|
||||
// .get_verification_key_shares(epoch_id)
|
||||
// .await?;
|
||||
// let proposal_ids = BTreeMap::from_iter(
|
||||
// self.dkg_client
|
||||
// .list_proposals()
|
||||
// .await?
|
||||
// .iter()
|
||||
// .filter_map(validate_proposal),
|
||||
// );
|
||||
// let filtered_receivers_by_idx: Vec<_> = self
|
||||
// .state
|
||||
// .current_dealers_by_idx()
|
||||
// .keys()
|
||||
// .copied()
|
||||
// .collect();
|
||||
// let recovered_partials: Vec<_> = self
|
||||
// .state
|
||||
// .recovered_vks()
|
||||
// .iter()
|
||||
// .map(|recovered_vk| recovered_vk.recovered_partials.clone())
|
||||
// .collect();
|
||||
// let recovered_partials = transpose_matrix(recovered_partials);
|
||||
// let params = &BANDWIDTH_CREDENTIAL_PARAMS;
|
||||
// for contract_share in vk_shares {
|
||||
// if let Some(proposal_id) = proposal_ids.get(&contract_share.owner).copied() {
|
||||
// match VerificationKey::try_from_bs58(contract_share.share) {
|
||||
// Ok(vk) => {
|
||||
// if let Some(idx) = filtered_receivers_by_idx
|
||||
// .iter()
|
||||
// .position(|node_index| contract_share.node_index == *node_index)
|
||||
// {
|
||||
// let ret = if !check_vk_pairing(params, &recovered_partials[idx], &vk) {
|
||||
// log::debug!(
|
||||
// "Voting NO to proposal {} because of failed VK pairing",
|
||||
// proposal_id
|
||||
// );
|
||||
// self.dkg_client
|
||||
// .vote_verification_key_share(proposal_id, false)
|
||||
// .await
|
||||
// } else {
|
||||
// log::debug!("Voting YES to proposal {}", proposal_id);
|
||||
// self.dkg_client
|
||||
// .vote_verification_key_share(proposal_id, true)
|
||||
// .await
|
||||
// };
|
||||
// accepted_vote_err(ret)?;
|
||||
// }
|
||||
// }
|
||||
// Err(_) => {
|
||||
// log::debug!(
|
||||
// "Voting NO to proposal {} because of failed base 58 deserialization",
|
||||
// proposal_id
|
||||
// );
|
||||
// let ret = self
|
||||
// .dkg_client
|
||||
// .vote_verification_key_share(proposal_id, false)
|
||||
// .await;
|
||||
// accepted_vote_err(ret)?;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// self.state.set_voted_vks();
|
||||
|
||||
info!("DKG: validated all the other verification keys");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::coconut::tests::helpers::{
|
||||
derive_keypairs, exchange_dealings, initialise_controllers, initialise_dkg,
|
||||
submit_public_keys,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn validate_verification_key() -> anyhow::Result<()> {
|
||||
let unused = 42;
|
||||
let validators = 1;
|
||||
|
||||
let mut controllers = initialise_controllers(validators);
|
||||
let chain = controllers[0].chain_state.clone();
|
||||
let epoch = chain.lock().unwrap().dkg_epoch.epoch_id;
|
||||
|
||||
initialise_dkg(&mut controllers, false);
|
||||
submit_public_keys(&mut controllers, false).await;
|
||||
exchange_dealings(&mut controllers, false).await;
|
||||
derive_keypairs(&mut controllers, false).await;
|
||||
|
||||
for controller in controllers.iter_mut() {
|
||||
let res = controller.verification_key_validation(epoch, false).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
assert!(controller.state.key_validation_state(epoch)?.completed);
|
||||
}
|
||||
|
||||
let chain = controllers[0].chain_state.clone();
|
||||
let guard = chain.lock().unwrap();
|
||||
let proposals = &guard.proposals;
|
||||
assert_eq!(proposals.len(), validators);
|
||||
|
||||
for proposal in proposals.values() {
|
||||
assert_eq!(Status::Passed, proposal.status)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
// let db = MockContractDb::new();
|
||||
// let mut clients_and_states = prepare_clients_and_states_with_validation(&db).await;
|
||||
// for controller in clients_and_states.iter_mut() {
|
||||
// let proposal = db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .get(&controller.state.proposal_id_value().unwrap())
|
||||
// .unwrap()
|
||||
// .clone();
|
||||
// assert_eq!(proposal.status, Status::Passed);
|
||||
// }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn validate_verification_key_malformed_share() {
|
||||
todo!()
|
||||
// let db = MockContractDb::new();
|
||||
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
|
||||
//
|
||||
// db.verification_share_db
|
||||
// .write()
|
||||
// .unwrap()
|
||||
// .entry(TEST_VALIDATORS_ADDRESS[0].to_string())
|
||||
// .and_modify(|share| share.share.push('x'));
|
||||
//
|
||||
// for controller in clients_and_states.iter_mut() {
|
||||
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// }
|
||||
//
|
||||
// for (idx, controller) in clients_and_states.iter().enumerate() {
|
||||
// let proposal = db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .get(&controller.state.proposal_id_value().unwrap())
|
||||
// .unwrap()
|
||||
// .clone();
|
||||
// if idx == 0 {
|
||||
// assert_eq!(proposal.status, Status::Rejected);
|
||||
// } else {
|
||||
// assert_eq!(proposal.status, Status::Passed);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn validate_verification_key_unpaired_share() {
|
||||
todo!()
|
||||
// let db = MockContractDb::new();
|
||||
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
|
||||
//
|
||||
// let second_share = db
|
||||
// .verification_share_db
|
||||
// .write()
|
||||
// .unwrap()
|
||||
// .get(TEST_VALIDATORS_ADDRESS[1])
|
||||
// .unwrap()
|
||||
// .share
|
||||
// .clone();
|
||||
// db.verification_share_db
|
||||
// .write()
|
||||
// .unwrap()
|
||||
// .entry(TEST_VALIDATORS_ADDRESS[0].to_string())
|
||||
// .and_modify(|share| share.share = second_share);
|
||||
//
|
||||
// for controller in clients_and_states.iter_mut() {
|
||||
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// }
|
||||
//
|
||||
// for (idx, controller) in clients_and_states.iter().enumerate() {
|
||||
// let proposal = db
|
||||
// .proposal_db
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .get(&controller.state.proposal_id_value().unwrap())
|
||||
// .unwrap()
|
||||
// .clone();
|
||||
// if idx == 0 {
|
||||
// assert_eq!(proposal.status, Status::Rejected);
|
||||
// } else {
|
||||
// assert_eq!(proposal.status, Status::Passed);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,7 @@ pub(crate) mod complaints;
|
||||
pub(crate) mod controller;
|
||||
pub(crate) mod dealing;
|
||||
pub(crate) mod key_derivation;
|
||||
mod key_finalization;
|
||||
mod key_validation;
|
||||
pub(crate) mod public_key;
|
||||
pub(crate) mod state;
|
||||
|
||||
@@ -5,11 +5,13 @@ use super::serde_helpers::generated_dealings;
|
||||
use nym_coconut_dkg_common::types::DealingIndex;
|
||||
use nym_dkg::{Dealing, NodeIndex};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use crate::coconut::dkg::state::DkgParticipant;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct DealingExchangeState {
|
||||
// pub(crate) assigned_index: Option<NodeIndex>,
|
||||
pub(crate) dealers: BTreeMap<NodeIndex, DkgParticipant>,
|
||||
|
||||
#[serde(with = "generated_dealings")]
|
||||
pub(crate) generated_dealings: HashMap<DealingIndex, Dealing>,
|
||||
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_dkg::Threshold;
|
||||
use super::serde_helpers::recovered_keys;
|
||||
use nym_coconut_dkg_common::types::DealingIndex;
|
||||
use nym_dkg::{G2Projective, RecoveredVerificationKeys, Threshold};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
type ReceiverIndex = usize;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct KeyDerivationState {
|
||||
pub(crate) expected_threshold: Option<Threshold>, // pub(crate) completed: bool,
|
||||
pub(crate) expected_threshold: Option<Threshold>,
|
||||
|
||||
#[serde(with = "recovered_keys")]
|
||||
pub(crate) derived_partials: BTreeMap<DealingIndex, RecoveredVerificationKeys>,
|
||||
|
||||
pub(crate) completed: bool,
|
||||
// because we couldn't decrypt shares or there were no shares, etc
|
||||
@@ -14,6 +22,19 @@ pub struct KeyDerivationState {
|
||||
}
|
||||
|
||||
impl KeyDerivationState {
|
||||
pub fn derived_partials_for(&self, receiver_index: ReceiverIndex) -> Option<Vec<G2Projective>> {
|
||||
let mut recovered = Vec::new();
|
||||
for keys in self.derived_partials.values() {
|
||||
// SAFETY:
|
||||
// make sure the receiver index of this receiver/dealer is within the size of the derived keys
|
||||
if keys.recovered_partials.len() <= receiver_index {
|
||||
return None;
|
||||
};
|
||||
recovered.push(keys.recovered_partials[receiver_index])
|
||||
}
|
||||
Some(recovered)
|
||||
}
|
||||
|
||||
pub fn completed(&self) -> bool {
|
||||
self.completed
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct FinalizationState {}
|
||||
|
||||
impl FinalizationState {
|
||||
/// Specifies whether this dealer has already registered in the particular DKG epoch
|
||||
pub fn completed(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
type ProposalId = u64;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ValidationState {
|
||||
pub votes: HashMap<ProposalId, bool>,
|
||||
|
||||
pub completed: bool,
|
||||
}
|
||||
|
||||
impl ValidationState {
|
||||
/// Specifies whether this dealer has already registered in the particular DKG epoch
|
||||
pub fn completed(&self) -> bool {
|
||||
self.completed
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,13 @@ use std::path::{Path, PathBuf};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::RwLockReadGuard;
|
||||
use url::Url;
|
||||
use crate::coconut::dkg::state::key_finalization::FinalizationState;
|
||||
use crate::coconut::dkg::state::key_validation::ValidationState;
|
||||
|
||||
mod dealing_exchange;
|
||||
mod key_derivation;
|
||||
mod key_finalization;
|
||||
mod key_validation;
|
||||
mod registration;
|
||||
mod serde_helpers;
|
||||
|
||||
@@ -242,18 +246,19 @@ impl PersistentState {
|
||||
#[derive(Default)]
|
||||
pub(crate) struct DkgState {
|
||||
pub(crate) registration: RegistrationState,
|
||||
|
||||
// available after registration is finished:
|
||||
pub(crate) dealers: BTreeMap<NodeIndex, DkgParticipant>,
|
||||
|
||||
|
||||
pub(crate) dealing_exchange: DealingExchangeState,
|
||||
|
||||
pub(crate) key_generation: KeyDerivationState,
|
||||
|
||||
pub(crate) key_validation: ValidationState,
|
||||
|
||||
pub(crate) key_finalization: FinalizationState,
|
||||
}
|
||||
|
||||
impl DkgState {
|
||||
pub(crate) fn set_dealers(&mut self, raw_dealers: Vec<DealerDetails>) {
|
||||
assert!(self.dealers.is_empty());
|
||||
assert!(self.dealing_exchange.dealers.is_empty());
|
||||
for raw_dealer in raw_dealers {
|
||||
let dkg_participant = DkgParticipant::from(raw_dealer);
|
||||
if let ParticipantState::Invalid(complaint) = &dkg_participant.state {
|
||||
@@ -262,7 +267,7 @@ impl DkgState {
|
||||
dkg_participant.address
|
||||
)
|
||||
}
|
||||
self.dealers
|
||||
self.dealing_exchange.dealers
|
||||
.insert(dkg_participant.assigned_index, dkg_participant);
|
||||
}
|
||||
}
|
||||
@@ -365,7 +370,7 @@ impl State {
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Vec<(Addr, NodeIndex)>, CoconutError> {
|
||||
Ok(self
|
||||
.dkg_state(epoch_id)?
|
||||
.dealing_exchange_state(epoch_id)?
|
||||
.dealers
|
||||
.values()
|
||||
.filter_map(|d| {
|
||||
@@ -383,7 +388,7 @@ impl State {
|
||||
epoch_id: EpochId,
|
||||
) -> Result<BTreeMap<NodeIndex, bte::PublicKey>, CoconutError> {
|
||||
Ok(self
|
||||
.dkg_state(epoch_id)?
|
||||
.dealing_exchange_state(epoch_id)?
|
||||
.dealers
|
||||
.values()
|
||||
.filter_map(|d| d.state.public_key().map(|k| (d.assigned_index, k)))
|
||||
@@ -462,6 +467,46 @@ impl State {
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn key_validation_state(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&ValidationState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get(&epoch_id)
|
||||
.map(|state| &state.key_validation)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn key_validation_state_mut(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&mut ValidationState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get_mut(&epoch_id)
|
||||
.map(|state| &mut state.key_validation)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn key_finalization_state(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&FinalizationState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get(&epoch_id)
|
||||
.map(|state| &state.key_finalization)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn key_finalization_state_mut(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&mut FinalizationState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get_mut(&epoch_id)
|
||||
.map(|state| &mut state.key_finalization)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn threshold(&self, epoch_id: EpochId) -> Result<Threshold, CoconutError> {
|
||||
self.key_derivation_state(epoch_id)?
|
||||
.expected_threshold
|
||||
|
||||
@@ -50,6 +50,44 @@ pub(super) mod vks_serde {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) mod recovered_keys {
|
||||
use nym_coconut_dkg_common::types::DealingIndex;
|
||||
use nym_dkg::RecoveredVerificationKeys;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
type Helper = BTreeMap<DealingIndex, Vec<u8>>;
|
||||
|
||||
pub fn serialize<S: Serializer>(
|
||||
val: &BTreeMap<DealingIndex, RecoveredVerificationKeys>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let helper: Helper = val
|
||||
.iter()
|
||||
.map(|(idx, rec)| (*idx, rec.to_bytes()))
|
||||
.collect();
|
||||
helper.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<BTreeMap<DealingIndex, RecoveredVerificationKeys>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let helper = Helper::deserialize(deserializer)?;
|
||||
helper
|
||||
.into_iter()
|
||||
.map(|(idx, rec)| {
|
||||
RecoveredVerificationKeys::try_from_bytes(&rec)
|
||||
.map_err(|err| D::Error::custom(format_args!("{:?}", err)))
|
||||
.map(|vk| (idx, vk))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) mod generated_dealings {
|
||||
use nym_coconut_dkg_common::types::DealingIndex;
|
||||
use nym_dkg::Dealing;
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::coconut::dkg::controller::DkgController;
|
||||
use crate::coconut::dkg::key_derivation::{
|
||||
verification_key_finalization, verification_key_validation,
|
||||
};
|
||||
use crate::coconut::tests::fixtures::{TestingDkgController, TestingDkgControllerBuilder};
|
||||
use crate::coconut::tests::FakeChainState;
|
||||
use nym_coconut_dkg_common::types::EpochState;
|
||||
@@ -110,23 +107,40 @@ pub(crate) async fn derive_keypairs(controllers: &mut [TestingDkgController], re
|
||||
}
|
||||
|
||||
pub(crate) async fn validate_keys(controllers: &mut [TestingDkgController], resharing: bool) {
|
||||
todo!()
|
||||
// let mut clients_and_states = derive_keypairs(db).await;
|
||||
// for controller in clients_and_states.iter_mut() {
|
||||
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// }
|
||||
// clients_and_states
|
||||
let epoch = controllers[0]
|
||||
.chain_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.dkg_epoch
|
||||
.epoch_id;
|
||||
|
||||
for controller in controllers.iter_mut() {
|
||||
controller
|
||||
.verification_key_validation(epoch, resharing)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let mut guard = controllers[0].chain_state.lock().unwrap();
|
||||
guard.dkg_epoch.state = EpochState::VerificationKeyFinalization { resharing }
|
||||
}
|
||||
|
||||
pub(crate) async fn finalize(controllers: &mut [TestingDkgController], resharing: bool) {
|
||||
todo!()
|
||||
// let mut clients_and_states = validate_keys(db).await;
|
||||
// for controller in clients_and_states.iter_mut() {
|
||||
// verification_key_finalization(&controller.dkg_client, &mut controller.state, false)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// }
|
||||
// clients_and_states
|
||||
let epoch = controllers[0]
|
||||
.chain_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.dkg_epoch
|
||||
.epoch_id;
|
||||
|
||||
for controller in controllers.iter_mut() {
|
||||
todo!()
|
||||
// controller
|
||||
// .verification_key_finalization(epoch, resharing)
|
||||
// .await
|
||||
// .unwrap();
|
||||
}
|
||||
|
||||
let mut guard = controllers[0].chain_state.lock().unwrap();
|
||||
guard.dkg_epoch.state = EpochState::InProgress {}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ const TEST_REWARDING_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8g
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FakeChainState {
|
||||
pub(crate) dkg_contract: AccountId,
|
||||
|
||||
pub(crate) dealers: HashMap<NodeIndex, DealerDetails>,
|
||||
pub(crate) past_dealers: HashMap<NodeIndex, DealerDetails>,
|
||||
|
||||
@@ -90,6 +92,7 @@ pub(crate) struct FakeChainState {
|
||||
impl Default for FakeChainState {
|
||||
fn default() -> Self {
|
||||
FakeChainState {
|
||||
dkg_contract: AccountId::new("n", &[69u8; 32]).unwrap(),
|
||||
dealers: HashMap::new(),
|
||||
past_dealers: Default::default(),
|
||||
|
||||
@@ -248,6 +251,10 @@ impl super::client::Client for DummyClient {
|
||||
self.validator_address.clone()
|
||||
}
|
||||
|
||||
async fn dkg_contract_address(&self) -> Result<AccountId> {
|
||||
Ok(self.state.lock().unwrap().dkg_contract.clone())
|
||||
}
|
||||
|
||||
async fn get_tx(&self, tx_hash: Hash) -> Result<TxResponse> {
|
||||
todo!()
|
||||
// Ok(self
|
||||
@@ -274,15 +281,14 @@ impl super::client::Client for DummyClient {
|
||||
}
|
||||
|
||||
async fn list_proposals(&self) -> Result<Vec<ProposalResponse>> {
|
||||
todo!()
|
||||
// Ok(self
|
||||
// .state
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .proposals
|
||||
// .values()
|
||||
// .cloned()
|
||||
// .collect())
|
||||
Ok(self
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.proposals
|
||||
.values()
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn get_spent_credential(
|
||||
@@ -427,16 +433,15 @@ impl super::client::Client for DummyClient {
|
||||
vote_yes: bool,
|
||||
_fee: Option<Fee>,
|
||||
) -> Result<()> {
|
||||
todo!()
|
||||
// if let Some(proposal) = self.state.lock().unwrap().proposals.get_mut(&proposal_id) {
|
||||
// // 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(())
|
||||
if let Some(proposal) = self.state.lock().unwrap().proposals.get_mut(&proposal_id) {
|
||||
// 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(())
|
||||
}
|
||||
|
||||
async fn execute_proposal(&self, proposal_id: u64) -> Result<()> {
|
||||
@@ -541,6 +546,7 @@ impl super::client::Client for DummyClient {
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult> {
|
||||
let address = self.validator_address.to_string();
|
||||
let dkg_contract = self.state.lock().unwrap().dkg_contract.clone();
|
||||
|
||||
let Some(dealer_details) = self.get_dealer_by_address(&address).await else {
|
||||
// Just throw some error, not really the correct one
|
||||
@@ -588,7 +594,7 @@ impl super::client::Client for DummyClient {
|
||||
percentage: Decimal::from_ratio(2u32, 3u32),
|
||||
total_weight: 100,
|
||||
},
|
||||
proposer: Addr::unchecked(self.validator_address.as_ref()),
|
||||
proposer: Addr::unchecked(dkg_contract.as_ref()),
|
||||
deposit: None,
|
||||
};
|
||||
guard.proposals.insert(proposal_id, proposal);
|
||||
|
||||
@@ -346,6 +346,15 @@ impl crate::coconut::client::Client for Client {
|
||||
self.client_address().await
|
||||
}
|
||||
|
||||
async fn dkg_contract_address(&self) -> Result<AccountId, CoconutError> {
|
||||
nyxd_query!(
|
||||
self,
|
||||
dkg_contract_address()
|
||||
.cloned()
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract").into())
|
||||
)
|
||||
}
|
||||
|
||||
async fn get_tx(&self, tx_hash: Hash) -> crate::coconut::error::Result<nyxd::TxResponse> {
|
||||
nyxd_query!(self, get_tx(tx_hash).await).map_err(|source| {
|
||||
CoconutError::TxRetrievalFailure {
|
||||
|
||||
Reference in New Issue
Block a user