From 1318b4425d68b269f6e26354534a4e5a3bbd6440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 11 May 2022 12:15:28 +0100 Subject: [PATCH] Initial event emission by the contract watcher --- Cargo.lock | 1 + .../validator-client/src/client.rs | 4 + .../coconut-dkg-contract/src/dealer.rs | 3 + .../coconut-dkg-contract/src/msg.rs | 2 + .../coconut-dkg-contract/src/types.rs | 1 + common/crypto/dkg/src/bte/keys.rs | 2 +- .../crypto/dkg/src/bte/proof_discrete_log.rs | 2 +- .../coconut-dkg/src/dealers/transactions.rs | 2 + contracts/coconut-dkg/src/lib.rs | 2 + validator-api/Cargo.toml | 1 + validator-api/src/dkg/error.rs | 7 ++ validator-api/src/dkg/events/event.rs | 6 ++ validator-api/src/dkg/main_loop/mod.rs | 15 +++- validator-api/src/dkg/mod.rs | 8 ++ .../src/dkg/smart_contract/publisher/mod.rs | 11 +++ .../src/dkg/smart_contract/watcher/event.rs | 35 ++++++++- .../src/dkg/smart_contract/watcher/mod.rs | 77 ++++++++++++++++++- validator-api/src/dkg/state/accessor.rs | 19 ++++- validator-api/src/dkg/state/mod.rs | 16 +++- validator-api/src/nymd_client.rs | 13 +++- 20 files changed, 210 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a5a454063..b285fe5d1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3239,6 +3239,7 @@ dependencies = [ "async-trait", "attohttpc", "bincode", + "bs58", "bytes", "cfg-if 1.0.0", "clap 2.34.0", diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 8b6243c416..94220cf48d 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -21,8 +21,10 @@ use crate::nymd::{ error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient, }; +#[cfg(feature = "dkg")] use crate::nymd::traits::DkgClient; use crate::nymd::SigningCosmWasmClient; +#[cfg(feature = "dkg")] use coconut_dkg_common::types::DealerDetails; #[cfg(feature = "nymd-client")] use mixnet_contract_common::{ @@ -165,6 +167,7 @@ impl Client { gateway_page_limit: config.gateway_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, rewarded_set_page_limit: config.rewarded_set_page_limit, + #[cfg(feature = "dkg")] dealers_page_limit: config.dealers_page_limit, validator_api: validator_api_client, nymd: nymd_client, @@ -226,6 +229,7 @@ impl Client { gateway_page_limit: config.gateway_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, rewarded_set_page_limit: config.rewarded_set_page_limit, + #[cfg(feature = "dkg")] dealers_page_limit: config.dealers_page_limit, validator_api: validator_api_client, nymd: nymd_client, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/dealer.rs b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/dealer.rs index 943644342a..a1e30e1a84 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/dealer.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/dealer.rs @@ -16,6 +16,9 @@ pub struct DealerDetails { pub ed25519_public_key: EncodedEd25519PublicKey, pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof, pub assigned_index: NodeIndex, + // TODO: in the future, perhaps, this could get replaced by some gossip system and address books + // like in 'normal' Tendermint? + pub host: String, } #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/msg.rs index c25862abd9..d93933f189 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/msg.rs @@ -4,6 +4,7 @@ use crate::types::{BlockHeight, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -18,6 +19,7 @@ pub enum ExecuteMsg { ed25519_key: EncodedEd25519PublicKey, bte_key_with_proof: EncodedBTEPublicKeyWithProof, owner_signature: String, + host: String, }, CommitDealing { epoch_id: u32, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/types.rs index 633a41935c..8048585b74 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg-contract/src/types.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; pub use crate::dealer::{Blacklisting, BlacklistingReason, DealerDetails}; +pub use cosmwasm_std::Addr; pub type BlockHeight = u64; pub type EncodedEd25519PublicKey = String; pub type EncodedEd25519PublicKeyRef<'a> = &'a str; diff --git a/common/crypto/dkg/src/bte/keys.rs b/common/crypto/dkg/src/bte/keys.rs index cca086adb2..7bc66aa552 100644 --- a/common/crypto/dkg/src/bte/keys.rs +++ b/common/crypto/dkg/src/bte/keys.rs @@ -347,7 +347,7 @@ impl PublicKey { } } -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pub struct PublicKeyWithProof { pub(crate) key: PublicKey, diff --git a/common/crypto/dkg/src/bte/proof_discrete_log.rs b/common/crypto/dkg/src/bte/proof_discrete_log.rs index feff6c1603..9fe2230b28 100644 --- a/common/crypto/dkg/src/bte/proof_discrete_log.rs +++ b/common/crypto/dkg/src/bte/proof_discrete_log.rs @@ -13,7 +13,7 @@ use zeroize::Zeroize; const DISCRETE_LOG_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG"; -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pub struct ProofOfDiscreteLog { pub(crate) rand_commitment: G1Projective, diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index f75b098dcb..c2626d0c14 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -110,6 +110,7 @@ pub fn try_add_dealer( ed25519_key: EncodedEd25519PublicKey, bte_key_with_proof: EncodedBTEPublicKeyWithProof, owner_signature: String, + host: String, ) -> Result { // check whether this sender is eligible to become a dealer verify_dealer(deps.branch(), env.block.height, &info.sender)?; @@ -161,6 +162,7 @@ pub fn try_add_dealer( ed25519_public_key: ed25519_key, bte_public_key_with_proof: bte_key_with_proof, assigned_index: node_index, + host, }; dealers_storage::current_dealers().save(deps.storage, &info.sender, &dealer_details)?; diff --git a/contracts/coconut-dkg/src/lib.rs b/contracts/coconut-dkg/src/lib.rs index 15dd70e2ad..7b8a0d9add 100644 --- a/contracts/coconut-dkg/src/lib.rs +++ b/contracts/coconut-dkg/src/lib.rs @@ -55,6 +55,7 @@ pub fn execute( ed25519_key, bte_key_with_proof, owner_signature, + host, } => dealers::transactions::try_add_dealer( deps, env, @@ -62,6 +63,7 @@ pub fn execute( ed25519_key, bte_key_with_proof, owner_signature, + host, ), ExecuteMsg::CommitDealing { epoch_id, diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index b063a12bad..ddd0b9afff 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -50,6 +50,7 @@ schemars = { version = "0.8", features = ["preserve_order"] } # required for DKG, perhaps will get locked behind feature flag bincode = "1.3.3" +bs58 = "0.4.0" bytes = "1.1.0" coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg-contract" } dkg = { path = "../common/crypto/dkg" } diff --git a/validator-api/src/dkg/error.rs b/validator-api/src/dkg/error.rs index ac9c586755..90d0fa1fe8 100644 --- a/validator-api/src/dkg/error.rs +++ b/validator-api/src/dkg/error.rs @@ -3,6 +3,7 @@ use std::io; use thiserror::Error; +use validator_client::nymd::error::NymdError; use validator_client::ValidatorClientError; #[derive(Error, Debug)] @@ -13,12 +14,18 @@ pub enum DkgError { #[error("{0}")] ContractClient(#[from] ValidatorClientError), + #[error("{0}")] + NymdClient(#[from] NymdError), + #[error("Networking error - {0}")] Networking(#[from] io::Error), #[error("Failed to serialize message - {0}")] SerializationError(#[from] bincode::Error), + #[error("todo")] + MalformedDealerDetails, + #[error("todo")] DeserializationError, } diff --git a/validator-api/src/dkg/events/event.rs b/validator-api/src/dkg/events/event.rs index 5b63fad70d..1c3266bc2d 100644 --- a/validator-api/src/dkg/events/event.rs +++ b/validator-api/src/dkg/events/event.rs @@ -11,6 +11,12 @@ pub(crate) enum Event { DkgContractChange(watcher::Event), } +impl Event { + pub(crate) fn new_contract_change_event(event: watcher::Event) -> Self { + Event::DkgContractChange(event) + } +} + impl Display for Event { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { diff --git a/validator-api/src/dkg/main_loop/mod.rs b/validator-api/src/dkg/main_loop/mod.rs index 90be9ae624..e6b453456f 100644 --- a/validator-api/src/dkg/main_loop/mod.rs +++ b/validator-api/src/dkg/main_loop/mod.rs @@ -1,8 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::dkg::smart_contract::publisher::Publisher; use crate::dkg::smart_contract::watcher; -use crate::dkg::state::{DkgState, StateAccessor}; +use crate::dkg::state::StateAccessor; +use coconut_dkg_common::types::DealerDetails; use futures::channel::mpsc; use futures::StreamExt; @@ -12,12 +14,19 @@ pub(crate) type ContractEventsReceiver = mpsc::UnboundedReceiver pub(crate) type ContractEventsSender = mpsc::UnboundedSender; // it is driven by events received from the contract watcher -pub(crate) struct ProcessingLoop { +pub(crate) struct ProcessingLoop { state_accessor: StateAccessor, contract_events_receiver: ContractEventsReceiver, + + contract_publisher: Publisher, + // network_sender: Sender } -impl ProcessingLoop { +impl ProcessingLoop { + fn verify_dealer(&self, contract_dealer_details: &DealerDetails) { + // + } + async fn process_event(&self, event: watcher::Event) { todo!() } diff --git a/validator-api/src/dkg/mod.rs b/validator-api/src/dkg/mod.rs index 14b112c017..acbf1823fc 100644 --- a/validator-api/src/dkg/mod.rs +++ b/validator-api/src/dkg/mod.rs @@ -9,6 +9,14 @@ pub(crate) mod networking; mod smart_contract; pub(crate) mod state; +pub(crate) struct Config { + // not entirely sure what should go here just yet +} + +pub(crate) fn initiate_dkg(config: Config) { + // +} + // upon startup, the following tasks will need to be spawned: // - smart contract watcher // - main loop processing diff --git a/validator-api/src/dkg/smart_contract/publisher/mod.rs b/validator-api/src/dkg/smart_contract/publisher/mod.rs index 3f8285b3da..5810ac4922 100644 --- a/validator-api/src/dkg/smart_contract/publisher/mod.rs +++ b/validator-api/src/dkg/smart_contract/publisher/mod.rs @@ -2,7 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 use crate::Client; +use validator_client::nymd::SigningCosmWasmClient; pub(crate) struct Publisher { client: Client, } + +impl Publisher +where + C: SigningCosmWasmClient + Send + Sync, +{ + pub(crate) async fn submit_dealing_commitment(&self) { + // self.client.submit_dealing_commitment().await; + // + } +} diff --git a/validator-api/src/dkg/smart_contract/watcher/event.rs b/validator-api/src/dkg/smart_contract/watcher/event.rs index d98fd64da4..b45e144c00 100644 --- a/validator-api/src/dkg/smart_contract/watcher/event.rs +++ b/validator-api/src/dkg/smart_contract/watcher/event.rs @@ -1,18 +1,45 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use coconut_dkg_common::types::{BlockHeight, DealerDetails}; use std::fmt::{Display, Formatter}; #[derive(Debug)] -pub(crate) enum Event { - NewDealingCommitment, +pub(crate) struct Event { + height: BlockHeight, + event_type: EventType, +} + +impl Event { + pub(crate) fn new(height: BlockHeight, event_type: EventType) -> Self { + Event { height, event_type } + } } impl Display for Event { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "SmartContractWatcherEvent - ")?; + write!( + f, + "SmartContractWatcherEvent at height {}. {}", + self.height, self.event_type + ) + } +} + +#[derive(Debug)] +pub(crate) enum EventType { + NewDealerIdentity { details: DealerDetails }, + NewDealingCommitment, +} + +impl Display for EventType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "EventType - ")?; match self { - Event::NewDealingCommitment => write!(f, "NewDealingCommitment"), + EventType::NewDealerIdentity { details } => { + write!(f, "NewDealerIdentity for {}", details.address) + } + EventType::NewDealingCommitment => write!(f, "NewDealingCommitment"), } } } diff --git a/validator-api/src/dkg/smart_contract/watcher/mod.rs b/validator-api/src/dkg/smart_contract/watcher/mod.rs index 5a771ee810..0b853f85e2 100644 --- a/validator-api/src/dkg/smart_contract/watcher/mod.rs +++ b/validator-api/src/dkg/smart_contract/watcher/mod.rs @@ -2,22 +2,93 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dkg::error::DkgError; +use crate::dkg::state::StateAccessor; use crate::Client; +use coconut_dkg_common::types::EpochState; use log::warn; use std::time::Duration; use tokio::time::interval; -mod event; +pub(crate) use event::{Event, EventType}; +use validator_client::nymd::SigningCosmWasmClient; -pub(crate) use event::Event; +mod event; pub(crate) struct Watcher { client: Client, polling_rate: Duration, + state_accessor: StateAccessor, } -impl Watcher { +impl Watcher +where + C: SigningCosmWasmClient + Send + Sync, +{ + async fn try_get_new_dealers(&self) -> Result<(), DkgError> { + let known_dealers = self.state_accessor.get_known_dealers().await; + let bad_dealers = self.state_accessor.get_malformed_dealers().await; + + let current_height = self.client.current_block_height().await?.value(); + let current_dealers = self.client.get_current_dealers().await?; + for dealer in current_dealers { + let is_bad = bad_dealers.contains(&dealer.address); + // ideally we would have been accessing the hashmap by address, but it would make + // other parts inconvenient. However, we're extremely unlikely to have more than 100 dealers + // anyway, so the overhead of iterating over the entire map is minimal + let is_known = known_dealers + .values() + .any(|known_dealer| known_dealer.chain_address == dealer.address); + + // we had absolutely no idea about this dealer existing + if !is_bad || !is_known { + let watcher_event = self::Event::new( + current_height, + EventType::NewDealerIdentity { details: dealer }, + ); + self.state_accessor + .push_contract_change_event(watcher_event) + .await; + } + } + + Ok(()) + } + + async fn perform_epoch_state_based_actions(&self, state: EpochState) -> Result<(), DkgError> { + match state { + EpochState::PublicKeySubmission { .. } => self.try_get_new_dealers().await, + EpochState::DealingExchange { .. } => todo!(), + EpochState::ComplaintSubmission { .. } => todo!(), + EpochState::ComplaintVoting { .. } => todo!(), + EpochState::VerificationKeySubmission { .. } => todo!(), + EpochState::VerificationKeyMismatchSubmission { .. } => todo!(), + EpochState::VerificationKeyMismatchVoting { .. } => todo!(), + EpochState::InProgress { .. } => todo!(), + } + } + async fn poll_contract(&self) -> Result<(), DkgError> { + // based on the current epoch state (assuming it HASN'T CHANGED since last check), the following further actions have to be performed: + // (if the epoch state changed, we have to ALSO perform actions as if it was in the previous variants): + + // 1. PublicKeySubmission -> get keys of all submitted dealers and if there are any new ones, update dkg state + // 2. DealingExchange -> get commitments to dealings and if there are any new ones, update dkg state + // 3. ComplaintSubmission -> look for any complaints and if there is any, grab it and emit an event + // 4. ComplaintVoting -> grab information about any votes and if exist, emit an event + // 5. VerificationKeySubmission -> get list of who submitted their verification keys and if there are any new ones either update state or emit an event + // 6. VerificationKeyMismatchSubmission -> look for any complaints and if there is any, grab it and emit an event, + // 7. VerificationKeyMismatchVoting -> grab information about any votes and if exist, emit an event + // 8. InProgress -> No need to do anything (... I think? unless maybe there was any information about epoch transition, to be determined) + + // figure out what we need to pay attention to (for example if we're in "waiting for complaints" state, + // we don't care about identities of potential new dealers just yet) + let prior_epoch = self.state_accessor.current_epoch().await; + let current_epoch = self.client.get_dkg_epoch().await?; + + if prior_epoch.state != current_epoch.state { + todo!() + } + Ok(()) } diff --git a/validator-api/src/dkg/state/accessor.rs b/validator-api/src/dkg/state/accessor.rs index 353edcc8e2..ce01308f31 100644 --- a/validator-api/src/dkg/state/accessor.rs +++ b/validator-api/src/dkg/state/accessor.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dkg::events::{DispatcherSender, Event}; -use crate::dkg::state::{DkgState, ReceivedDealing}; -use coconut_dkg_common::types::Epoch; +use crate::dkg::smart_contract::watcher; +use crate::dkg::state::{Dealer, DkgState, IdentityBytes, ReceivedDealing}; +use coconut_dkg_common::types::{Addr, Epoch}; use crypto::asymmetric::identity; +use std::collections::{HashMap, HashSet}; use std::net::SocketAddr; // essentially some intermediary that allows either pushing events to the dispatcher or operating @@ -25,6 +27,11 @@ impl StateAccessor { } } + pub(crate) async fn push_contract_change_event(&self, event: watcher::Event) { + self.push_event(Event::new_contract_change_event(event)) + .await + } + pub(crate) async fn current_epoch(&self) -> Epoch { self.dkg_state.current_epoch().await } @@ -39,4 +46,12 @@ impl StateAccessor { pub(crate) async fn is_dealers_remote_address(&self, remote: SocketAddr) -> (bool, Epoch) { self.dkg_state.is_dealers_remote_address(remote).await } + + pub(crate) async fn get_known_dealers(&self) -> HashMap { + self.dkg_state.get_known_dealers().await + } + + pub(crate) async fn get_malformed_dealers(&self) -> HashSet { + self.dkg_state.get_malformed_dealers().await + } } diff --git a/validator-api/src/dkg/state/mod.rs b/validator-api/src/dkg/state/mod.rs index 91ad25f712..eb433d1112 100644 --- a/validator-api/src/dkg/state/mod.rs +++ b/validator-api/src/dkg/state/mod.rs @@ -6,12 +6,13 @@ use crypto::asymmetric::identity; use dkg::{bte, Dealing}; use futures::lock::Mutex; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::net::SocketAddr; use std::sync::Arc; mod accessor; +use crate::dkg::error::DkgError; pub(crate) use accessor::StateAccessor; type IdentityBytes = [u8; identity::PUBLIC_KEY_LENGTH]; @@ -81,12 +82,17 @@ pub struct ReceivedDealing { #[derive(Debug, Serialize, Deserialize)] struct DkgStateInner { + last_seen_height: BlockHeight, bte_decryption_key: bte::DecryptionKey, signing_key: identity::PublicKey, current_epoch: Epoch, expected_epoch_dealing_digests: HashMap, + + // we need to keep track of all bad dealers as well so that we wouldn't attempt to compalaint about them + // repeatedly + bad_dealers: HashSet, current_epoch_dealers: HashMap, verified_epoch_dealings: HashMap, unconfirmed_dealings: HashMap, @@ -126,4 +132,12 @@ impl DkgState { .get(&dealer.to_bytes()) .cloned() } + + pub(crate) async fn get_known_dealers(&self) -> HashMap { + self.inner.lock().await.current_epoch_dealers.clone() + } + + pub(crate) async fn get_malformed_dealers(&self) -> HashSet { + self.inner.lock().await.bad_dealers.clone() + } } diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 8d26f4658c..a9964f5ab5 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -22,8 +22,8 @@ use validator_client::nymd::error::NymdError; use validator_client::nymd::traits::DkgClient; use validator_client::nymd::{ hash::{Hash, SHA256_HASH_SIZE}, - CosmWasmClient, CosmosCoin, Fee, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient, - TendermintTime, + CosmWasmClient, CosmosCoin, Fee, Height, QueryNymdClient, SigningCosmWasmClient, + SigningNymdClient, TendermintTime, }; use validator_client::ValidatorClientError; @@ -119,6 +119,15 @@ impl Client { Ok(time) } + pub(crate) async fn current_block_height(&self) -> Result + where + C: CosmWasmClient + Sync, + { + let height = self.0.read().await.nymd.get_current_block_height().await?; + + Ok(height) + } + pub(crate) async fn get_mixnodes(&self) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync,