happy path for key finalization
This commit is contained in:
@@ -54,6 +54,7 @@ pub fn try_commit_verification_key_share(
|
||||
resharing,
|
||||
env.contract.address.to_string(),
|
||||
STATE.load(deps.storage)?.multisig_addr.to_string(),
|
||||
// TODO: make this value configurable
|
||||
env.block
|
||||
.time
|
||||
.plus_seconds(BLOCK_TIME_FOR_VERIFICATION_SECS),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::coconut::error::Result;
|
||||
use cw3::ProposalResponse;
|
||||
use cw3::{ProposalResponse, VoteResponse};
|
||||
use cw4::MemberResponse;
|
||||
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
|
||||
use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse, DealingStatusResponse};
|
||||
@@ -24,6 +24,7 @@ pub trait Client {
|
||||
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>>;
|
||||
async fn get_vote(&self, proposal_id: u64, voter: String) -> Result<VoteResponse>;
|
||||
async fn get_spent_credential(
|
||||
&self,
|
||||
blinded_serial_number: String,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::coconut::client::Client;
|
||||
use crate::coconut::error::CoconutError;
|
||||
use cw3::ProposalResponse;
|
||||
use cw3::{ProposalResponse, Status};
|
||||
use cw4::MemberResponse;
|
||||
use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse};
|
||||
use nym_coconut_dkg_common::types::{
|
||||
@@ -119,6 +119,13 @@ impl DkgClient {
|
||||
self.inner.list_proposals().await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_proposal_status(
|
||||
&self,
|
||||
proposal_id: u64,
|
||||
) -> Result<Status, CoconutError> {
|
||||
self.inner.get_proposal(proposal_id).await.map(|p| p.status)
|
||||
}
|
||||
|
||||
pub(crate) async fn advance_epoch_state(&self) -> Result<(), CoconutError> {
|
||||
self.inner.advance_epoch_state().await
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-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::error::DkgError;
|
||||
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;
|
||||
@@ -173,7 +172,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
) -> Result<(), DkgError> {
|
||||
debug!("DKG: verification key finalization (resharing: {resharing})");
|
||||
|
||||
verification_key_finalization(&self.dkg_client, &mut self.state, resharing)
|
||||
self.verification_key_finalization(epoch_id, resharing)
|
||||
.await
|
||||
.map_err(|source| DkgError::VerificationKeyFinalizationFailure { source })
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
&mut self,
|
||||
key: &VerificationKey,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
) -> Result<u64, CoconutError> {
|
||||
debug!("submitting derived partial verification key to the contract");
|
||||
let res = self
|
||||
.dkg_client
|
||||
@@ -468,8 +468,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
})?;
|
||||
debug!("Submitted own verification key share, proposal id {proposal_id} is attached to it. tx hash: {hash}");
|
||||
|
||||
// TODO: state.set_proposal_id(proposal_id);
|
||||
Ok(())
|
||||
Ok(proposal_id)
|
||||
}
|
||||
|
||||
/// Third step of the DKG process during which the nym api will generate its Coconut keypair
|
||||
@@ -528,11 +527,14 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
self.submit_partial_verification_key(coconut_keypair.keys.verification_key(), resharing)
|
||||
let proposal_id = self.submit_partial_verification_key(coconut_keypair.keys.verification_key(), resharing)
|
||||
.await?;
|
||||
|
||||
self.state.set_coconut_keypair(coconut_keypair).await;
|
||||
self.state.key_derivation_state_mut(epoch_id)?.completed = true;
|
||||
let derivation_state = self.state.key_derivation_state_mut(epoch_id)?;
|
||||
|
||||
derivation_state.completed = true;
|
||||
derivation_state.proposal_id = Some(proposal_id);
|
||||
|
||||
info!("DKG: Finished key derivation");
|
||||
Ok(())
|
||||
|
||||
@@ -5,50 +5,100 @@ 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 cw3::Status;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
impl<R: RngCore + CryptoRng> DkgController<R> {}
|
||||
impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
pub(crate) async fn verification_key_finalization(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
_resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
let key_finalization_state = self.state.key_finalization_state(epoch_id)?;
|
||||
|
||||
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(());
|
||||
// check if we have already executed our own proposal
|
||||
if key_finalization_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!("our key has already been verified");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let proposal_id = self.state.proposal_id(epoch_id)?;
|
||||
|
||||
// check whether our key has already been verified with executed proposal,
|
||||
// either by us in previous iteration after a timeout
|
||||
// or by another party
|
||||
let status = self.dkg_client.get_proposal_status(proposal_id).await?;
|
||||
match status {
|
||||
Status::Pending | Status::Open => {
|
||||
warn!("our proposal ({proposal_id}) still hasn't received enough votes to get accepted");
|
||||
todo!();
|
||||
}
|
||||
Status::Rejected => {
|
||||
// TODO: technically there's nothing enforcing this...
|
||||
error!("our key verification proposal ({proposal_id}) has been rejected - we can't use our derived keys!");
|
||||
todo!()
|
||||
}
|
||||
Status::Passed => {
|
||||
self.dkg_client
|
||||
.execute_verification_key_share(proposal_id)
|
||||
.await?;
|
||||
}
|
||||
Status::Executed => {
|
||||
debug!("our dkg proposal has already been executed");
|
||||
}
|
||||
}
|
||||
|
||||
self.state.key_finalization_state_mut(epoch_id)?.completed = true;
|
||||
info!("DKG: Finalized own verification key on chain");
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
// each dealer is responsible for executing its own proposals
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::coconut::tests::helpers::{
|
||||
derive_keypairs, exchange_dealings, initialise_controllers, initialise_dkg,
|
||||
submit_public_keys, validate_keys,
|
||||
};
|
||||
|
||||
#[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);
|
||||
// }
|
||||
async fn finalize_verification_key() -> anyhow::Result<()> {
|
||||
let validators = 4;
|
||||
|
||||
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;
|
||||
validate_keys(&mut controllers, false).await;
|
||||
|
||||
for controller in controllers.iter_mut() {
|
||||
let res = controller.verification_key_finalization(epoch, false).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
assert!(controller.state.key_finalization_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::Executed, proposal.status)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,8 +269,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn validate_verification_key() -> anyhow::Result<()> {
|
||||
let unused = 42;
|
||||
let validators = 1;
|
||||
let validators = 4;
|
||||
|
||||
let mut controllers = initialise_controllers(validators);
|
||||
let chain = controllers[0].chain_state.clone();
|
||||
@@ -298,19 +297,6 @@ mod tests {
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
@@ -16,8 +16,11 @@ pub struct KeyDerivationState {
|
||||
#[serde(with = "recovered_keys")]
|
||||
pub(crate) derived_partials: BTreeMap<DealingIndex, RecoveredVerificationKeys>,
|
||||
|
||||
pub(crate) proposal_id: Option<u64>,
|
||||
|
||||
pub(crate) completed: bool,
|
||||
// because we couldn't decrypt shares or there were no shares, etc
|
||||
|
||||
// TODO: because we couldn't decrypt shares or there were no shares, etc
|
||||
// failed:
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct FinalizationState {}
|
||||
pub struct FinalizationState {
|
||||
pub(crate) completed: bool,
|
||||
}
|
||||
|
||||
impl FinalizationState {
|
||||
/// Specifies whether this dealer has already registered in the particular DKG epoch
|
||||
/// Specifies whether this (or another) dealer has already executed its verification proposal
|
||||
pub fn completed(&self) -> bool {
|
||||
todo!()
|
||||
self.completed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,6 +524,10 @@ impl State {
|
||||
.receiver_index
|
||||
.ok_or(CoconutError::UnavailableReceiverIndex { epoch_id })
|
||||
}
|
||||
|
||||
pub fn proposal_id(&self, epoch_id: EpochId) -> Result<u64, CoconutError> {
|
||||
self.key_derivation_state(epoch_id)?.proposal_id.ok_or(CoconutError::UnavailableProposalId {epoch_id})
|
||||
}
|
||||
|
||||
pub fn persistent_state_path(&self) -> &Path {
|
||||
self.persistent_state_path.as_path()
|
||||
|
||||
@@ -152,6 +152,9 @@ pub enum CoconutError {
|
||||
#[error("the threshold value for epoch {epoch_id} is not available")]
|
||||
UnavailableThreshold { epoch_id: EpochId },
|
||||
|
||||
#[error("the proposal id value for epoch {epoch_id} is not available")]
|
||||
UnavailableProposalId { epoch_id: EpochId },
|
||||
|
||||
#[error("insufficient number of dealings provided to derive the key")]
|
||||
InsufficientDealings {
|
||||
// TODO: details
|
||||
|
||||
@@ -134,11 +134,10 @@ pub(crate) async fn finalize(controllers: &mut [TestingDkgController], resharing
|
||||
.epoch_id;
|
||||
|
||||
for controller in controllers.iter_mut() {
|
||||
todo!()
|
||||
// controller
|
||||
// .verification_key_finalization(epoch, resharing)
|
||||
// .await
|
||||
// .unwrap();
|
||||
controller
|
||||
.verification_key_finalization(epoch, resharing)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let mut guard = controllers[0].chain_state.lock().unwrap();
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::coconut::{self, State};
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use async_trait::async_trait;
|
||||
use cosmwasm_std::{coin, to_binary, Addr, CosmosMsg, Decimal, WasmMsg};
|
||||
use cw3::ProposalResponse;
|
||||
use cw3::{ProposalResponse, VoteResponse};
|
||||
use cw4::{Cw4Contract, MemberResponse};
|
||||
use nym_api_requests::coconut::models::{IssuedCredentialBody, IssuedCredentialResponse};
|
||||
use nym_api_requests::coconut::{
|
||||
@@ -268,16 +268,15 @@ impl super::client::Client for DummyClient {
|
||||
}
|
||||
|
||||
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse> {
|
||||
todo!()
|
||||
// self.state
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .proposals
|
||||
// .get(&proposal_id)
|
||||
// .cloned()
|
||||
// .ok_or(CoconutError::IncorrectProposal {
|
||||
// reason: String::from("proposal not found"),
|
||||
// })
|
||||
self.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.proposals
|
||||
.get(&proposal_id)
|
||||
.cloned()
|
||||
.ok_or(CoconutError::IncorrectProposal {
|
||||
reason: String::from("proposal not found"),
|
||||
})
|
||||
}
|
||||
|
||||
async fn list_proposals(&self) -> Result<Vec<ProposalResponse>> {
|
||||
@@ -291,6 +290,10 @@ impl super::client::Client for DummyClient {
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn get_vote(&self, proposal_id: u64, voter: String) -> Result<VoteResponse> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_spent_credential(
|
||||
&self,
|
||||
blinded_serial_number: String,
|
||||
@@ -445,18 +448,17 @@ impl super::client::Client for DummyClient {
|
||||
}
|
||||
|
||||
async fn execute_proposal(&self, proposal_id: u64) -> Result<()> {
|
||||
todo!()
|
||||
// self.state
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .proposals
|
||||
// .entry(proposal_id)
|
||||
// .and_modify(|prop| {
|
||||
// if prop.status == cw3::Status::Passed {
|
||||
// prop.status = cw3::Status::Executed
|
||||
// }
|
||||
// });
|
||||
// Ok(())
|
||||
self.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.proposals
|
||||
.entry(proposal_id)
|
||||
.and_modify(|prop| {
|
||||
if prop.status == cw3::Status::Passed {
|
||||
prop.status = cw3::Status::Executed
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn advance_epoch_state(&self) -> Result<()> {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::epoch_operations::MixnodeWithPerformance;
|
||||
use crate::support::config::Config;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use cw3::ProposalResponse;
|
||||
use cw3::{ProposalResponse, VoteResponse};
|
||||
use cw4::MemberResponse;
|
||||
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
|
||||
use nym_coconut_dkg_common::dealer::DealingStatusResponse;
|
||||
@@ -375,6 +375,14 @@ impl crate::coconut::client::Client for Client {
|
||||
Ok(nyxd_query!(self, get_all_proposals().await?))
|
||||
}
|
||||
|
||||
async fn get_vote(
|
||||
&self,
|
||||
proposal_id: u64,
|
||||
voter: String,
|
||||
) -> crate::coconut::error::Result<VoteResponse> {
|
||||
Ok(nyxd_query!(self, query_vote(proposal_id, voter).await?))
|
||||
}
|
||||
|
||||
async fn get_spent_credential(
|
||||
&self,
|
||||
blinded_serial_number: String,
|
||||
|
||||
Reference in New Issue
Block a user