Feature/dkg state to disk (#1872)
* Add PersistentState * Save and load state to/from disk * If in progress, don't continually write the same state * Fix tests and add serde one * Update changelog * Fix clippy
This commit is contained in:
committed by
GitHub
parent
85515fe16e
commit
ebaf99fadb
@@ -1,7 +1,9 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub(crate) enum ComplaintReason {
|
||||
MalformedBTEPublicKey,
|
||||
InvalidBTEPublicKey,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::state::{ConsistentState, State};
|
||||
use crate::coconut::dkg::state::{ConsistentState, PersistentState, State};
|
||||
use crate::coconut::dkg::verification_key::{
|
||||
verification_key_finalization, verification_key_validation,
|
||||
};
|
||||
@@ -63,12 +63,20 @@ impl<R: RngCore + Clone> DkgController<R> {
|
||||
)) {
|
||||
coconut_keypair.set(coconut_keypair_value).await;
|
||||
}
|
||||
let persistent_state =
|
||||
PersistentState::load_from_file(config.persistent_state_path()).unwrap_or_default();
|
||||
|
||||
Ok(DkgController {
|
||||
dkg_client: DkgClient::new(nymd_client),
|
||||
secret_key_path: config.secret_key_path(),
|
||||
verification_key_path: config.verification_key_path(),
|
||||
state: State::new(config.get_announce_address(), dkg_keypair, coconut_keypair),
|
||||
state: State::new(
|
||||
config.persistent_state_path(),
|
||||
persistent_state,
|
||||
config.get_announce_address(),
|
||||
dkg_keypair,
|
||||
coconut_keypair,
|
||||
),
|
||||
rng,
|
||||
polling_rate: config.get_dkg_contract_polling_rate(),
|
||||
})
|
||||
@@ -114,6 +122,13 @@ impl<R: RngCore + Clone> DkgController<R> {
|
||||
};
|
||||
if let Err(e) = ret {
|
||||
warn!("Could not handle this iteration for the epoch state: {}", e);
|
||||
} else if epoch_state != EpochState::InProgress {
|
||||
let persistent_state = PersistentState::from(&self.state);
|
||||
if let Err(e) =
|
||||
persistent_state.save_to_file(self.state.persistent_state_path())
|
||||
{
|
||||
warn!("Could not backup the state for this iteration: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ pub(crate) async fn dealing_exchange(
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::coconut::dkg::complaints::ComplaintReason;
|
||||
use crate::coconut::dkg::state::PersistentState;
|
||||
use crate::coconut::tests::DummyClient;
|
||||
use crate::coconut::KeyPair;
|
||||
use coconut_dkg_common::dealer::DealerDetails;
|
||||
@@ -63,6 +64,7 @@ pub(crate) mod tests {
|
||||
use dkg::bte::Params;
|
||||
use rand::rngs::OsRng;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use url::Url;
|
||||
@@ -110,6 +112,8 @@ pub(crate) mod tests {
|
||||
);
|
||||
let params = setup();
|
||||
let mut state = State::new(
|
||||
PathBuf::default(),
|
||||
PersistentState::default(),
|
||||
Url::parse("localhost:8000").unwrap(),
|
||||
DkgKeyPair::new(¶ms, OsRng),
|
||||
KeyPair::new(),
|
||||
@@ -163,6 +167,8 @@ pub(crate) mod tests {
|
||||
);
|
||||
let params = setup();
|
||||
let mut state = State::new(
|
||||
PathBuf::default(),
|
||||
PersistentState::default(),
|
||||
Url::parse("localhost:8000").unwrap(),
|
||||
DkgKeyPair::new(¶ms, OsRng),
|
||||
KeyPair::new(),
|
||||
|
||||
@@ -34,10 +34,12 @@ pub(crate) async fn public_key_submission(
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::coconut::dkg::state::PersistentState;
|
||||
use crate::coconut::tests::DummyClient;
|
||||
use crate::coconut::KeyPair;
|
||||
use dkg::bte::keys::KeyPair as DkgKeyPair;
|
||||
use rand::rngs::OsRng;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
use validator_client::nymd::AccountId;
|
||||
@@ -51,6 +53,8 @@ pub(crate) mod tests {
|
||||
AccountId::from_str(TEST_VALIDATOR_ADDRESS).unwrap(),
|
||||
));
|
||||
let mut state = State::new(
|
||||
PathBuf::default(),
|
||||
PersistentState::default(),
|
||||
Url::parse("localhost:8000").unwrap(),
|
||||
DkgKeyPair::new(&dkg::bte::setup(), OsRng),
|
||||
KeyPair::new(),
|
||||
|
||||
@@ -9,13 +9,33 @@ use coconut_dkg_common::types::EpochState;
|
||||
use cosmwasm_std::Addr;
|
||||
use dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof};
|
||||
use dkg::{NodeIndex, RecoveredVerificationKeys, Threshold};
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use url::Url;
|
||||
|
||||
fn bte_pk_serialize<S: Serializer>(
|
||||
val: &PublicKeyWithProof,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
val.to_bytes().serialize(serializer)
|
||||
}
|
||||
|
||||
fn bte_pk_deserialize<'de, D>(deserializer: D) -> Result<PublicKeyWithProof, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let vec: Vec<u8> = Deserialize::deserialize(deserializer)?;
|
||||
PublicKeyWithProof::try_from_bytes(&vec).map_err(|err| Error::custom(format_args!("{:?}", err)))
|
||||
}
|
||||
|
||||
// note: each dealer is also a receiver which simplifies some logic significantly
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Deserialize, Debug, Serialize)]
|
||||
pub(crate) struct DkgParticipant {
|
||||
pub(crate) _address: Addr,
|
||||
#[serde(serialize_with = "bte_pk_serialize")]
|
||||
#[serde(deserialize_with = "bte_pk_deserialize")]
|
||||
pub(crate) bte_public_key_with_proof: PublicKeyWithProof,
|
||||
pub(crate) assigned_index: NodeIndex,
|
||||
}
|
||||
@@ -71,20 +91,6 @@ pub(crate) trait ConsistentState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct State {
|
||||
announce_address: Url,
|
||||
dkg_keypair: DkgKeyPair,
|
||||
coconut_keypair: CoconutKeyPair,
|
||||
node_index: Option<NodeIndex>,
|
||||
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
|
||||
receiver_index: Option<usize>,
|
||||
threshold: Option<Threshold>,
|
||||
recovered_vks: Vec<RecoveredVerificationKeys>,
|
||||
proposal_id: Option<u64>,
|
||||
voted_vks: bool,
|
||||
executed_proposal: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ConsistentState for State {
|
||||
fn node_index_value(&self) -> Result<NodeIndex, CoconutError> {
|
||||
@@ -131,27 +137,110 @@ impl ConsistentState for State {
|
||||
}
|
||||
}
|
||||
|
||||
fn vks_serialize<S: Serializer>(
|
||||
val: &[RecoveredVerificationKeys],
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let vec: Vec<Vec<u8>> = val.iter().map(|vk| vk.to_bytes()).collect();
|
||||
vec.serialize(serializer)
|
||||
}
|
||||
|
||||
fn vks_deserialize<'de, D>(deserializer: D) -> Result<Vec<RecoveredVerificationKeys>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let vec: Vec<Vec<u8>> = Deserialize::deserialize(deserializer)?;
|
||||
vec.into_iter()
|
||||
.map(|b| {
|
||||
RecoveredVerificationKeys::try_from_bytes(&b)
|
||||
.map_err(|err| D::Error::custom(format_args!("{:?}", err)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub(crate) struct PersistentState {
|
||||
node_index: Option<NodeIndex>,
|
||||
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
|
||||
receiver_index: Option<usize>,
|
||||
threshold: Option<Threshold>,
|
||||
#[serde(serialize_with = "vks_serialize")]
|
||||
#[serde(deserialize_with = "vks_deserialize")]
|
||||
recovered_vks: Vec<RecoveredVerificationKeys>,
|
||||
proposal_id: Option<u64>,
|
||||
voted_vks: bool,
|
||||
executed_proposal: bool,
|
||||
}
|
||||
|
||||
impl From<&State> for PersistentState {
|
||||
fn from(s: &State) -> Self {
|
||||
PersistentState {
|
||||
node_index: s.node_index,
|
||||
dealers: s.dealers.clone(),
|
||||
receiver_index: s.receiver_index,
|
||||
threshold: s.threshold,
|
||||
recovered_vks: s.recovered_vks.clone(),
|
||||
proposal_id: s.proposal_id,
|
||||
voted_vks: s.voted_vks,
|
||||
executed_proposal: s.executed_proposal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PersistentState {
|
||||
pub fn save_to_file(&self, path: PathBuf) -> Result<(), CoconutError> {
|
||||
std::fs::write(path, serde_json::to_string(self)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_from_file(path: PathBuf) -> Result<Self, CoconutError> {
|
||||
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct State {
|
||||
persistent_state_path: PathBuf,
|
||||
announce_address: Url,
|
||||
dkg_keypair: DkgKeyPair,
|
||||
coconut_keypair: CoconutKeyPair,
|
||||
node_index: Option<NodeIndex>,
|
||||
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
|
||||
receiver_index: Option<usize>,
|
||||
threshold: Option<Threshold>,
|
||||
recovered_vks: Vec<RecoveredVerificationKeys>,
|
||||
proposal_id: Option<u64>,
|
||||
voted_vks: bool,
|
||||
executed_proposal: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(
|
||||
persistent_state_path: PathBuf,
|
||||
persistent_state: PersistentState,
|
||||
announce_address: Url,
|
||||
dkg_keypair: DkgKeyPair,
|
||||
coconut_keypair: CoconutKeyPair,
|
||||
) -> Self {
|
||||
State {
|
||||
persistent_state_path,
|
||||
announce_address,
|
||||
dkg_keypair,
|
||||
coconut_keypair,
|
||||
node_index: None,
|
||||
dealers: BTreeMap::new(),
|
||||
receiver_index: None,
|
||||
threshold: None,
|
||||
recovered_vks: vec![],
|
||||
proposal_id: None,
|
||||
voted_vks: false,
|
||||
executed_proposal: false,
|
||||
node_index: persistent_state.node_index,
|
||||
dealers: persistent_state.dealers,
|
||||
receiver_index: persistent_state.receiver_index,
|
||||
threshold: persistent_state.threshold,
|
||||
recovered_vks: persistent_state.recovered_vks,
|
||||
proposal_id: persistent_state.proposal_id,
|
||||
voted_vks: persistent_state.voted_vks,
|
||||
executed_proposal: persistent_state.executed_proposal,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn persistent_state_path(&self) -> PathBuf {
|
||||
self.persistent_state_path.clone()
|
||||
}
|
||||
|
||||
pub fn announce_address(&self) -> &Url {
|
||||
&self.announce_address
|
||||
}
|
||||
|
||||
@@ -249,6 +249,7 @@ pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::coconut::dkg::dealing::dealing_exchange;
|
||||
use crate::coconut::dkg::public_key::public_key_submission;
|
||||
use crate::coconut::dkg::state::PersistentState;
|
||||
use crate::coconut::tests::DummyClient;
|
||||
use crate::coconut::KeyPair;
|
||||
use coconut_dkg_common::dealer::DealerDetails;
|
||||
@@ -259,6 +260,7 @@ pub(crate) mod tests {
|
||||
use rand::Rng;
|
||||
use std::collections::HashMap;
|
||||
use std::env::temp_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use url::Url;
|
||||
@@ -289,6 +291,8 @@ pub(crate) mod tests {
|
||||
);
|
||||
let keypair = DkgKeyPair::new(¶ms, OsRng);
|
||||
let state = State::new(
|
||||
PathBuf::default(),
|
||||
PersistentState::default(),
|
||||
Url::parse("localhost:8000").unwrap(),
|
||||
keypair,
|
||||
KeyPair::new(),
|
||||
|
||||
@@ -23,6 +23,9 @@ pub enum CoconutError {
|
||||
#[error("{0}")]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
SerdeJsonError(#[from] serde_json::Error),
|
||||
|
||||
#[error("Could not parse Ed25519 data")]
|
||||
Ed25519ParseError(#[from] Ed25519RecoveryError),
|
||||
|
||||
|
||||
@@ -282,6 +282,9 @@ pub struct CoconutSigner {
|
||||
/// Specifies whether rewarding service is enabled in this process.
|
||||
enabled: bool,
|
||||
|
||||
/// Path to a JSON file where state is persisted between different stages of DKG.
|
||||
dkg_persistent_state_path: PathBuf,
|
||||
|
||||
/// Path to the coconut verification key.
|
||||
verification_key_path: PathBuf,
|
||||
|
||||
@@ -299,6 +302,7 @@ pub struct CoconutSigner {
|
||||
}
|
||||
|
||||
impl CoconutSigner {
|
||||
pub const DKG_PERSISTENT_STATE_FILE: &'static str = "dkg_persistent_state.json";
|
||||
pub const DKG_DECRYPTION_KEY_FILE: &'static str = "dkg_decryption_key.pem";
|
||||
pub const DKG_PUBLIC_KEY_WITH_PROOF_FILE: &'static str = "dkg_public_key_with_proof.pem";
|
||||
pub const COCONUT_VERIFICATION_KEY_FILE: &'static str = "coconut_verification_key.pem";
|
||||
@@ -312,6 +316,10 @@ impl CoconutSigner {
|
||||
Config::default_data_directory(None).join(Self::COCONUT_SECRET_KEY_FILE)
|
||||
}
|
||||
|
||||
fn default_dkg_persistent_state_path() -> PathBuf {
|
||||
Config::default_data_directory(None).join(Self::DKG_PERSISTENT_STATE_FILE)
|
||||
}
|
||||
|
||||
fn default_dkg_decryption_key_path() -> PathBuf {
|
||||
Config::default_data_directory(None).join(Self::DKG_DECRYPTION_KEY_FILE)
|
||||
}
|
||||
@@ -325,6 +333,7 @@ impl Default for CoconutSigner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: Default::default(),
|
||||
dkg_persistent_state_path: CoconutSigner::default_dkg_persistent_state_path(),
|
||||
verification_key_path: CoconutSigner::default_coconut_verification_key_path(),
|
||||
secret_key_path: CoconutSigner::default_coconut_secret_key_path(),
|
||||
decryption_key_path: CoconutSigner::default_dkg_decryption_key_path(),
|
||||
@@ -345,6 +354,8 @@ impl Config {
|
||||
Config::default_data_directory(Some(id)).join(NodeStatusAPI::DB_FILE);
|
||||
self.network_monitor.credentials_database_path =
|
||||
Config::default_data_directory(Some(id)).join(NetworkMonitor::DB_FILE);
|
||||
self.coconut_signer.dkg_persistent_state_path =
|
||||
Config::default_data_directory(Some(id)).join(CoconutSigner::DKG_PERSISTENT_STATE_FILE);
|
||||
self.coconut_signer.verification_key_path = Config::default_data_directory(Some(id))
|
||||
.join(CoconutSigner::COCONUT_VERIFICATION_KEY_FILE);
|
||||
self.coconut_signer.secret_key_path =
|
||||
@@ -509,6 +520,11 @@ impl Config {
|
||||
self.node_status_api.database_path.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn persistent_state_path(&self) -> PathBuf {
|
||||
self.coconut_signer.dkg_persistent_state_path.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn verification_key_path(&self) -> PathBuf {
|
||||
self.coconut_signer.verification_key_path.clone()
|
||||
|
||||
Reference in New Issue
Block a user