Compare commits

...

60 Commits

Author SHA1 Message Date
Jędrzej Stuczyński f66d87525c pausing implementation 2022-05-23 11:44:13 +01:00
Jędrzej Stuczyński 625c9b376f More dealing action
And actually receiving broadcasted data from other dealers !
2022-05-20 17:23:15 +01:00
Jędrzej Stuczyński 9ceae4667a Fixed comparing dealers addresses to only use ip 2022-05-20 17:21:22 +01:00
Jędrzej Stuczyński dc2a386cd6 Storing and retrieving epoch dealings commitments 2022-05-19 17:36:49 +01:00
Jędrzej Stuczyński 34b1fc8989 Validator API-side logic for dealing broadcast 2022-05-19 15:11:41 +01:00
Jędrzej Stuczyński 487a3a6cd8 Work on dealing commitments 2022-05-17 17:20:39 +01:00
Jędrzej Stuczyński b919bd3d35 Recovering assigned node index 2022-05-17 12:42:45 +01:00
Jędrzej Stuczyński 3b14511e16 Fixed condition for pushing new dealers 2022-05-16 17:21:15 +01:00
Jędrzej Stuczyński 11b9e6da43 Fixed dealer remova loging 2022-05-16 17:21:04 +01:00
Jędrzej Stuczyński aee491173d Fixed plaintext encoding 2022-05-16 17:08:41 +01:00
Jędrzej Stuczyński 7d92f0b70a Fixed arguments order 2022-05-16 17:08:25 +01:00
Jędrzej Stuczyński 00421c8b06 decoding validator address when validating dealer eligibility 2022-05-16 16:52:28 +01:00
Jędrzej Stuczyński 1165a61ca6 Actually attempting to register dealer keys 2022-05-13 17:29:21 +01:00
Jędrzej Stuczyński 106e179f6c ... and bte key 2022-05-13 17:15:07 +01:00
Jędrzej Stuczyński c3034738f5 changed signature verification to also include host 2022-05-13 17:09:27 +01:00
Jędrzej Stuczyński e23f0bb952 Dont push empty dealer set changes 2022-05-13 16:21:29 +01:00
Jędrzej Stuczyński 8bc12bc079 full event propagation and execution of dkg key submission 2022-05-13 16:20:34 +01:00
Jędrzej Stuczyński 34aca1d4bc Queries for blacklisted dealers 2022-05-13 15:52:22 +01:00
Jędrzej Stuczyński a3d6fc23f8 Added data field to ExecuteResult 2022-05-13 13:57:31 +01:00
Jędrzej Stuczyński 3328e06f45 Changed initialisation to ensure public key submission step takes non-zero number of blocks 2022-05-13 12:23:25 +01:00
Jędrzej Stuczyński 0e853e7f32 fixed startup 2022-05-13 12:16:04 +01:00
Jędrzej Stuczyński 7844a583bf setting internal dkg contract address 2022-05-13 12:08:11 +01:00
Jędrzej Stuczyński 4c4f9d7739 Using correct page limit for getting dealers 2022-05-12 16:07:03 +01:00
Jędrzej Stuczyński 691049b85b Temporary junky setup of all dkg components 2022-05-12 16:03:22 +01:00
Jędrzej Stuczyński 50f26940be Added utility dkg contract message to reset the entire state 2022-05-12 15:10:56 +01:00
Jędrzej Stuczyński 9f4029eb85 Added additional log statements for future debugging purposes 2022-05-12 11:28:42 +01:00
Jędrzej Stuczyński fb09e97eb6 Logic for processing dealer set changes 2022-05-12 11:12:02 +01:00
Jędrzej Stuczyński 1318b4425d Initial event emission by the contract watcher 2022-05-11 12:15:28 +01:00
Jędrzej Stuczyński 43f1194268 Parsing of dealer details 2022-05-11 12:14:39 +01:00
Jędrzej Stuczyński bd216453cc Querying contract for the current(and past) set of dkg dealers 2022-05-10 15:05:21 +01:00
Jędrzej Stuczyński 7271d19afa Adding sample ExecuteMsg to DkgClient trait 2022-05-09 12:29:59 +01:00
Jędrzej Stuczyński f85ac03cda Stub for DkgClient 2022-05-06 10:19:11 +01:00
Jędrzej Stuczyński ff9cdba941 Ability to serialize DkgState 2022-05-05 16:20:46 +01:00
Jędrzej Stuczyński 48e5fcd244 Serde for dkg types via serde_bytes 2022-05-05 16:19:16 +01:00
Jędrzej Stuczyński 9d84e15a92 Showing better errors for critical failures 2022-05-05 16:12:26 +01:00
Jędrzej Stuczyński 160046b359 Further organising 2022-05-05 14:40:56 +01:00
Jędrzej Stuczyński 72665d3878 Actually handling dkg requests 2022-05-05 12:23:17 +01:00
Jędrzej Stuczyński 5fb7ebeb63 Reorganised accessing state 2022-05-05 12:17:41 +01:00
Jędrzej Stuczyński 3963aebc97 Simplified error response handling 2022-05-05 11:02:26 +01:00
Jędrzej Stuczyński 6554916fae Merge branch 'develop' into feature/integrated-hybrid-dkg 2022-05-05 10:07:46 +01:00
Jędrzej Stuczyński d71f860751 Changes to serialization + playing around with the concept of event dispatcher 2022-05-03 15:30:33 +01:00
Jędrzej Stuczyński 6541d4e0d3 WIP 2022-04-28 09:13:43 +01:00
Jędrzej Stuczyński 1efc0847b5 Header deserialization 2022-04-27 10:18:55 +01:00
Jędrzej Stuczyński 1f0360c828 Initial building blocks for the networking part 2022-04-26 17:11:10 +01:00
Jędrzej Stuczyński 0ff602c7a7 Merge branch 'develop' into feature/integrated-hybrid-dkg 2022-04-26 10:57:50 +01:00
Jędrzej Stuczyński c541977c40 temporary epoch storage 2022-04-25 09:48:54 +01:00
Jędrzej Stuczyński 2dfb60e296 Merge with develop 2022-04-21 14:01:47 +01:00
Jędrzej Stuczyński 0d963aeb1f Advancing epoch state 2022-04-20 11:27:13 +01:00
Jędrzej Stuczyński 10d2a6eb8b Fixed test fixtures 2022-04-19 12:23:54 +01:00
Jędrzej Stuczyński d148726b4b Current epoch queries 2022-04-19 12:20:08 +01:00
Jędrzej Stuczyński ac7ed0d0dd Setting initial DKG Epoch on init 2022-04-19 11:51:28 +01:00
Jędrzej Stuczyński 4f561ba53b Moved dealers related parts to separate module 2022-04-19 11:36:51 +01:00
Jędrzej Stuczyński 9f39aec198 Type alias for EpochId 2022-04-19 11:33:49 +01:00
Jędrzej Stuczyński 22f4daaee4 Utility for removing dealers 2022-04-19 11:06:40 +01:00
Jędrzej Stuczyński e9096bcfc4 Added indices on node index for past and current dealers 2022-04-19 11:06:33 +01:00
Jędrzej Stuczyński 77320e12c4 Copy for Blacklisting 2022-04-19 11:03:59 +01:00
Jędrzej Stuczyński 67a9334328 Blacklisting related utility 2022-04-13 17:05:12 +01:00
Jędrzej Stuczyński 24f4b19e51 Initial logic for adding new dealer
contains bunch of placeholder assumptions
2022-04-13 15:01:25 +01:00
Jędrzej Stuczyński 9d2b7d6940 Checking for valid dealer + blacklisting 2022-04-13 11:57:23 +01:00
Jędrzej Stuczyński 1a8058aff5 Scafolding for DKG contract 2022-04-12 16:59:08 +01:00
81 changed files with 5283 additions and 143 deletions
Generated
+34 -2
View File
@@ -622,6 +622,16 @@ dependencies = [
"serde",
]
[[package]]
name = "coconut-dkg-common"
version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "coconut-interface"
version = "0.1.0"
@@ -722,7 +732,13 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
name = "contracts-common"
version = "0.1.0"
dependencies = [
"blake3",
"bs58",
"cosmwasm-std",
"digest 0.10.3",
"generic-array 0.14.5",
"schemars",
"serde",
]
[[package]]
@@ -1062,6 +1078,8 @@ dependencies = [
"pemstore",
"rand 0.7.3",
"rand_chacha 0.2.2",
"serde",
"serde_bytes",
"subtle-encoding",
"x25519-dalek",
]
@@ -1372,6 +1390,7 @@ dependencies = [
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"serde",
"serde_bytes",
"serde_derive",
"sha2",
"thiserror",
@@ -1426,6 +1445,7 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4"
dependencies = [
"serde",
"signature",
]
@@ -1439,6 +1459,7 @@ dependencies = [
"ed25519",
"rand 0.7.3",
"serde",
"serde_bytes",
"sha2",
"zeroize",
]
@@ -2019,6 +2040,7 @@ version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
dependencies = [
"serde",
"typenum",
"version_check",
]
@@ -3224,16 +3246,22 @@ dependencies = [
"anyhow",
"async-trait",
"attohttpc",
"bincode",
"bs58",
"bytes",
"cfg-if 1.0.0",
"clap 2.34.0",
"coconut-bandwidth-contract-common",
"coconut-dkg-common",
"coconut-interface",
"config",
"console-subscriber",
"contracts-common",
"credential-storage",
"credentials",
"crypto",
"dirs",
"dkg",
"dotenv",
"futures",
"gateway-client",
@@ -3255,12 +3283,14 @@ dependencies = [
"rocket_sync_db_pools",
"schemars",
"serde",
"serde_bytes",
"serde_json",
"sqlx",
"thiserror",
"time 0.3.9",
"tokio",
"tokio-stream",
"tokio-util 0.6.9",
"topology",
"url",
"validator-api-requests",
@@ -4827,9 +4857,9 @@ dependencies = [
[[package]]
name = "serde_bytes"
version = "0.11.5"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54"
dependencies = [
"serde",
]
@@ -6184,9 +6214,11 @@ dependencies = [
"async-trait",
"base64",
"bip39",
"coconut-dkg-common",
"coconut-interface",
"colored",
"config",
"contracts-common",
"cosmrs 0.4.1 (git+https://github.com/nymtech/cosmos-rust?branch=bugfix/account-id-length-validation)",
"cosmwasm-std",
"flate2",
+1
View File
@@ -32,6 +32,7 @@ members = [
"common/crypto/dkg",
"common/bandwidth-claim-contract",
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/coconut-dkg-contract",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/vesting-contract",
@@ -12,6 +12,8 @@ base64 = "0.13"
colored = "2.0"
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg-contract", optional = true }
contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common", optional = true }
vesting-contract = { path = "../../../contracts/vesting" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -54,3 +56,4 @@ nymd-client = [
"itertools",
"cosmwasm-std",
]
dkg = ["nymd-client", "coconut-dkg-common", "contracts-common"]
@@ -21,6 +21,12 @@ use crate::nymd::{
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
#[cfg(feature = "dkg")]
use crate::nymd::{traits::DkgClient, SigningCosmWasmClient};
#[cfg(feature = "dkg")]
use coconut_dkg_common::dealer::ContractDealingCommitment;
#[cfg(feature = "dkg")]
use coconut_dkg_common::types::{BlacklistedDealer, DealerDetails, EpochId};
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{
mixnode::DelegationEvent, ContractStateParams, Delegation, IdentityKey, Interval,
@@ -42,11 +48,15 @@ pub struct Config {
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
#[cfg(feature = "dkg")]
coconut_dkg_contract_address: Option<cosmrs::AccountId>,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
rewarded_set_page_limit: Option<u32>,
#[cfg(feature = "dkg")]
dealers_page_limit: Option<u32>,
}
#[cfg(feature = "nymd-client")]
@@ -65,11 +75,15 @@ impl Config {
mixnet_contract_address,
vesting_contract_address,
erc20_bridge_contract_address,
#[cfg(feature = "dkg")]
coconut_dkg_contract_address: None,
api_url,
mixnode_page_limit: None,
gateway_page_limit: None,
mixnode_delegations_page_limit: None,
rewarded_set_page_limit: None,
#[cfg(feature = "dkg")]
dealers_page_limit: None,
}
}
@@ -92,6 +106,15 @@ impl Config {
self.rewarded_set_page_limit = limit;
self
}
#[cfg(feature = "dkg")]
pub fn with_coconut_dkg_contract(
mut self,
coconut_dkg_contract_address: Option<cosmrs::AccountId>,
) -> Config {
self.coconut_dkg_contract_address = coconut_dkg_contract_address;
self
}
}
#[cfg(feature = "nymd-client")]
@@ -100,12 +123,16 @@ pub struct Client<C> {
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
#[cfg(feature = "dkg")]
coconut_dkg_contract_address: Option<cosmrs::AccountId>,
mnemonic: Option<bip39::Mnemonic>,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
rewarded_set_page_limit: Option<u32>,
#[cfg(feature = "dkg")]
dealers_page_limit: Option<u32>,
// ideally they would have been read-only, but unfortunately rust doesn't have such features
pub validator_api: validator_api::Client,
@@ -119,7 +146,7 @@ impl Client<SigningNymdClient> {
mnemonic: bip39::Mnemonic,
) -> Result<Client<SigningNymdClient>, ValidatorClientError> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let nymd_client = NymdClient::connect_with_mnemonic(
let mut nymd_client = NymdClient::connect_with_mnemonic(
config.network,
config.nymd_url.as_str(),
config.mixnet_contract_address.clone(),
@@ -129,16 +156,23 @@ impl Client<SigningNymdClient> {
None,
)?;
#[cfg(feature = "dkg")]
nymd_client.set_coconut_dkg_contract_address(config.coconut_dkg_contract_address.clone());
Ok(Client {
network: config.network,
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
erc20_bridge_contract_address: config.erc20_bridge_contract_address,
#[cfg(feature = "dkg")]
coconut_dkg_contract_address: config.coconut_dkg_contract_address,
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
rewarded_set_page_limit: None,
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,
})
@@ -166,7 +200,7 @@ impl Client<SigningNymdClient> {
impl Client<QueryNymdClient> {
pub fn new_query(config: Config) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let nymd_client = NymdClient::connect(
let mut nymd_client = NymdClient::connect(
config.nymd_url.as_str(),
Some(config.mixnet_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).unwrap()
@@ -187,16 +221,23 @@ impl Client<QueryNymdClient> {
),
)?;
#[cfg(feature = "dkg")]
nymd_client.set_coconut_dkg_contract_address(config.coconut_dkg_contract_address.clone());
Ok(Client {
network: config.network,
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
erc20_bridge_contract_address: config.erc20_bridge_contract_address,
#[cfg(feature = "dkg")]
coconut_dkg_contract_address: config.coconut_dkg_contract_address,
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
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,
})
@@ -219,6 +260,16 @@ impl<C> Client<C> {
self.validator_api.change_url(new_endpoint)
}
#[cfg(feature = "dkg")]
pub fn set_coconut_dkg_contract(
&mut self,
coconut_dkg_contract_address: Option<cosmrs::AccountId>,
) {
self.nymd
.set_coconut_dkg_contract_address(coconut_dkg_contract_address.clone());
self.coconut_dkg_contract_address = coconut_dkg_contract_address;
}
// use case: somebody initialised client without a contract in order to upload and initialise one
// and now they want to actually use it without making new client
pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) {
@@ -716,3 +767,115 @@ impl ApiClient {
Ok(self.validator_api.get_coconut_verification_key().await?)
}
}
// dkg-related impl block
#[cfg(feature = "dkg")]
impl<C> Client<C>
where
C: SigningCosmWasmClient + Send + Sync,
{
pub async fn get_all_nymd_current_dealers(
&self,
) -> Result<Vec<DealerDetails>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut dealers = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_current_dealers_paged(start_after.take(), self.dealers_page_limit)
.await?;
dealers.append(&mut paged_response.dealers);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(dealers)
}
pub async fn get_all_nymd_past_dealers(
&self,
) -> Result<Vec<DealerDetails>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut dealers = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_past_dealers_paged(start_after.take(), self.dealers_page_limit)
.await?;
dealers.append(&mut paged_response.dealers);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(dealers)
}
pub async fn get_all_nymd_blacklisted_dealers(
&self,
) -> Result<Vec<BlacklistedDealer>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut dealers = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_blacklisted_dealers_paged(start_after.take(), self.dealers_page_limit)
.await?;
dealers.append(&mut paged_response.blacklisted_dealers);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(dealers)
}
pub async fn get_all_nymd_epoch_dealings_commitments(
&self,
epoch_id: EpochId,
) -> Result<Vec<ContractDealingCommitment>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut commitments = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_epoch_dealings_commitments_paged(
epoch_id,
start_after.take(),
self.dealers_page_limit,
)
.await?;
commitments.append(&mut paged_response.commitments);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(commitments)
}
}
@@ -320,10 +320,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
println!("height: {}", tx_res.height);
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.tx_result.log)?,
data: tx_res.tx_result.data,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -364,6 +367,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.tx_result.log)?,
data: tx_res.tx_result.data,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -25,6 +25,7 @@ use cosmrs::proto::cosmwasm::wasm::v1::{
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
};
use cosmrs::tendermint::abci::Data;
use cosmrs::tendermint::{abci, chain};
use cosmrs::tx::{AccountNumber, Gas, SequenceNumber};
use cosmrs::{tx, AccountId, Any, Coin};
@@ -672,6 +673,8 @@ pub struct MigrateResult {
pub struct ExecuteResult {
pub logs: Vec<Log>,
pub data: Data,
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
@@ -53,6 +53,9 @@ pub enum Operation {
UpdateMixnetAddress,
CheckpointMixnodes,
ReconcileDelegations,
#[cfg(feature = "dkg")]
CommitDkgDealing,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -97,6 +100,9 @@ impl fmt::Display for Operation {
Operation::CheckpointMixnodes => f.write_str("CheckpointMixnodes"),
Operation::ReconcileDelegations => f.write_str("ReconcileDelegations"),
Operation::AdvanceCurrentEpoch => f.write_str("AdvanceCurrentEpoch"),
#[cfg(feature = "dkg")]
Operation::CommitDkgDealing => f.write_str("CommitDkgDealing"),
}
}
}
@@ -104,6 +110,7 @@ impl fmt::Display for Operation {
impl Operation {
// TODO: some value tweaking
pub fn default_gas_limit(&self) -> Gas {
let default_limit = 175_000u64.into();
match self {
Operation::Upload => 3_000_000u64.into(),
Operation::Init => 500_000u64.into(),
@@ -111,36 +118,14 @@ impl Operation {
Operation::ChangeAdmin => 80_000u64.into(),
Operation::Send => 80_000u64.into(),
Operation::BondMixnode => 175_000u64.into(),
Operation::BondMixnodeOnBehalf => 200_000u64.into(),
Operation::UnbondMixnode => 175_000u64.into(),
Operation::UnbondMixnodeOnBehalf => 175_000u64.into(),
Operation::UpdateMixnodeConfig => 175_000u64.into(),
Operation::DelegateToMixnode => 175_000u64.into(),
Operation::DelegateToMixnodeOnBehalf => 175_000u64.into(),
Operation::UndelegateFromMixnode => 175_000u64.into(),
Operation::UndelegateFromMixnodeOnBehalf => 175_000u64.into(),
Operation::BondGateway => 175_000u64.into(),
Operation::BondGatewayOnBehalf => 200_000u64.into(),
Operation::UnbondGateway => 175_000u64.into(),
Operation::UnbondGatewayOnBehalf => 200_000u64.into(),
Operation::UpdateContractSettings => 175_000u64.into(),
Operation::BeginMixnodeRewarding => 175_000u64.into(),
Operation::FinishMixnodeRewarding => 175_000u64.into(),
Operation::TrackUnbondGateway => 175_000u64.into(),
Operation::TrackUnbondMixnode => 175_000u64.into(),
Operation::WithdrawVestedCoins => 175_000u64.into(),
Operation::TrackUndelegation => 175_000u64.into(),
Operation::CreatePeriodicVestingAccount => 175_000u64.into(),
Operation::AdvanceCurrentInterval => 175_000u64.into(),
Operation::WriteRewardedSet => 175_000u64.into(),
Operation::ClearRewardedSet => 175_000u64.into(),
Operation::UpdateMixnetAddress => 80_000u64.into(),
Operation::CheckpointMixnodes => 175_000u64.into(),
Operation::ReconcileDelegations => 500_000u64.into(),
Operation::AdvanceCurrentEpoch => 175_000u64.into(),
// all operations not explicitly listed default to `175_000`
_ => default_limit,
}
}
@@ -58,6 +58,8 @@ pub struct NymdClient<C> {
mixnet_contract_address: Option<AccountId>,
vesting_contract_address: Option<AccountId>,
erc20_bridge_contract_address: Option<AccountId>,
#[cfg(feature = "dkg")]
coconut_dkg_contract_address: Option<AccountId>,
client_address: Option<Vec<AccountId>>,
custom_gas_limits: HashMap<Operation, Gas>,
simulated_gas_multiplier: f32,
@@ -78,6 +80,8 @@ impl NymdClient<QueryNymdClient> {
mixnet_contract_address,
vesting_contract_address,
erc20_bridge_contract_address,
#[cfg(feature = "dkg")]
coconut_dkg_contract_address: None,
client_address: None,
custom_gas_limits: HashMap::default(),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
@@ -112,6 +116,8 @@ impl NymdClient<SigningNymdClient> {
mixnet_contract_address,
vesting_contract_address,
erc20_bridge_contract_address,
#[cfg(feature = "dkg")]
coconut_dkg_contract_address: None,
client_address: Some(client_address),
custom_gas_limits: HashMap::default(),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
@@ -145,6 +151,8 @@ impl NymdClient<SigningNymdClient> {
mixnet_contract_address,
vesting_contract_address,
erc20_bridge_contract_address,
#[cfg(feature = "dkg")]
coconut_dkg_contract_address: None,
client_address: Some(client_address),
custom_gas_limits: HashMap::default(),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
@@ -1325,7 +1333,52 @@ impl<C> NymdClient<C> {
}
}
fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin {
// dkg-related impl block
#[cfg(feature = "dkg")]
impl<C> NymdClient<C> {
pub fn set_coconut_dkg_contract_address(
&mut self,
coconut_dkg_contract_address: Option<AccountId>,
) {
self.coconut_dkg_contract_address = coconut_dkg_contract_address;
}
pub fn coconut_dkg_contract_address(&self) -> Result<&AccountId, NymdError> {
self.coconut_dkg_contract_address
.as_ref()
.ok_or(NymdError::NoContractAddressAvailable)
}
// pub async fn submit_dealing_commitment(
// &self,
// epoch_id: u32,
// dealing_digest: [u8; 32],
// receivers: u32,
// ) -> Result<ExecuteResult, NymdError>
// where
// C: DkgClient,
// {
// let fee = self.operation_fee(Operation::CommitDkgDealing);
//
// let req = coconut_dkg_common::msg::ExecuteMsg::CommitDealing {
// epoch_id,
// dealing_digest,
// receivers,
// };
// self.client
// .execute(
// self.address(),
// self.coconut_dkg_contract_address()?,
// &req,
// fee,
// "dealing commitment",
// Vec::new(),
// )
// .await
// }
}
pub(crate) fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin {
CosmosCoin {
denom: coin.denom.parse().unwrap(),
// this might be a bit iffy, cosmwasm coin stores value as u128, while cosmos does it as u64
@@ -0,0 +1,213 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{cosmwasm_coin_to_cosmos_coin, Fee, NymdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use coconut_dkg_common::dealer::{DealerDetailsResponse, PagedCommitmentsResponse};
use coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use coconut_dkg_common::types::{
BlacklistingResponse, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, Epoch, EpochId,
MinimumDepositResponse, PagedBlacklistingResponse, PagedDealerResponse,
};
use contracts_common::commitment::ContractSafeCommitment;
use cosmrs::AccountId;
#[async_trait]
pub trait DkgClient {
async fn get_current_dkg_epoch(&self) -> Result<Epoch, NymdError>;
async fn get_dealer_details(
&self,
address: &AccountId,
) -> Result<DealerDetailsResponse, NymdError>;
async fn get_current_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError>;
async fn get_past_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError>;
async fn get_blacklisted_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedBlacklistingResponse, NymdError>;
async fn get_blacklisting(&self, dealer: String) -> Result<BlacklistingResponse, NymdError>;
async fn get_deposit_amount(&self) -> Result<MinimumDepositResponse, NymdError>;
async fn get_epoch_dealings_commitments_paged(
&self,
epoch: EpochId,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedCommitmentsResponse, NymdError>;
async fn register_dealer(
&self,
identity: EncodedEd25519PublicKey,
bte_key: EncodedBTEPublicKeyWithProof,
owner_signature: String,
listening_address: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn submit_dealing_commitment(
&self,
epoch_id: u32,
commitment: ContractSafeCommitment,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C> DkgClient for NymdClient<C>
where
C: SigningCosmWasmClient + Send + Sync,
{
async fn get_current_dkg_epoch(&self) -> Result<Epoch, NymdError> {
let request = DkgQueryMsg::GetCurrentEpoch {};
self.client
.query_contract_smart(self.coconut_dkg_contract_address()?, &request)
.await
}
async fn get_dealer_details(
&self,
address: &AccountId,
) -> Result<DealerDetailsResponse, NymdError> {
let request = DkgQueryMsg::GetDealerDetails {
dealer_address: address.to_string(),
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address()?, &request)
.await
}
async fn get_current_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError> {
let request = DkgQueryMsg::GetCurrentDealers {
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address()?, &request)
.await
}
async fn get_past_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError> {
let request = DkgQueryMsg::GetPastDealers {
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address()?, &request)
.await
}
async fn get_blacklisted_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedBlacklistingResponse, NymdError> {
let request = DkgQueryMsg::GetBlacklistedDealers {
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address()?, &request)
.await
}
async fn get_blacklisting(&self, dealer: String) -> Result<BlacklistingResponse, NymdError> {
let request = DkgQueryMsg::GetBlacklisting { dealer };
self.client
.query_contract_smart(self.coconut_dkg_contract_address()?, &request)
.await
}
async fn get_deposit_amount(&self) -> Result<MinimumDepositResponse, NymdError> {
let request = DkgQueryMsg::GetDepositAmount {};
self.client
.query_contract_smart(self.coconut_dkg_contract_address()?, &request)
.await
}
async fn get_epoch_dealings_commitments_paged(
&self,
epoch: EpochId,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedCommitmentsResponse, NymdError> {
let request = DkgQueryMsg::GetEpochDealingsCommitments {
limit: page_limit,
start_after,
epoch,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address()?, &request)
.await
}
async fn register_dealer(
&self,
identity: EncodedEd25519PublicKey,
bte_key: EncodedBTEPublicKeyWithProof,
owner_signature: String,
listening_address: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let req = DkgExecuteMsg::RegisterDealer {
ed25519_key: identity,
bte_key_with_proof: bte_key,
owner_signature,
host: listening_address,
};
let deposit = self.get_deposit_amount().await?;
self.client
.execute(
self.address(),
self.coconut_dkg_contract_address()?,
&req,
fee.unwrap_or_default(),
format!("registering {} as a dealer", self.address()),
vec![cosmwasm_coin_to_cosmos_coin(deposit.amount)],
)
.await
}
async fn submit_dealing_commitment(
&self,
epoch_id: u32,
commitment: ContractSafeCommitment,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let req = DkgExecuteMsg::CommitDealing {
epoch_id,
commitment,
};
self.client
.execute(
self.address(),
self.coconut_dkg_contract_address()?,
&req,
fee.unwrap_or_default(),
"dealing commitment",
Vec::new(),
)
.await
}
}
@@ -1,8 +1,12 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "dkg")]
mod dkg_client;
mod vesting_query_client;
mod vesting_signing_client;
#[cfg(feature = "dkg")]
pub use dkg_client::DkgClient;
pub use vesting_query_client::VestingQueryClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -0,0 +1,13 @@
[package]
name = "coconut-dkg-common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta8"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
contracts-common = { path = "../contracts-common" }
@@ -0,0 +1,251 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{
BlockHeight, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, EpochId, NodeIndex,
};
use contracts_common::commitment::ContractSafeCommitment;
use cosmwasm_std::Addr;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct DealerDetails {
pub address: Addr,
pub joined_at: BlockHeight,
pub left_at: Option<BlockHeight>,
pub blacklisting: Option<Blacklisting>,
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)]
#[serde(rename_all = "snake_case")]
pub struct Blacklisting {
pub reason: BlacklistingReason,
pub height: BlockHeight,
pub expiration: Option<BlockHeight>,
}
impl Blacklisting {
pub fn has_expired(&self, current_block: BlockHeight) -> bool {
self.expiration
.map(|expiration| expiration <= current_block)
.unwrap_or_default()
}
}
impl Display for Blacklisting {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(expiration) = self.expiration {
write!(
f,
"blacklisted at block height {}. reason given: {}. expires at: {}",
self.height, self.height, expiration
)
} else {
write!(
f,
"permanently blacklisted at block height {}. reason given: {}",
self.height, self.height
)
}
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum BlacklistingReason {
InactiveForConsecutiveEpochs,
MalformedBTEPublicKey,
InvalidBTEPublicKey,
MalformedEd25519PublicKey,
Ed25519PossessionVerificationFailure,
}
impl Display for BlacklistingReason {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
BlacklistingReason::InactiveForConsecutiveEpochs => {
write!(f, "has been inactive for multiple consecutive epochs")
}
BlacklistingReason::MalformedBTEPublicKey => {
write!(f, "provided malformed BTE Public Key")
}
BlacklistingReason::InvalidBTEPublicKey => write!(f, "provided invalid BTE Public Key"),
BlacklistingReason::MalformedEd25519PublicKey => {
write!(f, "provided malformed ed25519 Public Key")
}
BlacklistingReason::Ed25519PossessionVerificationFailure => {
write!(
f,
"failed to verify possession of provided ed25519 Public Key"
)
}
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DealerType {
Current,
Past,
Unknown,
}
impl DealerType {
pub fn is_current(&self) -> bool {
matches!(&self, DealerType::Current)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DealerDetailsResponse {
pub details: Option<DealerDetails>,
pub dealer_type: DealerType,
}
impl DealerDetailsResponse {
pub fn new(details: Option<DealerDetails>, dealer_type: DealerType) -> Self {
DealerDetailsResponse {
details,
dealer_type,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct PagedDealerResponse {
pub dealers: Vec<DealerDetails>,
pub per_page: usize,
pub start_next_after: Option<Addr>,
}
impl PagedDealerResponse {
pub fn new(
dealers: Vec<DealerDetails>,
per_page: usize,
start_next_after: Option<Addr>,
) -> Self {
PagedDealerResponse {
dealers,
per_page,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct PagedBlacklistingResponse {
pub blacklisted_dealers: Vec<BlacklistedDealer>,
pub per_page: usize,
pub start_next_after: Option<Addr>,
}
impl PagedBlacklistingResponse {
pub fn new(
blacklisted_dealers: Vec<BlacklistedDealer>,
per_page: usize,
start_next_after: Option<Addr>,
) -> Self {
PagedBlacklistingResponse {
blacklisted_dealers,
per_page,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct BlacklistedDealer {
pub dealer: Addr,
pub blacklisting: Blacklisting,
}
impl BlacklistedDealer {
pub fn new(dealer: Addr, blacklisting: Blacklisting) -> Self {
BlacklistedDealer {
dealer,
blacklisting,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct BlacklistingResponse {
pub dealer: Addr,
pub blacklisting: Option<Blacklisting>,
}
impl BlacklistingResponse {
pub fn new(dealer: Addr, blacklisting: Option<Blacklisting>) -> Self {
BlacklistingResponse {
dealer,
blacklisting,
}
}
pub fn is_blacklisted(&self, current_height: BlockHeight) -> bool {
match self.blacklisting {
None => false,
Some(blacklisting) => !blacklisting.has_expired(current_height),
}
}
pub fn unchecked_get_blacklisting(&self) -> &Blacklisting {
self.blacklisting
.as_ref()
.expect("dealer is not blacklisted")
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ContractDealingCommitment {
pub commitment: ContractSafeCommitment,
pub dealer: Addr,
pub epoch_id: EpochId,
}
impl ContractDealingCommitment {
pub fn new(commitment: ContractSafeCommitment, dealer: Addr, epoch_id: EpochId) -> Self {
ContractDealingCommitment {
commitment,
dealer,
epoch_id,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct PagedCommitmentsResponse {
pub commitments: Vec<ContractDealingCommitment>,
pub per_page: usize,
pub start_next_after: Option<Addr>,
}
impl PagedCommitmentsResponse {
pub fn new(
commitments: Vec<ContractDealingCommitment>,
per_page: usize,
start_next_after: Option<Addr>,
) -> Self {
PagedCommitmentsResponse {
commitments,
per_page,
start_next_after,
}
}
}
@@ -0,0 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod dealer;
pub mod msg;
pub mod types;
@@ -0,0 +1,74 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{
BlockHeight, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, EpochId, Threshold,
};
use contracts_common::commitment::ContractSafeCommitment;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {
pub public_key_submission_end_height: BlockHeight,
pub system_threshold: Option<Threshold>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
RegisterDealer {
ed25519_key: EncodedEd25519PublicKey,
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
owner_signature: String,
host: String,
},
CommitDealing {
epoch_id: u32,
// the commitment shall be constructed on the epoch, dealing and all receivers (as a BTreeMap)
commitment: ContractSafeCommitment,
},
// DEBUG ONLY TXs. THEY SHALL BE REMOVED BEFORE FINALISING THE CODE
// only exists for debugging purposes on local network to reset the entire state of the contract
DebugUnsafeResetAll {
init_msg: InstantiateMsg,
},
DebugAdvanceEpochState {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetCurrentEpoch {},
GetDealerDetails {
dealer_address: String,
},
GetCurrentDealers {
limit: Option<u32>,
start_after: Option<String>,
},
GetPastDealers {
limit: Option<u32>,
start_after: Option<String>,
},
GetBlacklistedDealers {
limit: Option<u32>,
start_after: Option<String>,
},
GetBlacklisting {
dealer: String,
},
GetDepositAmount {},
GetEpochDealingsCommitments {
limit: Option<u32>,
start_after: Option<String>,
epoch: EpochId,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
@@ -0,0 +1,178 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
pub use crate::dealer::{
BlacklistedDealer, Blacklisting, BlacklistingReason, BlacklistingResponse, DealerDetails,
PagedBlacklistingResponse, PagedDealerResponse,
};
pub use contracts_common::commitment::ContractSafeCommitment;
pub use cosmwasm_std::{Addr, Coin};
pub type BlockHeight = u64;
pub type EncodedEd25519PublicKey = String;
pub type EncodedEd25519PublicKeyRef<'a> = &'a str;
pub type EncodedBTEPublicKeyWithProof = String;
pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str;
pub type NodeIndex = u64;
pub type Threshold = u64;
pub type EpochId = u32;
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct Epoch {
pub id: EpochId,
pub state: EpochState,
pub state_duration: EpochStateDuration,
// TODO: need to ponder a bit whether it's actually a property of a particular epoch
pub system_threshold: Threshold,
}
impl Ord for Epoch {
// we don't care about `system_threshold` when ordering
fn cmp(&self, other: &Self) -> Ordering {
if self.id != other.id {
self.id.cmp(&other.id)
} else {
self.state.cmp(&other.state)
}
}
}
impl PartialOrd for Epoch {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Display for Epoch {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Epoch {} at state {}", self.id, self.state)
}
}
impl Epoch {
pub fn next_state(
&self,
current_time: Option<BlockHeight>,
end_time: Option<BlockHeight>,
) -> Option<Self> {
let mut advance_epoch = false;
let state = match self.state.next() {
Some(next_state) => next_state,
None => {
advance_epoch = true;
EpochState::PublicKeySubmission
}
};
let id = if advance_epoch { self.id + 1 } else { self.id };
let new_state_start = current_time.unwrap_or(self.state_duration.finish_by?);
Some(Epoch {
id,
state,
state_duration: EpochStateDuration {
begun_at: new_state_start,
finish_by: end_time,
},
system_threshold: self.system_threshold,
})
}
}
// currently (it is still extremely likely to change, we might be able to get rid of verification key-related complaints),
// the epoch can be in the following states (in order):
// 1. PublicKeySubmission -> potential dealers are submitting their BTE and ed25519 public keys to participate in dealing exchange
// 2. DealingExchange -> the actual (off-chain) dealing exchange is happening
// 3. ComplaintSubmission -> receivers submitting evidence of other dealers sending malformed data
// 4. ComplaintVoting -> (if any complaints were submitted) receivers voting on the validity of the evidence provided
// 5. VerificationKeySubmission -> receivers submitting their partial (and master) verification keys
// 6. VerificationKeyMismatchSubmission -> receivers / watchers raising issue that the submitted VK are mismatched with their local derivations
// 7. VerificationKeyMismatchVoting -> (if any complaints were submitted) receivers voting on received mismatches
// 8. InProgress -> all receivers have all their secrets derived and all is good
//
// Note: It's important that the variant ordering is not changed otherwise it would mess up the derived `PartialOrd`
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
#[serde(rename_all = "snake_case")]
pub enum EpochState {
PublicKeySubmission,
DealingExchange,
ComplaintSubmission,
ComplaintVoting,
VerificationKeySubmission,
VerificationKeyMismatchSubmission,
VerificationKeyMismatchVoting,
InProgress,
}
impl Display for EpochState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
EpochState::PublicKeySubmission => write!(f, "PublicKeySubmission"),
EpochState::DealingExchange => write!(f, "DealingExchange"),
EpochState::ComplaintSubmission => write!(f, "ComplaintSubmission"),
EpochState::ComplaintVoting => write!(f, "ComplaintVoting"),
EpochState::VerificationKeySubmission => write!(f, "VerificationKeySubmission"),
EpochState::VerificationKeyMismatchSubmission => {
write!(f, "VerificationKeyMismatchSubmission")
}
EpochState::VerificationKeyMismatchVoting => {
write!(f, "VerificationKeyMismatchVoting")
}
EpochState::InProgress => write!(f, "InProgress"),
}
}
}
impl EpochState {
pub fn next(self) -> Option<Self> {
match self {
EpochState::PublicKeySubmission => Some(EpochState::DealingExchange),
EpochState::DealingExchange => Some(EpochState::ComplaintSubmission),
EpochState::ComplaintSubmission => Some(EpochState::ComplaintVoting),
EpochState::ComplaintVoting => Some(EpochState::VerificationKeySubmission),
EpochState::VerificationKeySubmission => {
Some(EpochState::VerificationKeyMismatchSubmission)
}
EpochState::VerificationKeyMismatchSubmission => {
Some(EpochState::VerificationKeyMismatchVoting)
}
EpochState::VerificationKeyMismatchVoting => Some(EpochState::InProgress),
EpochState::InProgress => None,
}
}
pub fn all_until(&self, end: Self) -> Vec<Self> {
let mut states = vec![*self];
while states.last().unwrap() != &end {
let next_state = states.last().unwrap().next().expect("somehow reached the end of state diff -> this should be impossible under any circumstances!");
states.push(next_state);
}
states
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub struct EpochStateDuration {
pub begun_at: BlockHeight,
pub finish_by: Option<BlockHeight>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct MinimumDepositResponse {
pub amount: Coin,
}
impl MinimumDepositResponse {
pub fn new(amount: Coin) -> Self {
MinimumDepositResponse { amount }
}
}
@@ -7,4 +7,15 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bs58 = "0.4.0"
cosmwasm-std = "1.0.0-beta8"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
blake3 = { version = "1.3.1", features = ["traits-preview"], optional = true }
digest = { version = "0.10.3", optional = true }
generic-array = { version = "0.14.5", features = ["serde"], optional = true }
[features]
committable_trait = ["blake3", "digest", "generic-array"]
@@ -0,0 +1,186 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "committable_trait")]
pub use digest::{Digest, Output};
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
#[cfg(feature = "committable_trait")]
use std::marker::PhantomData;
use std::ops::Deref;
// some sane upper-bound size on commitment sizes
// currently set to 1024bits
pub const MAX_COMMITMENT_SIZE: usize = 128;
// TODO: if we are to use commitments for different types, it might make sense to introduce something like
// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)]
pub struct ContractSafeCommitment(Vec<u8>);
impl Deref for ContractSafeCommitment {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for ContractSafeCommitment {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if !self.0.is_empty() {
write!(f, "0x")?;
}
for byte in self.0.iter().take(MAX_COMMITMENT_SIZE) {
write!(f, "{:02X}", byte)?;
}
// just some sanity safeguards
if self.0.len() > MAX_COMMITMENT_SIZE {
write!(f, "...")?;
}
Ok(())
}
}
// since cosmwasm stores everything with byte representation of stringified json, it's actually more efficient
// to serialize this as a string as opposed to keeping it as vector of bytes.
// for example vec![255,255] would have string representation of "[255,255]" and will be serialized to
// [91, 50, 53, 53, 44, 50, 53, 53, 93]. the equivalent base58 encoded string `"LUv"` will be serialized to
// [34, 76, 85, 118, 34]
//
// the difference between base58 and base64 is rather minimal and I've gone with base58 for consistency sake
impl Serialize for ContractSafeCommitment {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&bs58::encode(&self.0).into_string())
}
}
impl<'de> Deserialize<'de> for ContractSafeCommitment {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
let bytes = bs58::decode(&s)
.into_vec()
.map_err(serde::de::Error::custom)?;
Ok(ContractSafeCommitment(bytes))
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct UnsupportedCommitmentSize;
#[cfg(feature = "committable_trait")]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct InconsistentCommitmentSize;
#[cfg(feature = "committable_trait")]
pub type DefaultHasher = blake3::Hasher;
#[cfg(feature = "committable_trait")]
pub trait Committable {
type DigestAlgorithm: Digest;
fn commitment_size() -> usize {
<Self::DigestAlgorithm as Digest>::output_size()
}
fn to_bytes(&self) -> Vec<u8>;
fn produce_commitment(&self) -> MessageCommitment<Self> {
MessageCommitment {
commitment: Self::DigestAlgorithm::digest(self.to_bytes()),
_message_type: Default::default(),
}
}
fn verify_commitment(&self, commitment: &MessageCommitment<Self>) -> bool {
let recomputed = self.produce_commitment();
recomputed.commitment == commitment.commitment
}
}
#[cfg(feature = "committable_trait")]
#[derive(Debug, Serialize, Deserialize)]
pub struct MessageCommitment<T>
where
T: ?Sized + Committable,
{
commitment: Output<T::DigestAlgorithm>,
#[serde(skip)]
_message_type: PhantomData<T>,
}
#[cfg(feature = "committable_trait")]
impl<T> Clone for MessageCommitment<T>
where
T: ?Sized + Committable,
{
fn clone(&self) -> Self {
MessageCommitment {
commitment: self.commitment.clone(),
_message_type: Default::default(),
}
}
}
#[cfg(feature = "committable_trait")]
impl<T> MessageCommitment<T>
where
T: ?Sized + Committable,
{
pub fn value(&self) -> &[u8] {
self.commitment.as_ref()
}
pub fn unchecked_set_value(value: &[u8]) -> Self {
MessageCommitment {
commitment: Output::<T::DigestAlgorithm>::clone_from_slice(value),
_message_type: Default::default(),
}
}
pub fn new(message: &T) -> MessageCommitment<T> {
message.produce_commitment()
}
pub fn contract_safe_commitment(&self) -> ContractSafeCommitment {
self.into()
}
pub fn is_same_as(&self, other: &ContractSafeCommitment) -> bool {
self.commitment.as_slice() == other.0
}
}
#[cfg(feature = "committable_trait")]
impl<'a, T> From<&'a MessageCommitment<T>> for ContractSafeCommitment
where
T: ?Sized + Committable,
{
fn from(commitment: &'a MessageCommitment<T>) -> Self {
ContractSafeCommitment(commitment.value().to_vec())
}
}
#[cfg(feature = "committable_trait")]
impl<'a, T> TryFrom<&'a ContractSafeCommitment> for MessageCommitment<T>
where
T: ?Sized + Committable,
{
type Error = InconsistentCommitmentSize;
fn try_from(value: &'a ContractSafeCommitment) -> Result<Self, Self::Error> {
if value.len() != <T::DigestAlgorithm as digest::Digest>::output_size() {
Err(InconsistentCommitmentSize)
} else {
Ok(MessageCommitment::unchecked_set_value(value))
}
}
}
@@ -1,4 +1,5 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod commitment;
pub mod events;
+9
View File
@@ -26,10 +26,19 @@ nymsphinx-types = { path = "../nymsphinx/types" }
pemstore = { path = "../../common/pemstore" }
config = { path="../../common/config" }
serde_bytes = { version = "0.11.6", optional = true}
[dependencies.serde_crate]
version = "1.0"
optional = true
default-features = false
package = "serde"
[dev-dependencies]
rand_chacha = "0.2"
[features]
serde = ["serde_crate", "serde_bytes", "ed25519-dalek/serde"]
asymmetric = ["x25519-dalek", "ed25519-dalek"]
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
symmetric = ["aes", "ctr", "cipher", "generic-array"]
+1
View File
@@ -21,6 +21,7 @@ rand_chacha = "0.3"
rand_core = "0.6.3"
sha2 = "0.9"
serde = "1.0"
serde_bytes = { version = "0.11.6" }
serde_derive = "1.0"
thiserror = "1.0"
zeroize = { version = "1.4", features = ["zeroize_derive"] }
+2 -2
View File
@@ -14,8 +14,8 @@ use std::collections::HashMap;
use std::ops::Neg;
use zeroize::Zeroize;
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Ciphertexts {
pub rr: [G1Projective; NUM_CHUNKS],
pub ss: [G1Projective; NUM_CHUNKS],
+42 -1
View File
@@ -9,6 +9,9 @@ use bls12_381::{G1Projective, G2Projective, Scalar};
use ff::Field;
use group::GroupEncoding;
use rand_core::RngCore;
use serde::de::Error as SerdeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes};
use zeroize::Zeroize;
#[derive(Debug, Zeroize)]
@@ -344,7 +347,7 @@ impl PublicKey {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct PublicKeyWithProof {
pub(crate) key: PublicKey,
@@ -410,6 +413,25 @@ impl PublicKeyWithProof {
}
}
impl Serialize for PublicKeyWithProof {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SerdeBytes::new(&self.to_bytes()).serialize(serializer)
}
}
impl<'d> Deserialize<'d> for PublicKeyWithProof {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
let bytes = <SerdeByteBuf>::deserialize(deserializer)?;
PublicKeyWithProof::try_from_bytes(bytes.as_ref()).map_err(SerdeError::custom)
}
}
#[derive(Debug, Zeroize)]
#[zeroize(drop)]
#[cfg_attr(test, derive(PartialEq))]
@@ -642,6 +664,25 @@ impl DecryptionKey {
}
}
impl Serialize for DecryptionKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SerdeBytes::new(&self.to_bytes()).serialize(serializer)
}
}
impl<'d> Deserialize<'d> for DecryptionKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
let bytes = <SerdeByteBuf>::deserialize(deserializer)?;
DecryptionKey::try_from_bytes(bytes.as_ref()).map_err(SerdeError::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
+6
View File
@@ -302,6 +302,12 @@ pub struct Params {
_h_prepared: G2Prepared,
}
impl Default for Params {
fn default() -> Self {
setup()
}
}
pub fn setup() -> Params {
let f0 = hash_g2(b"f0", SETUP_DOMAIN);
+2 -2
View File
@@ -67,8 +67,8 @@ impl<'a> Instance<'a> {
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ProofOfChunking {
y0: G1Projective,
bb: Vec<G1Projective>,
@@ -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,
+2 -2
View File
@@ -76,8 +76,8 @@ impl<'a> Instance<'a> {
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ProofOfSecretSharing {
ff: G1Projective,
aa: G2Projective,
+23 -1
View File
@@ -14,10 +14,13 @@ use crate::interpolation::{
use crate::{NodeIndex, Share, Threshold};
use bls12_381::{G2Projective, Scalar};
use rand_core::RngCore;
use serde::de::Error as SerdeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes};
use std::collections::BTreeMap;
use zeroize::Zeroize;
#[derive(Debug)]
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Dealing {
pub public_coefficients: PublicCoefficients,
@@ -245,6 +248,25 @@ impl Dealing {
}
}
impl Serialize for Dealing {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SerdeBytes::new(&self.to_bytes()).serialize(serializer)
}
}
impl<'d> Deserialize<'d> for Dealing {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
let bytes = <SerdeByteBuf>::deserialize(deserializer)?;
Dealing::try_from_bytes(bytes.as_ref()).map_err(SerdeError::custom)
}
}
// this assumes all dealings have been verified
pub fn try_recover_verification_keys(
dealings: &[Dealing],
+2 -5
View File
@@ -3,16 +3,13 @@
// forward-secure public key encryption scheme
pub mod bte;
pub mod dealing;
pub mod error;
pub mod interpolation;
// this entire module is a big placeholder for whatever scheme we decide to use for the
// secure channel encryption scheme, but I would assume that the top-level API would
// remain more or less the same
pub mod dealing;
pub(crate) mod share;
pub(crate) mod utils;
pub use bte::{Epoch, Params};
pub use dealing::*;
pub use share::*;
+73 -1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
use ed25519_dalek::SecretKey;
pub use ed25519_dalek::SignatureError;
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
use nymsphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
@@ -10,6 +11,13 @@ use pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use rand::{CryptoRng, RngCore};
use std::fmt::{self, Display, Formatter};
#[cfg(feature = "serde")]
use serde::de::Error as SerdeError;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "serde")]
use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes};
#[derive(Debug)]
pub enum Ed25519RecoveryError {
MalformedBytes(SignatureError),
@@ -40,6 +48,7 @@ impl fmt::Display for Ed25519RecoveryError {
impl std::error::Error for Ed25519RecoveryError {}
/// Keypair for usage in ed25519 EdDSA.
#[derive(Debug)]
pub struct KeyPair {
private_key: PrivateKey,
public_key: PublicKey,
@@ -135,6 +144,28 @@ impl PublicKey {
}
}
#[cfg(feature = "serde")]
impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'d> Deserialize<'d> for PublicKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
Ok(PublicKey(ed25519_dalek::PublicKey::deserialize(
deserializer,
)?))
}
}
impl PemStorableKey for PublicKey {
type Error = Ed25519RecoveryError;
@@ -200,6 +231,26 @@ impl PrivateKey {
}
}
#[cfg(feature = "serde")]
impl Serialize for PrivateKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'d> Deserialize<'d> for PrivateKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
Ok(PrivateKey(SecretKey::deserialize(deserializer)?))
}
}
impl PemStorableKey for PrivateKey {
type Error = Ed25519RecoveryError;
@@ -216,7 +267,7 @@ impl PemStorableKey for PrivateKey {
}
}
#[derive(Debug)]
#[derive(Copy, Clone, Debug)]
pub struct Signature(ed25519_dalek::Signature);
impl Signature {
@@ -237,3 +288,24 @@ impl Signature {
Ok(Signature(ed25519_dalek::Signature::from_bytes(bytes)?))
}
}
#[cfg(feature = "serde")]
impl Serialize for Signature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SerdeBytes::new(&self.to_bytes()).serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'d> Deserialize<'d> for Signature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
let bytes = <SerdeByteBuf>::deserialize(deserializer)?;
Signature::from_bytes(bytes.as_ref()).map_err(SerdeError::custom)
}
}
+2 -2
View File
@@ -29,5 +29,5 @@ pub use blake3;
#[cfg(feature = "symmetric")]
pub use ctr;
// TODO: this function uses all three modules: asymmetric crypto, symmetric crypto and derives key...,
// so I don't know where to put it...
#[cfg(feature = "serde")]
extern crate serde_crate as serde;
+3
View File
@@ -18,6 +18,7 @@ cfg_if::cfg_if! {
if #[cfg(network = "mainnet")] {
pub const DEFAULT_NETWORK: all::Network = all::Network::MAINNET;
pub const DENOM: &str = mainnet::DENOM;
pub const STAKE_DENOM: &str = mainnet::STAKE_DENOM;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
@@ -25,6 +26,7 @@ cfg_if::cfg_if! {
} else if #[cfg(network = "qa")] {
pub const DEFAULT_NETWORK: all::Network = all::Network::QA;
pub const DENOM: &str = qa::DENOM;
pub const STAKE_DENOM: &str = qa::STAKE_DENOM;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_ERC20_CONTRACT_ADDRESS;
@@ -32,6 +34,7 @@ cfg_if::cfg_if! {
} else if #[cfg(network = "sandbox")] {
pub const DEFAULT_NETWORK: all::Network = all::Network::SANDBOX;
pub const DENOM: &str = sandbox::DENOM;
pub const STAKE_DENOM: &str = sandbox::STAKE_DENOM;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_ERC20_CONTRACT_ADDRESS;
+1
View File
@@ -5,6 +5,7 @@ use crate::ValidatorDetails;
pub(crate) const BECH32_PREFIX: &str = "n";
pub const DENOM: &str = "unym";
pub const STAKE_DENOM: &str = "unyx";
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
+1
View File
@@ -5,6 +5,7 @@ use crate::ValidatorDetails;
pub(crate) const BECH32_PREFIX: &str = "nymt";
pub const DENOM: &str = "unymt";
pub const STAKE_DENOM: &str = "unyxt";
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt17x6pt4msccvawgxjeg5nmnygttu56tftg5l6j3";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt1t4dmskxea0avvrj8xtmu66hv7dkyg9s8059t3c";
+1
View File
@@ -5,6 +5,7 @@ use crate::ValidatorDetails;
pub(crate) const BECH32_PREFIX: &str = "nymt";
pub const DENOM: &str = "unymt";
pub const STAKE_DENOM: &str = "unyxt";
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh";
+121 -87
View File
@@ -17,9 +17,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.52"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
[[package]]
name = "arrayref"
@@ -29,9 +29,9 @@ checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "autocfg"
version = "1.0.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "az"
@@ -66,6 +66,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bech32"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5738be7561b0eeb501ef1d5c5db3f24e01ceb55fededd9b00039aada34966ad"
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -134,9 +140,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "bytemuck"
version = "1.7.3"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f"
checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc"
[[package]]
name = "byteorder"
@@ -152,9 +158,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cc"
version = "1.0.72"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
dependencies = [
"jobserver",
]
@@ -223,6 +229,31 @@ dependencies = [
"serde",
]
[[package]]
name = "coconut-dkg"
version = "0.1.0"
dependencies = [
"bech32",
"bs58",
"coconut-dkg-common",
"config",
"cosmwasm-std",
"cosmwasm-storage",
"cw-storage-plus",
"serde",
"thiserror",
]
[[package]]
name = "coconut-dkg-common"
version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -246,7 +277,10 @@ checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
name = "contracts-common"
version = "0.1.0"
dependencies = [
"bs58",
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
@@ -273,9 +307,9 @@ dependencies = [
[[package]]
name = "cosmwasm-schema"
version = "1.0.0-beta7"
version = "1.0.0-beta8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63f79866e7b2190b6b6cb06959e308183c8d9511a8530f7292073f3cddc963db"
checksum = "0d75f6a05667d8613b24171ef2c77a8bf6fb9c14f9e3aaa39aa10e0c6416ed67"
dependencies = [
"schemars",
"serde_json",
@@ -310,9 +344,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
dependencies = [
"libc",
]
@@ -380,9 +414,9 @@ dependencies = [
[[package]]
name = "curve25519-dalek"
version = "3.2.0"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
dependencies = [
"byteorder",
"digest 0.9.0",
@@ -449,9 +483,9 @@ dependencies = [
[[package]]
name = "cw2"
version = "0.13.1"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686da2d2b3b646bea15d448dc25c3e132097a7c40ef9ba7b4db741375b6181f"
checksum = "993df11574f29574dd443eb0c189484bb91bc0638b6de3e32ab7f9319c92122d"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
@@ -461,9 +495,9 @@ dependencies = [
[[package]]
name = "cw3"
version = "0.13.1"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7c7a87aed0637432c9ec39a691e533e18006dd400c67bce9f0ad643de7d52c"
checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef"
dependencies = [
"cosmwasm-std",
"cw-utils",
@@ -473,9 +507,9 @@ dependencies = [
[[package]]
name = "cw3-fixed-multisig"
version = "0.13.1"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "666afbb3bcaefc697047a0be8c4c5015be053a8456d27dd26e0b8ab114f6f5dd"
checksum = "eaa27263a2535bf0111de61a7e6f12abfda87b13d3639911c9f5e97b1f26c3d0"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
@@ -508,9 +542,9 @@ dependencies = [
[[package]]
name = "cw4"
version = "0.13.1"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d51b6e05094bfb91029f5baf5d1f39ee10f16fd61f8bf0e6f6632a5dbfb7f9"
checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
@@ -574,9 +608,9 @@ dependencies = [
[[package]]
name = "dyn-clone"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28"
[[package]]
name = "ecdsa"
@@ -592,9 +626,9 @@ dependencies = [
[[package]]
name = "ed25519"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39"
checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4"
dependencies = [
"signature",
]
@@ -688,9 +722,9 @@ dependencies = [
[[package]]
name = "fixed"
version = "1.11.0"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80a9a8cb2e34880a498f09367089339bda5e12d6f871640f947850f7113058c0"
checksum = "86d3f4dd10ddfcb0bd2b2efe9f18aff37ed48265fda3e20e5d53f046aba9d50a"
dependencies = [
"az",
"bytemuck",
@@ -749,9 +783,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.3"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
dependencies = [
"cfg-if",
"libc",
@@ -772,9 +806,9 @@ dependencies = [
[[package]]
name = "git2"
version = "0.13.25"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c"
dependencies = [
"bitflags",
"libc",
@@ -854,9 +888,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "humantime-serde"
version = "1.0.1"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c"
dependencies = [
"humantime",
"serde",
@@ -899,9 +933,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.55"
version = "0.3.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
dependencies = [
"wasm-bindgen",
]
@@ -932,15 +966,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.112"
version = "0.2.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
[[package]]
name = "libgit2-sys"
version = "0.12.26+1.3.0"
version = "0.13.2+1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494"
checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b"
dependencies = [
"cc",
"libc",
@@ -950,15 +984,15 @@ dependencies = [
[[package]]
name = "libm"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db"
[[package]]
name = "libz-sys"
version = "1.1.3"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859"
dependencies = [
"cc",
"libc",
@@ -1019,7 +1053,7 @@ dependencies = [
"schemars",
"serde",
"thiserror",
"time 0.3.6",
"time 0.3.9",
"vergen",
"vesting-contract-common",
]
@@ -1038,7 +1072,7 @@ dependencies = [
"serde",
"serde_repr",
"thiserror",
"time 0.3.6",
"time 0.3.9",
]
[[package]]
@@ -1075,9 +1109,9 @@ dependencies = [
[[package]]
name = "num_threads"
version = "0.1.2"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71a1eb3a36534514077c1e079ada2fb170ef30c47d203aa6916138cf882ecd52"
checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0"
dependencies = [
"libc",
]
@@ -1091,9 +1125,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "opaque-debug"
@@ -1186,9 +1220,9 @@ dependencies = [
[[package]]
name = "pkg-config"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "ppv-lite86"
@@ -1222,9 +1256,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.36"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-xid",
]
@@ -1260,9 +1294,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.14"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
@@ -1305,7 +1339,7 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom 0.2.3",
"getrandom 0.2.6",
]
[[package]]
@@ -1329,9 +1363,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.5.4"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"regex-syntax",
]
@@ -1389,15 +1423,15 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.4"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
[[package]]
name = "serde"
version = "1.0.133"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
@@ -1413,9 +1447,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.133"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
@@ -1435,9 +1469,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.74"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142"
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
dependencies = [
"itoa",
"ryu",
@@ -1551,9 +1585,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.85"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [
"proc-macro2",
"quote",
@@ -1604,9 +1638,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.6"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d54b9298e05179c335de2b9645d061255bcd5155f843b3e328d2cfe0a5b413"
checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
dependencies = [
"itoa",
"libc",
@@ -1616,15 +1650,15 @@ dependencies = [
[[package]]
name = "time-macros"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]]
name = "tinyvec"
version = "1.5.1"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
@@ -1637,9 +1671,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.8"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
@@ -1770,9 +1804,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.78"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -1780,9 +1814,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.78"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
dependencies = [
"bumpalo",
"lazy_static",
@@ -1795,9 +1829,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.78"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1805,9 +1839,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.78"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
dependencies = [
"proc-macro2",
"quote",
@@ -1818,9 +1852,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.78"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
[[package]]
name = "winapi"
+1 -1
View File
@@ -1,5 +1,5 @@
[workspace]
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group"]
members = ["bandwidth-claim", "coconut-bandwidth", "coconut-dkg", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group"]
[profile.release]
opt-level = 3
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "coconut-dkg"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg-contract" }
config = { path = "../../common/config"}
bs58 = "0.4"
cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] }
cosmwasm-storage = "1.0.0-beta8"
cw-storage-plus = "0.13.2"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
bech32 = "0.9.0"
thiserror = "1.0.23"
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Uint128;
// to be determined whether those should be constants or exist as contract state
pub(crate) const MINIMUM_DEPOSIT: Uint128 = Uint128::new(1_000_000_000);
// if submitted invalid keys/signatures, get blacklisted for approximately 1 day (with ~5s per block)
pub(crate) const INVALID_ED25519_BLACKLISTING_EXPIRATION: u64 = 17280;
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod storage;
pub mod transactions;
@@ -0,0 +1,109 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage;
use crate::dealers::storage::{IndexedDealersMap, BLACKLISTED_DEALERS};
use coconut_dkg_common::dealer::{
BlacklistedDealer, BlacklistingResponse, DealerDetailsResponse, DealerType,
PagedBlacklistingResponse, PagedDealerResponse,
};
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
fn query_dealers(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
underlying_map: IndexedDealersMap<'_>,
) -> StdResult<PagedDealerResponse> {
let limit = limit
.unwrap_or(storage::DEALERS_PAGE_DEFAULT_LIMIT)
.min(storage::DEALERS_PAGE_MAX_LIMIT) as usize;
let addr = start_after
.map(|addr| deps.api.addr_validate(&addr))
.transpose()?;
let start = addr.as_ref().map(Bound::exclusive);
let dealers = underlying_map
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = dealers.last().map(|dealer| dealer.address.clone());
Ok(PagedDealerResponse::new(dealers, limit, start_next_after))
}
pub fn query_dealer_details(
deps: Deps<'_>,
dealer_address: String,
) -> StdResult<DealerDetailsResponse> {
let addr = deps.api.addr_validate(&dealer_address)?;
if let Some(current) = storage::current_dealers().may_load(deps.storage, &addr)? {
return Ok(DealerDetailsResponse::new(
Some(current),
DealerType::Current,
));
}
if let Some(past) = storage::past_dealers().may_load(deps.storage, &addr)? {
return Ok(DealerDetailsResponse::new(Some(past), DealerType::Past));
}
Ok(DealerDetailsResponse::new(None, DealerType::Unknown))
}
pub fn query_current_dealers_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerResponse> {
query_dealers(deps, start_after, limit, storage::current_dealers())
}
pub fn query_past_dealers_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerResponse> {
query_dealers(deps, start_after, limit, storage::past_dealers())
}
pub fn query_blacklisted_dealers_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedBlacklistingResponse> {
let limit = limit
.unwrap_or(storage::DEALERS_PAGE_DEFAULT_LIMIT)
.min(storage::DEALERS_PAGE_MAX_LIMIT) as usize;
let addr = start_after
.map(|addr| deps.api.addr_validate(&addr))
.transpose()?;
let start = addr.as_ref().map(Bound::exclusive);
let blacklisted_dealers = BLACKLISTED_DEALERS
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|(addr, blacklisting)| BlacklistedDealer::new(addr, blacklisting)))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = blacklisted_dealers
.last()
.map(|dealer| dealer.dealer.clone());
Ok(PagedBlacklistingResponse::new(
blacklisted_dealers,
limit,
start_next_after,
))
}
pub fn query_blacklisting(deps: Deps<'_>, dealer: String) -> StdResult<BlacklistingResponse> {
let addr = deps.api.addr_validate(&dealer)?;
let blacklisting = BLACKLISTED_DEALERS.may_load(deps.storage, &addr)?;
Ok(BlacklistingResponse::new(addr, blacklisting))
}
@@ -0,0 +1,106 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::{
Blacklisting, BlacklistingReason, BlockHeight, DealerDetails, NodeIndex,
};
use cosmwasm_std::{Addr, StdResult, Storage};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, UniqueIndex};
const CURRENT_DEALERS_PK: &str = "crd";
const PAST_DEALERS_PK: &str = "ptd";
const DEALERS_NODE_INDEX_IDX_NAMESPACE: &str = "dni";
pub(crate) const DEALERS_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const DEALERS_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) const BLACKLISTED_DEALERS: Map<'_, &'_ Addr, Blacklisting> = Map::new("bld");
pub(crate) const NODE_INDEX_COUNTER: Item<NodeIndex> = Item::new("node_index_counter");
pub(crate) type IndexedDealersMap<'a> = IndexedMap<'a, &'a Addr, DealerDetails, DealersIndex<'a>>;
pub(crate) struct DealersIndex<'a> {
pub(crate) node_index: UniqueIndex<'a, NodeIndex, DealerDetails>,
}
impl<'a> IndexList<DealerDetails> for DealersIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<DealerDetails>> + '_> {
let v: Vec<&dyn Index<DealerDetails>> = vec![&self.node_index];
Box::new(v.into_iter())
}
}
pub(crate) fn current_dealers<'a>() -> IndexedDealersMap<'a> {
let indexes = DealersIndex {
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
};
IndexedMap::new(CURRENT_DEALERS_PK, indexes)
}
pub(crate) fn past_dealers<'a>() -> IndexedDealersMap<'a> {
let indexes = DealersIndex {
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
};
IndexedMap::new(PAST_DEALERS_PK, indexes)
}
pub(crate) fn next_node_index(store: &mut dyn Storage) -> StdResult<NodeIndex> {
// make sure we don't start from 0, otherwise all the crypto breaks (kinda)
let id: NodeIndex = NODE_INDEX_COUNTER.may_load(store)?.unwrap_or_default() + 1;
NODE_INDEX_COUNTER.save(store, &id)?;
Ok(id)
}
pub(crate) fn blacklist_dealer(
store: &mut dyn Storage,
dealer: &Addr,
reason: BlacklistingReason,
current_block_height: BlockHeight,
expiration: Option<BlockHeight>,
) -> StdResult<()> {
let blacklisting = Blacklisting {
reason,
height: current_block_height,
expiration,
};
BLACKLISTED_DEALERS.save(store, dealer, &blacklisting)
}
pub(crate) fn obtain_blacklisting(
store: &mut dyn Storage,
dealer: &Addr,
current_height: BlockHeight,
) -> StdResult<Option<(Blacklisting, bool)>> {
if let Some(blacklisting) = BLACKLISTED_DEALERS.may_load(store, dealer)? {
if !blacklisting.has_expired(current_height) {
Ok(Some((blacklisting, false)))
} else {
// remove the blacklisting if it has expired
BLACKLISTED_DEALERS.remove(store, dealer);
Ok(Some((blacklisting, true)))
}
} else {
Ok(None)
}
}
pub(crate) fn remove_dealer(
store: &mut dyn Storage,
dealer: &Addr,
current_height: BlockHeight,
blacklisting: Option<Blacklisting>,
) -> StdResult<()> {
let mut dealer_data = current_dealers().load(store, dealer)?;
// optimisation as "normal" `remove` calls `may_load` to get old data (which we already have), followed by `replace`
current_dealers().replace(store, dealer, None, Some(&dealer_data))?;
dealer_data.left_at = Some(current_height);
dealer_data.blacklisting = blacklisting;
if let Some(blacklisting) = blacklisting {
BLACKLISTED_DEALERS.save(store, dealer, &blacklisting)?;
}
past_dealers().save(store, dealer, &dealer_data)
}
@@ -0,0 +1,193 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::{INVALID_ED25519_BLACKLISTING_EXPIRATION, MINIMUM_DEPOSIT};
use crate::dealers::storage as dealers_storage;
use crate::ContractError;
use coconut_dkg_common::types::{
BlacklistingReason, BlockHeight, DealerDetails, EncodedBTEPublicKeyWithProof,
EncodedBTEPublicKeyWithProofRef, EncodedEd25519PublicKey, EncodedEd25519PublicKeyRef,
};
use config::defaults::STAKE_DENOM;
use cosmwasm_std::{Addr, Coin, Deps, DepsMut, Env, MessageInfo, Response};
// currently we only require that
// a) it's a validator
// b) it wasn't blacklisted
// c) it isn't already a dealer
fn verify_dealer(
deps: DepsMut<'_>,
current_height: BlockHeight,
dealer: &Addr,
) -> Result<(), ContractError> {
if let Some((blacklisting, expired)) =
dealers_storage::obtain_blacklisting(deps.storage, dealer, current_height)?
{
if !expired {
return Err(ContractError::BlacklistedDealer {
reason: blacklisting,
});
}
}
if dealers_storage::current_dealers()
.may_load(deps.storage, dealer)?
.is_some()
{
return Err(ContractError::AlreadyADealer);
}
// validator addresses are using a different prefix, so we need to check if their underlying
// data is the same as the senders address
// for example, for sender `nymt1qs3k39g2sw7qcqn0u2eedkpszl02hu605uymu2`, where bech32 prefix is "nymt",
// the associated validator address is `nymtvaloper1qs3k39g2sw7qcqn0u2eedkpszl02hu60jph657`
// this is impossible to fail as the address has been validated by the validator's api,
// but catch it with an error just in case anyway...
let (_, data, _) =
bech32::decode(dealer.as_ref()).map_err(|_| ContractError::InvalidValidatedAddress {
address: dealer.clone(),
})?;
let all_validators = deps.querier.query_all_validators()?;
if !all_validators.iter().any(|validator| {
bech32::decode(&validator.address)
.map(|(_, validator_data, _)| validator_data == data)
.unwrap_or_default()
}) {
return Err(ContractError::NotAValidator);
}
Ok(())
}
fn validate_dealer_deposit(mut deposit: Vec<Coin>) -> Result<Coin, ContractError> {
// check if anything was put as deposit
if deposit.is_empty() {
return Err(ContractError::NoDepositFound);
}
if deposit.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
// check that the denomination is correct
if deposit[0].denom != STAKE_DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have at least MINIMUM_DEPOSIT coins in our deposit
if deposit[0].amount < MINIMUM_DEPOSIT {
return Err(ContractError::InsufficientDeposit {
received: deposit[0].amount.into(),
minimum: MINIMUM_DEPOSIT.into(),
});
}
// the unwrap would have been safe here under all circumstances, since we checked whether the vector is empty
// but in case something did change, change option into an error
deposit.pop().ok_or(ContractError::NoDepositFound)
}
pub(crate) fn validate_key_possession_signature(
deps: Deps<'_>,
owner: &Addr,
signature: String,
encoded_key: EncodedEd25519PublicKeyRef<'_>,
encoded_bte_key: EncodedBTEPublicKeyWithProofRef<'_>,
host: &str,
) -> Result<(), ContractError> {
let mut key_bytes = [0u8; 32];
let mut signature_bytes = [0u8; 64];
bs58::decode(encoded_key)
.into(&mut key_bytes)
.map_err(ContractError::MalformedEd25519PublicKey)?;
bs58::decode(signature)
.into(&mut signature_bytes)
.map_err(ContractError::MalformedEd25519Signature)?;
let mut plaintext = owner.to_string();
plaintext.push_str(host);
plaintext.push_str(encoded_bte_key);
let res = deps
.api
.ed25519_verify(plaintext.as_bytes(), &signature_bytes, &key_bytes)?;
if !res {
Err(ContractError::InvalidEd25519Signature)
} else {
Ok(())
}
}
pub fn try_add_dealer(
mut deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
ed25519_key: EncodedEd25519PublicKey,
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
owner_signature: String,
host: String,
) -> Result<Response, ContractError> {
// TODO: check if we're in correct epoch state
// check whether this sender is eligible to become a dealer
verify_dealer(deps.branch(), env.block.height, &info.sender)?;
// check if this dealer actually has control of his ed25519 key
// (BTE key has a proof assigned so if a malformed key is provided, somebody should complaint
// and then get this dealers deposit for themselves)
if let Err(err) = validate_key_possession_signature(
deps.as_ref(),
&info.sender,
owner_signature,
&ed25519_key,
&bte_key_with_proof,
&host,
) {
dealers_storage::blacklist_dealer(
deps.storage,
&info.sender,
BlacklistingReason::Ed25519PossessionVerificationFailure,
env.block.height,
Some(env.block.height + INVALID_ED25519_BLACKLISTING_EXPIRATION),
)?;
return Err(err);
}
// validate and extract sent deposit
let _deposit = validate_dealer_deposit(info.funds)?;
// if it was already a dealer in the past, assign the same node index
let node_index = if let Some(prior_details) =
dealers_storage::past_dealers().may_load(deps.storage, &info.sender)?
{
// since this dealer is going to become active now, remove it from the past dealers
dealers_storage::past_dealers().replace(
deps.storage,
&info.sender,
None,
Some(&prior_details),
)?;
prior_details.assigned_index
} else {
dealers_storage::next_node_index(deps.storage)?
};
// save the dealer into the storage
let dealer_details = DealerDetails {
address: info.sender.clone(),
joined_at: env.block.height,
left_at: None,
blacklisting: None,
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)?;
Ok(Response::new().set_data(node_index.to_be_bytes()))
}
@@ -0,0 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod storage;
pub mod transactions;
@@ -0,0 +1,47 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealings::storage;
use crate::dealings::storage::DEALING_COMMITMENTS;
use coconut_dkg_common::dealer::{ContractDealingCommitment, PagedCommitmentsResponse};
use coconut_dkg_common::types::EpochId;
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
pub fn query_epoch_dealings_commitments_paged(
deps: Deps<'_>,
epoch: EpochId,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedCommitmentsResponse> {
let limit = limit
.unwrap_or(storage::COMMITMENTS_PAGE_DEFAULT_LIMIT)
.min(storage::COMMITMENTS_PAGE_MAX_LIMIT) as usize;
let addr = start_after
.map(|addr| deps.api.addr_validate(&addr))
.transpose()?;
let start = addr.as_ref().map(Bound::exclusive);
let commitments = DEALING_COMMITMENTS
.prefix(epoch)
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| {
res.map(|(dealer, commitment)| {
ContractDealingCommitment::new(commitment, dealer, epoch)
})
})
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = commitments
.last()
.map(|commitment| commitment.dealer.clone());
Ok(PagedCommitmentsResponse::new(
commitments,
limit,
start_next_after,
))
}
@@ -0,0 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::{ContractSafeCommitment, EpochId};
use cosmwasm_std::Addr;
use cw_storage_plus::Map;
pub(crate) const COMMITMENTS_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const COMMITMENTS_PAGE_DEFAULT_LIMIT: u32 = 50;
type CommitmentKey<'a> = (EpochId, &'a Addr);
// Note to whoever is looking at this implementation and is thinking of using something similar
// for storing small commitments/hashes of data on chain:
// If there's a lot of entries you want to store thinking, "oh, this digest is only 32 bytes, it's not that much",
// the default cosmwasm' serializer will bloat it to around ~100B. So you really don't want to be using
// Buckets/Maps, etc. for that purpose. Instead you want to use `storage` directly (look into the actual implementation of
// `Map` or `Bucket` to see what I mean. Instead of using the `to_vec` method on serde_json_wasm, you'd
// provide your data directly yourself.
// but you must be extremely careful when doing so, as you might end up overwriting some existing data
// if you don't choose your prefixes wisely.
// I didn't have to do it here as I'm storing relatively little data and after just base58-encoding
// my bytes, I was fine with the json overhead.
pub(crate) const DEALING_COMMITMENTS: Map<'_, CommitmentKey<'_>, ContractSafeCommitment> =
Map::new("dcmt");
@@ -0,0 +1,47 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage as dealers_storage;
use crate::dealings::storage::DEALING_COMMITMENTS;
use crate::epoch::storage as epoch_storage;
use crate::ContractError;
use coconut_dkg_common::types::ContractSafeCommitment;
use cosmwasm_std::{DepsMut, MessageInfo, Response};
pub fn try_commit_dealing(
deps: DepsMut<'_>,
info: MessageInfo,
epoch_id: u32,
commitment: ContractSafeCommitment,
) -> Result<Response, ContractError> {
let current_epoch = epoch_storage::current_epoch(deps.storage)?;
// TODO: check if we're in correct epoch state (i.e. current_epoch.state)
// ensure the sender is a dealer for the current epoch
if dealers_storage::current_dealers()
.may_load(deps.storage, &info.sender)?
.is_none()
{
return Err(ContractError::NotADealer);
}
// make sure the dealer wants to submit commitment for THIS epoch
if current_epoch.id != epoch_id {
return Err(ContractError::MismatchedEpoch {
current: current_epoch.id,
request_for: epoch_id,
});
}
// check if this dealer has already committed to a dealing
// (we don't want to allow overwriting it as some receivers might already be using the commitment)
let storage_key = (epoch_id, &info.sender);
if DEALING_COMMITMENTS.has(deps.storage, storage_key) {
return Err(ContractError::AlreadyCommitted);
}
DEALING_COMMITMENTS.save(deps.storage, storage_key, &commitment)?;
Ok(Response::new())
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod storage;
pub mod transactions;
@@ -0,0 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::ContractError;
use coconut_dkg_common::types::Epoch;
use cosmwasm_std::Storage;
pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
storage::current_epoch(storage)
}
@@ -0,0 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::ContractError;
use coconut_dkg_common::types::Epoch;
use cosmwasm_std::Storage;
use cw_storage_plus::Item;
pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch");
pub(crate) fn current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
CURRENT_EPOCH
.load(storage)
.map_err(|_| ContractError::EpochNotInitialised)
}
@@ -0,0 +1,2 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::{Blacklisting, EpochId};
use config::defaults::STAKE_DENOM;
use cosmwasm_std::{Addr, StdError, VerificationError};
use thiserror::Error;
/// Custom errors for contract failure conditions.
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("No coin was sent for the deposit, you must send {}", STAKE_DENOM)]
NoDepositFound,
#[error("Received multiple coin types")]
MultipleDenoms,
#[error("Wrong coin denomination, you must send {}", STAKE_DENOM)]
WrongDenom,
#[error("Not enough funds sent for deposit. (received {received}, minimum {minimum})")]
InsufficientDeposit { received: u128, minimum: u128 },
#[error("Failed to recover ed25519 public key from its base58 representation - {0}. This dealer will be temporarily blacklisted now.")]
MalformedEd25519PublicKey(bs58::decode::Error),
#[error("Failed to recover ed25519 signature from its base58 representation - {0}. This dealer will be temporarily blacklisted now.")]
MalformedEd25519Signature(bs58::decode::Error),
#[error("Failed to perform ed25519 signature verification - {0}. This dealer will be temporarily blacklisted now.")]
Ed25519VerificationError(#[from] VerificationError),
#[error("Provided ed25519 signature did not verify correctly. This dealer will be temporarily blacklisted now.")]
InvalidEd25519Signature,
#[error("This dealer has been blacklisted - {reason}")]
BlacklistedDealer { reason: Blacklisting },
#[error("This potential dealer is not a validator")]
NotAValidator,
#[error("This sender is already a dealer for the epoch")]
AlreadyADealer,
#[error("Epoch hasn't been correctly initialised!")]
EpochNotInitialised,
#[error("Attempted to set the current epoch state to finish in the past")]
EpochStateFinishInPast,
// we should never ever see this error (famous last words in programming), therefore, I'd want to
// explicitly declare it so that when we ultimate do see it, it's gonna be more informative over "normal" panic
#[error("Somehow our validated address {address} is not using correct bech32 encoding")]
InvalidValidatedAddress { address: Addr },
#[error("This sender is not a dealer for the current epoch")]
NotADealer,
#[error("The current epoch does not match the one present in the request. Current: {current}, in request: {request_for}")]
MismatchedEpoch {
current: EpochId,
request_for: EpochId,
},
#[error("This dealer has already commited dealing for this epoch")]
AlreadyCommitted,
}
+220
View File
@@ -0,0 +1,220 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::MINIMUM_DEPOSIT;
use crate::dealers::queries::{
query_blacklisted_dealers_paged, query_blacklisting, query_current_dealers_paged,
query_dealer_details, query_past_dealers_paged,
};
use crate::dealings::queries::query_epoch_dealings_commitments_paged;
use crate::epoch::queries::query_current_epoch;
use crate::error::ContractError;
use coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use coconut_dkg_common::types::{Epoch, EpochState, MinimumDepositResponse};
use config::defaults::STAKE_DENOM;
use cosmwasm_std::{
entry_point, to_binary, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
};
use epoch::storage as epoch_storage;
mod constants;
mod dealers;
mod dealings;
mod epoch;
mod error;
mod support;
/// Instantiate the contract.
///
/// `deps` contains Storage, API and Querier
/// `env` contains block, message and contract info
/// `msg` is the contract initialization message, sort of like a constructor call.
#[entry_point]
pub fn instantiate(
deps: DepsMut<'_>,
env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
if msg.public_key_submission_end_height < env.block.height {
return Err(ContractError::EpochStateFinishInPast);
}
// if threshold was not provided in arguments, use ceil(2/3 of validators)
let system_threshold = if let Some(system_threshold) = msg.system_threshold {
system_threshold
} else {
let validators = deps.querier.query_all_validators()?.len() as u64;
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
(2 * validators + 3 - 1) / 3
};
epoch_storage::CURRENT_EPOCH.save(
deps.storage,
&Epoch {
id: 0,
state: EpochState::PublicKeySubmission {
begun_at: env.block.height,
finish_by: msg.public_key_submission_end_height,
},
system_threshold,
},
)?;
Ok(Response::default())
}
/// Handle an incoming message
#[entry_point]
pub fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::RegisterDealer {
ed25519_key,
bte_key_with_proof,
owner_signature,
host,
} => dealers::transactions::try_add_dealer(
deps,
env,
info,
ed25519_key,
bte_key_with_proof,
owner_signature,
host,
),
ExecuteMsg::CommitDealing {
epoch_id,
commitment,
} => dealings::transactions::try_commit_dealing(deps, info, epoch_id, commitment),
ExecuteMsg::DebugUnsafeResetAll { init_msg } => {
reset_contract_state(deps, env, info, init_msg)
}
ExecuteMsg::DebugAdvanceEpochState {} => advance_epoch_state(deps, env),
}
}
fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response, ContractError> {
const STATE_LENGTH: u64 = 1000;
let epoch = epoch_storage::CURRENT_EPOCH.load(deps.storage)?;
let next = epoch
.next_state(
Some(env.block.height),
Some(env.block.height + STATE_LENGTH),
)
.unwrap();
epoch_storage::CURRENT_EPOCH.save(deps.storage, &next)?;
Ok(Response::default())
}
fn reset_contract_state(
mut deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
init_msg: InstantiateMsg,
) -> Result<Response, ContractError> {
// this resets the epoch
instantiate(deps.branch(), env, info, init_msg)?;
// clear all dealings, public keys, etc
let current = dealers::storage::current_dealers()
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<Result<Vec<_>, _>>()?;
let past = dealers::storage::past_dealers()
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<Result<Vec<_>, _>>()?;
let blacklisted = crate::dealers::storage::BLACKLISTED_DEALERS
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<Result<Vec<_>, _>>()?;
let commitments = crate::dealings::storage::DEALING_COMMITMENTS
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<Result<Vec<_>, _>>()?;
for dealer in current {
dealers::storage::current_dealers().remove(deps.storage, &dealer)?;
}
for dealer in past {
dealers::storage::past_dealers().remove(deps.storage, &dealer)?;
}
for dealer in blacklisted {
dealers::storage::BLACKLISTED_DEALERS.remove(deps.storage, &dealer);
}
for (epoch, addr) in commitments {
dealings::storage::DEALING_COMMITMENTS.remove(deps.storage, (epoch, &addr));
}
crate::dealers::storage::NODE_INDEX_COUNTER.save(deps.storage, &0u64)?;
Ok(Response::default())
}
#[entry_point]
pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let response = match msg {
QueryMsg::GetCurrentEpoch {} => to_binary(&query_current_epoch(deps.storage)?)?,
QueryMsg::GetDealerDetails { dealer_address } => {
to_binary(&query_dealer_details(deps, dealer_address)?)?
}
QueryMsg::GetCurrentDealers { limit, start_after } => {
to_binary(&query_current_dealers_paged(deps, start_after, limit)?)?
}
QueryMsg::GetPastDealers { limit, start_after } => {
to_binary(&query_past_dealers_paged(deps, start_after, limit)?)?
}
QueryMsg::GetBlacklistedDealers { limit, start_after } => {
to_binary(&query_blacklisted_dealers_paged(deps, start_after, limit)?)?
}
QueryMsg::GetBlacklisting { dealer } => to_binary(&query_blacklisting(deps, dealer)?)?,
QueryMsg::GetDepositAmount {} => to_binary(&MinimumDepositResponse::new(Coin::new(
MINIMUM_DEPOSIT.u128(),
STAKE_DENOM,
)))?,
QueryMsg::GetEpochDealingsCommitments {
limit,
epoch,
start_after,
} => to_binary(&query_epoch_dealings_commitments_paged(
deps,
epoch,
start_after,
limit,
)?)?,
};
Ok(response)
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
}
#[cfg(test)]
mod tests {
use super::*;
use config::defaults::DENOM;
use cosmwasm_std::coins;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {
public_key_submission_end_height: env.block.height + 123,
system_threshold: None,
};
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), env.clone(), info, msg);
assert!(res.is_ok())
}
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::Epoch;
pub mod storage;
pub(crate) struct State {
current_epoch: Epoch,
// keep track of the next epoch if it's in the process of sharing dealings, etc.
upcoming_epoch: Option<Epoch>,
}
@@ -0,0 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::ContractError;
use coconut_dkg_common::types::Epoch;
use cosmwasm_std::Storage;
use cw_storage_plus::Item;
pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch");
pub(crate) fn current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
CURRENT_EPOCH
.load(storage)
.map_err(|_| ContractError::EpochNotInitialised)
}
+3
View File
@@ -0,0 +1,3 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod tests;
@@ -0,0 +1,22 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod helpers {
use crate::instantiate;
use coconut_dkg_common::msg::InstantiateMsg;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps};
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {
public_key_submission_end_height: env.block.height + 123,
system_threshold: None,
};
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env, info, msg).unwrap();
deps
}
}
+3
View File
@@ -725,7 +725,10 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
name = "contracts-common"
version = "0.1.0"
dependencies = [
"bs58",
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
+13 -2
View File
@@ -47,16 +47,27 @@ okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] }
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" }
contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common", features = ["committable_trait"] }
dkg = { path = "../common/crypto/dkg" }
serde_bytes = { version = "0.11.6" }
tokio-util = { version = "0.6", features = ["codec"] }
## internal
coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
config = { path = "../common/config" }
crypto = { path="../common/crypto" }
crypto = { path="../common/crypto", features = ["asymmetric", "serde"] }
gateway-client = { path="../common/client-libs/gateway-client" }
mixnet-contract-common = { path= "../common/cosmwasm-smart-contracts/mixnet-contract" }
nymsphinx = { path="../common/nymsphinx" }
topology = { path="../common/topology" }
validator-api-requests = { path = "validator-api-requests" }
validator-client = { path="../common/client-libs/validator-client", features = ["nymd-client"] }
validator-client = { path="../common/client-libs/validator-client", features = ["nymd-client", "dkg"] }
version-checker = { path="../common/version-checker" }
coconut-interface = { path = "../common/coconut-interface", optional = true }
credentials = { path = "../common/credentials", optional = true }
@@ -0,0 +1,45 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::networking::message::NewDealingMessage;
use crate::dkg::state::DkgState;
use futures::channel::mpsc;
use futures::StreamExt;
use log::{debug, error};
// Once the DKG epoch begins, all parties will begin exchanging dealings with each other.
// We really don't want to be processing all of them in parallel since we would starve other
// parts of the process of CPU. Hence we put them in a queue and process each of them one by one.
pub(crate) type DealingReceiver = mpsc::UnboundedReceiver<NewDealingMessage>;
pub(crate) type DealingSender = mpsc::UnboundedSender<NewDealingMessage>;
pub(crate) struct Processor {
// TODO: should it hold the actual dkg_state or rather the stateaccessor and emit events regarding processed dealing?
dkg_state: DkgState,
receiver: DealingReceiver,
}
impl Processor {
pub(crate) fn new(dkg_state: DkgState, receiver: DealingReceiver) -> Self {
Processor {
dkg_state,
receiver,
}
}
async fn process_dealing(&self, dealing: NewDealingMessage) {
info!("here we would be handling new dealing!\n{}", dealing);
}
pub(crate) async fn run(&mut self) {
debug!("starting Dealing Processor");
while let Some(dealing) = self.receiver.next().await {
self.process_dealing(dealing).await
}
// since we have no graceful shutdowns, seeing this error means something bad has happened
// as all senders got dropped
error!("Dealing Processor has stopped receiving events! The process is in an undefined state. Shutting down...");
std::process::exit(1);
}
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::io;
use thiserror::Error;
use validator_client::nymd::error::NymdError;
use validator_client::ValidatorClientError;
#[derive(Error, Debug)]
pub enum DkgError {
#[error("Internal error - {0}")]
Internal(#[from] ::dkg::error::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("Failed to recover assigned node index")]
NodeIndexRecoveryError,
#[error("Failed to broadcast our message to every specified receiver ({total} receivers were specified)")]
FullBroadcastFailure { total: usize },
#[error("todo")]
MalformedDealerDetails,
#[error("todo")]
DeserializationError,
}
@@ -0,0 +1,68 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::dealing_processing::DealingSender;
use crate::dkg::events::Event;
use crate::dkg::main_loop::ContractEventsSender;
use futures::channel::mpsc;
use futures::StreamExt;
use log::{debug, error, trace};
use std::fmt::Display;
pub(crate) type DispatcherSender = mpsc::UnboundedSender<Event>;
pub(crate) type DispatcherReceiver = mpsc::UnboundedReceiver<Event>;
pub(crate) struct Dispatcher {
event_receiver: DispatcherReceiver,
dealing_processor: DealingSender,
contract_event_sender: ContractEventsSender,
}
impl Dispatcher {
pub(crate) fn new(
event_receiver: DispatcherReceiver,
dealing_processor: DealingSender,
contract_event_sender: ContractEventsSender,
) -> Self {
Dispatcher {
event_receiver,
dealing_processor,
contract_event_sender,
}
}
// we require `T` to be explicitly `Display` for the purposes of providing better error messages
// before crashing
fn forward_event<T: Display>(&self, channel: &mpsc::UnboundedSender<T>, event_item: T) {
if let Err(err) = channel.unbounded_send(event_item) {
log::error!("Our event dispatcher failed to forward {} event - the receiver has presumably crashed. Shutting down the API...", err.into_inner());
std::process::exit(1);
}
}
fn handle_event(&self, event: Event) {
match event {
Event::NewDealing(new_dealing_request) => {
trace!("received and forwarding NewDealing Event");
self.forward_event(&self.dealing_processor, new_dealing_request)
}
Event::DkgContractChange(watcher_event) => {
trace!("received and forwarding DkgContractChange Event");
self.forward_event(&self.contract_event_sender, watcher_event)
}
}
}
pub(crate) async fn run(&mut self) {
debug!("starting Dispatcher");
while let Some(new_event) = self.event_receiver.next().await {
self.handle_event(new_event)
}
// since we have no graceful shutdowns, seeing this error means something bad has happened
// as all senders got dropped
error!("Event Dispatcher has stopped receiving events! The process is in an undefined state. Shutting down...");
std::process::exit(1);
}
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::networking::message::NewDealingMessage;
use crate::dkg::smart_contract::watcher;
use std::fmt::{Display, Formatter};
#[derive(Debug)]
pub(crate) enum Event {
NewDealing(NewDealingMessage),
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 {
Event::NewDealing(new_dealing_message) => {
write!(f, "NewDealingEvent ({})", new_dealing_message)
}
Event::DkgContractChange(contract_watcher_event) => {
write!(f, "DkgContractChangeEvent ({})", contract_watcher_event)
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod dispatcher;
mod event;
pub(crate) use dispatcher::{Dispatcher, DispatcherSender};
pub(crate) use event::Event;
@@ -0,0 +1,61 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::state::DkgParticipant;
use coconut_dkg_common::types::{EpochId, Threshold};
use contracts_common::commitment::{Committable, DefaultHasher, Digest};
use dkg::NodeIndex;
use std::collections::BTreeMap;
type ReceiversDigest = Vec<u8>;
// not sure if this is the best place for it, but we can just move it later
// note that its an ephemeral type and thus the references in here rather than owned types
#[derive(Debug)]
pub(crate) struct CommittableEpochDealing<'a> {
epoch_id: EpochId,
system_threshold: Threshold,
dealing_bytes: &'a [u8],
// since all dealers are going to be using exactly the same set of receivers,
// perform commitment on a hash of receivers so that we wouldn't need to recompute the bytes every time
// we receive a dealing and want to verify the commitment
receivers_digest: &'a ReceiversDigest,
}
impl<'a> CommittableEpochDealing<'a> {
pub(crate) fn new(
epoch_id: EpochId,
system_threshold: Threshold,
dealing_bytes: &'a [u8],
receivers_digest: &'a ReceiversDigest,
) -> Self {
CommittableEpochDealing {
epoch_id,
system_threshold,
dealing_bytes,
receivers_digest,
}
}
}
pub(crate) fn hash_receivers(receivers: &BTreeMap<NodeIndex, DkgParticipant>) -> ReceiversDigest {
let mut bytes = Vec::new();
// note: since it's a BTreeMap, we're guaranteed to always iterate in the same order over the values
for receiver in receivers.values() {
bytes.append(&mut receiver.to_bytes());
}
DefaultHasher::digest(bytes).to_vec()
}
impl<'a> Committable for CommittableEpochDealing<'a> {
type DigestAlgorithm = DefaultHasher;
fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.epoch_id.to_be_bytes());
bytes.extend_from_slice(&self.system_threshold.to_be_bytes());
bytes.extend_from_slice(self.dealing_bytes);
bytes.extend_from_slice(self.receivers_digest);
bytes
}
}
+348
View File
@@ -0,0 +1,348 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::error::DkgError;
use crate::dkg::events::DispatcherSender;
use crate::dkg::main_loop::dealing_commitment::{hash_receivers, CommittableEpochDealing};
use crate::dkg::networking::message::OffchainDkgMessage;
use crate::dkg::networking::sender::Broadcaster;
use crate::dkg::smart_contract::publisher::Publisher;
use crate::dkg::smart_contract::watcher;
use crate::dkg::smart_contract::watcher::{CommitmentChange, DealerChange, EventType};
use crate::dkg::state::{DkgParticipant, DkgState, Malformation, MalformedDealer, StateShare};
use coconut_dkg_common::types::{Addr, BlockHeight, DealerDetails, Epoch, EpochId};
use contracts_common::commitment::{Committable, ContractSafeCommitment, MessageCommitment};
use dkg::bte::encrypt_shares;
use dkg::{Dealing, Params};
use futures::channel::mpsc;
use futures::StreamExt;
use log::{debug, info, trace};
use rand::RngCore;
use std::net::SocketAddr;
use validator_client::nymd::SigningCosmWasmClient;
pub(crate) mod dealing_commitment;
// essentially events originating from the contract watcher could only drive our state forward
// (TODO: is it actually true? I guess we'll find out soon enough)
pub(crate) type ContractEventsReceiver = mpsc::UnboundedReceiver<watcher::Event>;
pub(crate) type ContractEventsSender = mpsc::UnboundedSender<watcher::Event>;
// it is driven by events received from the contract watcher
pub(crate) struct ProcessingLoop<C, R> {
dkg_params: dkg::Params,
rng: R,
// technically we could have used `StateAccessor` here as well, but I really want to keep
// `StateAccessor` pure from anything that would have to modify `dkg_state`
dkg_state: DkgState,
dispatcher_sender: DispatcherSender,
contract_events_receiver: ContractEventsReceiver,
contract_publisher: Publisher<C>,
}
impl<C, R: RngCore> ProcessingLoop<C, R>
where
C: SigningCosmWasmClient + Send + Sync,
{
pub(crate) fn new(
rng: R,
dkg_state: DkgState,
dispatcher_sender: DispatcherSender,
contract_events_receiver: ContractEventsReceiver,
contract_publisher: Publisher<C>,
) -> Self {
ProcessingLoop {
dkg_params: Params::default(),
rng,
dkg_state,
dispatcher_sender,
contract_events_receiver,
contract_publisher,
}
}
fn random_request_id(&mut self) -> u64 {
self.rng.next_u64()
}
async fn broadcast(&self, msg: OffchainDkgMessage, addresses: Vec<SocketAddr>) {
todo!()
}
async fn send_to(&self, msg: OffchainDkgMessage, address: SocketAddr) {
todo!()
}
async fn raise_malformed_dealer_complaint(
&self,
dealer_address: Addr,
malformation: Malformation,
) {
info!(
"here we would be raising complaint about dealer {} being malformed: {:?}",
dealer_address, malformation
);
// todo!()
}
async fn broadcast_dealing(
&mut self,
epoch_id: EpochId,
dealing: Dealing,
addresses: Vec<SocketAddr>,
) -> Result<(), DkgError> {
let dealing_bytes = dealing.to_bytes();
let signature = self.dkg_state.sign_dealing(epoch_id, &dealing_bytes).await;
let request_id = self.random_request_id();
let msg =
OffchainDkgMessage::new_dealing_message(request_id, epoch_id, dealing_bytes, signature);
info!(
"broadcasting dealing for epoch {} to {} other parties",
epoch_id,
addresses.len()
);
trace!("parties getting the dealing: {:?}", addresses);
Broadcaster::new(addresses)
.broadcast_with_feedback(msg)
.await
}
async fn produce_and_share_dealing(&mut self, epoch: Epoch)
where
R: RngCore,
{
let self_index = self.dkg_state.assigned_index().await;
let self_host = self.dkg_state.network_address().await;
let receivers = self.dkg_state.ordered_receivers().await;
let receivers_digest = hash_receivers(&receivers);
let dkg_receivers = receivers
.iter()
.map(|(k, v)| (*k, *v.bte_public_key.public_key()))
.collect();
// make sure we don't include ourselves (also note: at this point the addresses dont have to be ordered)
let remote_hosts = receivers
.values()
.map(|receiver| receiver.remote_address)
.filter(|addr| addr != &self_host)
.collect::<Vec<_>>();
let threshold = epoch.system_threshold;
let dkg_epoch = dkg::Epoch::new(epoch.id);
// for now completely ignore the idea of resharing
let (dealing, self_share) = Dealing::create(
&mut self.rng,
&self.dkg_params,
self_index,
threshold,
dkg_epoch,
&dkg_receivers,
None,
);
let dealing_bytes = dealing.to_bytes();
let committable = CommittableEpochDealing::new(
epoch.id,
epoch.system_threshold,
&dealing_bytes,
&receivers_digest,
);
let commitment = committable.produce_commitment();
// first publish commitment to the chain and then broadcast it to other parties
if let Err(err) = self
.contract_publisher
.submit_dealing_commitment(epoch.id, commitment.contract_safe_commitment())
.await
{
error!("failed to submit dealing commitment to the chain - {}", err);
// TODO: should we exit the process at this point? This seem to be a rather critical problem
// as we cannot participate in DKG without that
return;
}
// TODO: should we be broadcasting in separate task to not block the loop for processing other messages?
if let Err(err) = self
.broadcast_dealing(epoch.id, dealing, remote_hosts)
.await
{
error!("failed to broadcast our dealing to other parties - {}", err);
// TODO: again, should we exit here?
return;
}
// TODO: think how to handle it => we want to keep it until we receive all dealings to
// derive the shared key, but we really want to avoid storing it on disk in plain
let state_share = self_share.map(|self_share| {
let (ciphertext, _) = encrypt_shares(
&[(&self_share, self.dkg_state.public_bte_key())],
dkg_epoch,
&self.dkg_params,
&mut self.rng,
);
StateShare::new(Some(self_share), ciphertext)
});
self.dkg_state.post_dealing_submission(state_share).await
}
async fn deal_with_new_dealer(&self, dealer: DkgParticipant) {
if dealer.bte_public_key.verify() {
self.dkg_state.try_add_new_dealer(dealer).await
} else {
debug!("received dealer {} failed to prove possession of its BTE key and it will be dealt with accordingly", dealer.chain_address);
let dealer_address = dealer.chain_address.clone();
// the dealer failed to provide valid proof of possession
let malformation = Malformation::InvalidBTEPublicKey;
self.dkg_state
.try_add_malformed_dealer(MalformedDealer::Parsed(dealer))
.await;
self.raise_malformed_dealer_complaint(dealer_address, malformation)
.await;
}
}
async fn deal_with_malformed_dealer(
&self,
dealer_details: DealerDetails,
malformation: Malformation,
) {
debug!(
"received dealer {} is malformed ({:?}) and it will be dealt with accordingly",
dealer_details.address, malformation
);
let dealer_address = dealer_details.address.clone();
self.dkg_state
.try_add_malformed_dealer(MalformedDealer::Raw(dealer_details))
.await;
self.raise_malformed_dealer_complaint(dealer_address, malformation)
.await;
}
async fn process_new_dealer(&self, dealer_details: DealerDetails) {
trace!("processing new dealer ({})", dealer_details.address);
match DkgParticipant::try_parse_from_raw(&dealer_details) {
Ok(dealer) => self.deal_with_new_dealer(dealer).await,
Err(malformed_dealer) => {
self.deal_with_malformed_dealer(dealer_details, malformed_dealer)
.await
}
}
}
async fn process_dealer_removal(&self, dealer_address: Addr) {
trace!("processing dealer removal ({})", dealer_address);
self.dkg_state.try_remove_dealer(dealer_address).await
}
async fn process_new_key_submission(&self, height: BlockHeight) {
debug!("attempting to register our own dealer keys for this round of dkg");
let chain_address = self.contract_publisher.get_address().await;
let registration = self
.dkg_state
.prepare_dealer_registration(chain_address)
.await;
match self
.contract_publisher
.register_dealer(
registration.identity,
registration.bte_key,
registration.owner_signature,
registration.network_address,
)
.await
{
Err(err) => error!("failed to register our dealer - {}", err),
Ok(node_index) => {
info!(
"registered our dealer for this DKG round and got assigned index: {}",
node_index
);
self.dkg_state.post_key_submission(node_index).await
}
}
}
async fn process_dealer_changes(&self, changes: Vec<DealerChange>, height: BlockHeight) {
debug!(
"processing dealer set change event with {} changes at height {}",
changes.len(),
height
);
for change in changes {
match change {
DealerChange::Addition { details } => self.process_new_dealer(details).await,
DealerChange::Removal { address } => self.process_dealer_removal(address).await,
}
}
}
async fn process_commitments_changes(
&self,
changes: Vec<CommitmentChange>,
height: BlockHeight,
) {
debug!(
"processing known commitments change event with {} changes at height {}",
changes.len(),
height
);
for change in changes {
match change {
CommitmentChange::Addition {
address,
commitment,
} => info!("here we would add known commitment"),
CommitmentChange::Removal { address } => {
info!("here we would remove known commitment")
}
CommitmentChange::Update {
address,
commitment,
} => info!("here we would update known commitment"),
}
}
}
async fn process_event(&mut self, event: watcher::Event) {
match event.event_type {
EventType::NewKeySubmission => self.process_new_key_submission(event.height).await,
EventType::DealerSetChange { changes } => {
self.process_dealer_changes(changes, event.height).await
}
EventType::NewDealingCommitment { epoch } => {
self.produce_and_share_dealing(epoch).await
}
EventType::NoChange => {
trace!("no change in the contract, going to only update the last seen height");
}
EventType::KnownCommitmentsChange { changes } => {
self.process_commitments_changes(changes, event.height)
.await
}
}
self.dkg_state.update_last_seen_height(event.height).await
}
pub(crate) async fn run(&mut self) {
debug!("starting DKG main processing loop");
while let Some(event) = self.contract_events_receiver.next().await {
self.process_event(event).await
}
// since we have no graceful shutdowns, seeing this error means something bad has happened
// as all senders got dropped
error!("DKG Processing Loop has stopped receiving events! The process is in an undefined state. Shutting down...");
std::process::exit(1);
}
}
+167
View File
@@ -0,0 +1,167 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::dealing_processing::Processor;
use crate::dkg::events::Dispatcher;
use crate::dkg::main_loop::ProcessingLoop;
use crate::dkg::networking::receiver::Listener;
use crate::dkg::state::{DkgState, StateAccessor};
use crate::Client;
use crypto::asymmetric::identity;
use dkg::bte;
use futures::channel::mpsc;
use log::{error, info};
use rand::rngs::OsRng;
use std::net::{SocketAddr, ToSocketAddrs};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use validator_client::nymd::{SigningCosmWasmClient, SigningNymdClient};
mod dealing_processing;
pub(crate) mod error;
pub(crate) mod events;
mod main_loop;
pub(crate) mod networking;
mod smart_contract;
pub(crate) mod state;
pub(crate) struct Config {
// not entirely sure what should go here just yet
network_address: SocketAddr,
contract_polling_interval: Duration,
// note: perhaps it should go behind some wrapper so that if the epoch is updated,
// the value stored on the disk would be overwritten
decryption_key: bte::DecryptionKey,
public_key: bte::PublicKeyWithProof,
identity: identity::KeyPair,
}
async fn run_dkg<C>(config: Config, nyxd_client: Client<C>) -> anyhow::Result<()>
where
C: SigningCosmWasmClient + Send + Sync + 'static,
{
info!("initialising dkg...");
let rng = OsRng;
let (dispatcher_sender, dispatcher_receiver) = mpsc::unbounded();
let (contracts_events_sender, contracts_events_receiver) = mpsc::unbounded();
let (dealing_sender, dealing_receiver) = mpsc::unbounded();
// TODO: change it to attempt to load from a file first
let state = DkgState::initialise_fresh(
&nyxd_client,
config.network_address,
config.identity,
config.decryption_key,
config.public_key,
)
.await
.map_err(|err| {
error!("failed to initialise dkg state - {}", err);
err
})?;
let state_accessor = StateAccessor::new(state.clone(), dispatcher_sender.clone());
let mut event_dispatcher =
Dispatcher::new(dispatcher_receiver, dealing_sender, contracts_events_sender);
let mut dealing_processor = Processor::new(state.clone(), dealing_receiver);
let contract_watcher = smart_contract::Watcher::new(
nyxd_client.clone(),
state_accessor.clone(),
config.contract_polling_interval,
);
let publisher = smart_contract::Publisher::new(nyxd_client);
let mut net_listener = Listener::new(config.network_address, state_accessor);
let mut processing_loop = ProcessingLoop::new(
rng,
state,
dispatcher_sender,
contracts_events_receiver,
publisher,
);
tokio::spawn(async move { event_dispatcher.run().await });
tokio::spawn(async move { net_listener.run().await });
tokio::spawn(async move { dealing_processor.run().await });
tokio::spawn(async move { contract_watcher.run().await });
processing_loop.run().await;
Ok(())
}
// upon startup, the following tasks will need to be spawned:
// - smart contract watcher
// - main loop processing
// - dealing processor
// - network listener
// - event dispatcher
// (possibly): network sender (if listens for events, otherwise under control of main loop)
// (possibly): contract publisher (if listens for events, otherwise under control of main loop)
// this only exists for purposes of local testing so that I wouldn't need to setup the entire valid API instance
// including validator connections
// in "proper" main, this would have been constructed elsewhere, but we just want to have something to work with now
fn make_client() -> Client<SigningNymdClient> {
let mnemonic = std::env::var("MNEMONIC").unwrap();
let validator_url = "http://localhost:26657".parse().unwrap();
// this one is irrelevant as we don't need to call it
let api_url = "http://localhost:8080".parse().unwrap();
let contract_address = "nymt1v6qjx5smfdxnh5gr8vprswl60rstyprj3wh4gz5mg7gcl7mtl5xqnpvdf6"
.parse()
.unwrap();
let client_config = validator_client::Config::new(
config::defaults::all::Network::QA,
validator_url,
api_url,
None,
None,
None,
)
.with_coconut_dkg_contract(Some(contract_address));
let inner = validator_client::Client::new_signing(client_config, mnemonic.parse().unwrap())
.expect("Failed to connect to nymd!");
Client(Arc::new(RwLock::new(inner)))
}
// note: for time being the entire dkg is only concerned about deriving a single shared scalar
// and not the entire coconut keypair
pub(crate) async fn dkg_only_main() -> anyhow::Result<()> {
const POLLING_RATE: Duration = Duration::from_secs(10);
let port = std::env::var("PORT").unwrap();
let network_address = format!("localhost:{}", port)
.to_socket_addrs()
.unwrap()
.next()
.unwrap();
let client = make_client();
let mut rng = OsRng;
let mut rng_07 = rand_07::rngs::OsRng;
let dkg_params = dkg::bte::setup();
let bte_keys = dkg::bte::keygen(&dkg_params, &mut rng);
let ed25519_keys = identity::KeyPair::new(&mut rng_07);
let dkg_config = Config {
network_address,
contract_polling_interval: POLLING_RATE,
decryption_key: bte_keys.0,
public_key: bte_keys.1,
identity: ed25519_keys,
};
run_dkg(dkg_config, client).await
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::error::DkgError;
use crate::dkg::networking::message::{Header, OffchainDkgMessage};
use crate::dkg::networking::PROTOCOL_VERSION;
use bytes::{Buf, BytesMut};
use tokio_util::codec::{Decoder, Encoder};
const MAX_ALLOWED_MESSAGE_LEN: usize = 2 * 1024 * 1024;
#[derive(Debug)]
pub struct DkgCodec;
impl<'a> Encoder<&'a OffchainDkgMessage> for DkgCodec {
type Error = DkgError;
fn encode(&mut self, item: &OffchainDkgMessage, dst: &mut BytesMut) -> Result<(), Self::Error> {
item.encode(dst)
}
}
impl Decoder for DkgCodec {
type Item = OffchainDkgMessage;
type Error = DkgError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if src.len() < Header::LEN {
// can't do much without being able to deserialize the header
return Ok(None);
}
// header deserialization is simple enough to not be too cumbersome if it were to be repeated
let header = Header::try_from_bytes(&src[..Header::LEN])?;
if header.payload_length as usize > MAX_ALLOWED_MESSAGE_LEN {
todo!("return an error here")
}
if header.protocol_version != PROTOCOL_VERSION {
todo!("return another error here")
}
if src.len() < Header::LEN + header.payload_length as usize {
// we haven't received the entire expected message yet.
// However, reserve enough bytes in the buffer for it
src.reserve(Header::LEN + header.payload_length as usize - src.len());
// We inform the Framed that we need more bytes to form the next frame.
return Ok(None);
}
let payload = src[Header::LEN..Header::LEN + header.payload_length as usize].to_vec();
src.advance(Header::LEN + header.payload_length as usize);
Ok(Some(OffchainDkgMessage::try_from_bytes(payload)?))
}
}
+249
View File
@@ -0,0 +1,249 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::error::DkgError;
use crate::dkg::networking::PROTOCOL_VERSION;
use crate::dkg::state::ReceivedDealing;
use bytes::{BufMut, BytesMut};
use coconut_dkg_common::types::EpochId;
use crypto::asymmetric::identity;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::io;
use std::net::SocketAddr;
use std::time::Duration;
use thiserror::Error;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OffchainDkgMessage {
NewDealing {
id: u64,
message: NewDealingMessage,
},
RemoteDealingRequest {
id: u64,
message: RemoteDealingRequestMessage,
},
RemoteDealingResponse {
id: u64,
message: RemoteDealingResponseMessage,
},
ErrorResponse {
id: Option<u64>,
message: ErrorResponseMessage,
},
}
impl Display for OffchainDkgMessage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "OffchainDkgMessage ")?;
match self {
OffchainDkgMessage::NewDealing { id, message } => {
write!(f, "with id {} and message: {}", id, message)
}
OffchainDkgMessage::RemoteDealingRequest { id, message } => {
write!(f, "with id {} and message: {}", id, message)
}
OffchainDkgMessage::RemoteDealingResponse { id, message } => {
write!(f, "with id {} and message: {}", id, message)
}
OffchainDkgMessage::ErrorResponse { id, message } => {
write!(f, "with id {:?} and message: {}", id, message)
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewDealingMessage {
pub epoch_id: u32,
// we keep the dealing in its serialized state as that's what is being signed (and hashed)
// so that it's easier to verify
pub dealing_bytes: Vec<u8>,
pub dealer_signature: identity::Signature,
}
impl Display for NewDealingMessage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"NewDealingMessage for epoch {} with length {}",
self.epoch_id,
self.dealing_bytes.len()
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoteDealingRequestMessage {
pub epoch_id: u32,
pub dealer: identity::PublicKey,
}
impl Display for RemoteDealingRequestMessage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"RemoteDealingRequestMessage for epoch {} for dealer {}",
self.epoch_id, self.dealer
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RemoteDealingResponseMessage {
Available { dealing: ReceivedDealing },
Unavailable,
}
impl Display for RemoteDealingResponseMessage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "RemoteDealingResponseMessage - ")?;
match self {
RemoteDealingResponseMessage::Available { .. } => write!(f, "Available"),
RemoteDealingResponseMessage::Unavailable => write!(f, "Unavailable"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Error)]
pub enum ErrorResponseMessage {
#[error("Received request for epoch: {requested}, while the current epoch is {current}")]
InvalidEpoch { current: u32, requested: u32 },
#[error("This sender is not a known dealer for this DKG epoch. Epoch: {epoch_id}, sender: {sender_address}")]
UnknownDealer {
sender_address: SocketAddr,
epoch_id: u32,
},
#[error("{typ} is not a valid request type")]
InvalidRequest { typ: String },
#[error("This request failed to get resolved within {} seconds", .timeout.as_secs())]
Timeout { timeout: Duration },
}
impl OffchainDkgMessage {
pub(crate) fn new_error_response(id: Option<u64>, message: ErrorResponseMessage) -> Self {
OffchainDkgMessage::ErrorResponse { id, message }
}
pub(crate) fn new_dealing_message(
id: u64,
epoch_id: EpochId,
dealing_bytes: Vec<u8>,
dealer_signature: identity::Signature,
) -> Self {
OffchainDkgMessage::NewDealing {
id,
message: NewDealingMessage {
epoch_id,
dealing_bytes,
dealer_signature,
},
}
}
pub(crate) fn new_remote_dealing_response(id: u64, dealing: Option<ReceivedDealing>) -> Self {
let message = match dealing {
Some(dealing) => RemoteDealingResponseMessage::Available { dealing },
None => RemoteDealingResponseMessage::Unavailable,
};
OffchainDkgMessage::RemoteDealingResponse { id, message }
}
pub(crate) fn try_from_bytes(bytes: Vec<u8>) -> Result<Self, DkgError> {
Ok(bincode::deserialize(&bytes)?)
}
pub(crate) fn try_to_bytes(&self) -> Result<Vec<u8>, DkgError> {
Ok(bincode::serialize(&self)?)
}
fn frame(&self) -> Result<FramedOffchainDkgMessage, DkgError> {
let payload = self.try_to_bytes()?;
Ok(FramedOffchainDkgMessage {
header: Header {
payload_length: payload.len() as u64,
protocol_version: PROTOCOL_VERSION,
},
payload,
})
}
pub(crate) fn encode(&self, dst: &mut BytesMut) -> Result<(), DkgError> {
dst.put(self.frame()?.into_bytes().as_ref());
Ok(())
}
}
struct FramedOffchainDkgMessage {
header: Header,
payload: Vec<u8>,
}
impl FramedOffchainDkgMessage {
fn into_bytes(mut self) -> Vec<u8> {
let mut header_bytes = self.header.into_bytes();
let mut out = Vec::with_capacity(header_bytes.len() + self.payload.len());
out.append(&mut header_bytes);
out.append(&mut self.payload);
out
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) struct Header {
pub(crate) payload_length: u64,
pub(crate) protocol_version: u32,
}
impl Header {
pub(crate) const LEN: usize = 12;
pub(crate) fn into_bytes(self) -> Vec<u8> {
let mut out = Vec::with_capacity(Self::LEN);
out.extend_from_slice(&self.payload_length.to_be_bytes());
out.extend_from_slice(&self.protocol_version.to_be_bytes());
debug_assert_eq!(Self::LEN, out.len());
out
}
pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
if bytes.len() != Self::LEN {
return Err(DkgError::Networking(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"OffchainDkgMessageType::Header: got {} bytes, expected: {}",
bytes.len(),
Self::LEN
),
)));
}
Ok(Header {
payload_length: u64::from_be_bytes(bytes[..8].try_into().unwrap()),
protocol_version: u32::from_be_bytes(bytes[8..].try_into().unwrap()),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dkg::networking::PROTOCOL_VERSION;
#[test]
fn header_deserialization() {
let valid_header = Header {
payload_length: 1234,
protocol_version: PROTOCOL_VERSION,
};
let bytes = valid_header.into_bytes();
assert_eq!(valid_header, Header::try_from_bytes(&bytes).unwrap())
}
}
+9
View File
@@ -0,0 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod codec;
pub(crate) mod message;
pub(crate) mod receiver;
pub(crate) mod sender;
pub(crate) const PROTOCOL_VERSION: u32 = 1;
@@ -0,0 +1,171 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::events::Event;
use crate::dkg::networking::codec::DkgCodec;
use crate::dkg::networking::message::{
ErrorResponseMessage, NewDealingMessage, OffchainDkgMessage, RemoteDealingRequestMessage,
};
use crate::dkg::state::StateAccessor;
use futures::{SinkExt, StreamExt};
use std::net::SocketAddr;
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::time::timeout;
use tokio_util::codec::Framed;
const DEFAULT_MAX_CONNECTION_DURATION: Duration = Duration::from_secs(2 * 60 * 60);
#[derive(Debug)]
pub struct ConnectionHandler {
// connection cannot exist for more than this time
max_connection_duration: Duration,
state_accessor: StateAccessor,
conn: Framed<TcpStream, DkgCodec>,
remote: SocketAddr,
}
impl ConnectionHandler {
pub(crate) fn new(state_accessor: StateAccessor, conn: TcpStream, remote: SocketAddr) -> Self {
ConnectionHandler {
max_connection_duration: DEFAULT_MAX_CONNECTION_DURATION,
state_accessor,
remote,
conn: Framed::new(conn, DkgCodec),
}
}
async fn send_response(&mut self, response_message: OffchainDkgMessage) {
if let Err(err) = self.conn.send(&response_message).await {
warn!("Failed to send response back to {} - {}", self.remote, err)
}
}
async fn send_error_response(&mut self, id: Option<u64>, error: ErrorResponseMessage) {
self.send_response(OffchainDkgMessage::new_error_response(id, error))
.await
}
async fn handle_new_dealing(&self, _id: u64, message: NewDealingMessage) {
self.state_accessor
.push_event(Event::NewDealing(message))
.await
}
async fn handle_remote_dealing_request(
&mut self,
id: u64,
message: RemoteDealingRequestMessage,
) {
let current_epoch = self.state_accessor.current_epoch().await;
if current_epoch.id != message.epoch_id {
return self
.send_error_response(
Some(id),
ErrorResponseMessage::InvalidEpoch {
current: current_epoch.id,
requested: message.epoch_id,
},
)
.await;
}
let dealing = self
.state_accessor
.get_verified_dealing(message.dealer)
.await;
self.send_response(OffchainDkgMessage::new_remote_dealing_response(id, dealing))
.await;
}
async fn handle_request(&mut self, request: OffchainDkgMessage) {
match request {
OffchainDkgMessage::NewDealing { id, message } => {
self.handle_new_dealing(id, message).await
}
OffchainDkgMessage::RemoteDealingRequest { id, message } => {
self.handle_remote_dealing_request(id, message).await
}
OffchainDkgMessage::RemoteDealingResponse { id, .. } => {
self.send_error_response(
Some(id),
ErrorResponseMessage::InvalidRequest {
typ: "RemoteDealingResponse".into(),
},
)
.await
}
OffchainDkgMessage::ErrorResponse { id, .. } => {
self.send_error_response(
id,
ErrorResponseMessage::InvalidRequest {
typ: "ErrorResponse".into(),
},
)
.await
}
}
}
async fn _handle_connection(&mut self) {
debug!("Starting connection handler for {}", self.remote);
let (is_dealer, epoch) = self
.state_accessor
.is_dealers_remote_address(self.remote)
.await;
if !is_dealer {
warn!(
"Received a request from an unknown dealer - {}",
self.remote
);
self.send_error_response(
None,
ErrorResponseMessage::UnknownDealer {
sender_address: self.remote,
epoch_id: epoch.id,
},
)
.await;
return;
}
while let Some(framed_dkg_request) = self.conn.next().await {
// TODO: change to trace
debug!("got new message from {}", self.remote);
match framed_dkg_request {
Ok(framed_dkg_request) => self.handle_request(framed_dkg_request).await,
Err(err) => {
warn!(
"The socket connection got corrupted with error: {:?}. Closing the socket",
err
);
break;
}
}
}
debug!("Closing connection from {}", self.remote);
}
pub async fn handle_connection(mut self) {
let remote = self.remote;
if timeout(self.max_connection_duration, self._handle_connection())
.await
.is_err()
{
warn!(
"we timed out while trying to resolve connection from {}",
remote
);
self.send_error_response(
None,
ErrorResponseMessage::Timeout {
timeout: self.max_connection_duration,
},
)
.await;
}
}
}
@@ -0,0 +1,56 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TODO: if it becomes too cumbersome, perhaps consider a more streamlined solution like tarpc
use crate::dkg::networking::receiver::handler::ConnectionHandler;
use crate::dkg::state::StateAccessor;
use log::debug;
use std::fmt::Display;
use std::net::SocketAddr;
use std::process;
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
// note that we do not expect persistent connections between dealers, they should only really
// exist for the duration of a single message exchange
pub struct Listener<A> {
address: A,
state_accessor: StateAccessor,
}
impl<A> Listener<A> {
pub(crate) fn new(address: A, state_accessor: StateAccessor) -> Self {
Listener {
address,
state_accessor,
}
}
fn on_connect(&self, conn: TcpStream, remote: SocketAddr) {
tokio::spawn(
ConnectionHandler::new(self.state_accessor.clone(), conn, remote).handle_connection(),
);
}
pub(crate) async fn run(&mut self)
where
A: ToSocketAddrs + Display,
{
debug!("starting off-chain DKG Listener");
let listener = match TcpListener::bind(&self.address).await {
Ok(listener) => listener,
Err(err) => {
error!("Failed to bind to {} - {}.", self.address, err);
process::exit(1);
}
};
loop {
match listener.accept().await {
Ok((socket, remote_addr)) => self.on_connect(socket, remote_addr),
Err(err) => warn!("Failed to accept incoming connection - {:?}", err),
}
}
}
}
@@ -0,0 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod handler;
mod listener;
pub(crate) use listener::Listener;
@@ -0,0 +1,272 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::error::DkgError;
use crate::dkg::networking::codec::DkgCodec;
use crate::dkg::networking::message::OffchainDkgMessage;
use futures::channel::mpsc;
use futures::{stream, SinkExt, StreamExt};
use log::{debug, info, trace, warn};
use std::io;
use std::net::SocketAddr;
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::time::timeout;
use tokio_util::codec::Framed;
// TODO: for now just leave them here and make it configurable with proper config later
const DEFAULT_CONCURRENCY: usize = 5;
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_secs(60);
const DEFAULT_SEND_TIMEOUT: Duration = Duration::from_secs(10);
pub(crate) struct SendResponse {
source: SocketAddr,
response: Result<Option<OffchainDkgMessage>, DkgError>,
}
type FeedbackSender = mpsc::UnboundedSender<SendResponse>;
pub(crate) struct Broadcaster {
addresses: Vec<SocketAddr>,
concurrency_level: usize,
connection_timeout: Duration,
response_timeout: Duration,
send_timeout: Duration,
}
impl Broadcaster {
pub(crate) fn new(addresses: Vec<SocketAddr>) -> Self {
Broadcaster {
addresses,
concurrency_level: DEFAULT_CONCURRENCY,
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
response_timeout: DEFAULT_RESPONSE_TIMEOUT,
send_timeout: DEFAULT_SEND_TIMEOUT,
}
}
pub(crate) fn with_concurrency_level(mut self, concurrency_level: usize) -> Self {
self.concurrency_level = concurrency_level;
self
}
pub(crate) fn with_connection_timeout(mut self, connection_timeout: Duration) -> Self {
self.connection_timeout = connection_timeout;
self
}
pub(crate) fn with_response_timeout(mut self, response_timeout: Duration) -> Self {
self.response_timeout = response_timeout;
self
}
pub(crate) fn with_send_timeout(mut self, send_timeout: Duration) -> Self {
self.send_timeout = send_timeout;
self
}
pub(crate) fn set_addresses(&mut self, new_addresses: Vec<SocketAddr>) {
self.addresses = new_addresses;
}
fn create_broadcast_configs(
&self,
message: OffchainDkgMessage,
feedback_sender: Option<FeedbackSender>,
) -> Vec<BroadcastConfig> {
self.addresses
.iter()
.map(|&address| BroadcastConfig {
address,
connection_timeout: self.connection_timeout,
response_timeout: Some(self.response_timeout),
send_timeout: self.send_timeout,
feedback_sender: feedback_sender.clone(),
message: message.clone(),
})
.collect()
}
pub(crate) async fn broadcast_with_feedback(
&self,
msg: OffchainDkgMessage,
) -> Result<(), DkgError> {
if self.addresses.is_empty() {
warn!("attempting to broadcast {} while no remotes are known", msg);
return Ok(());
}
debug!("broadcasting {} to {} remotes", msg, self.addresses.len());
let (feedback_tx, mut feedback_rx) = mpsc::unbounded();
stream::iter(self.create_broadcast_configs(msg, Some(feedback_tx)))
.for_each_concurrent(self.concurrency_level, |cfg| cfg.send())
.await;
let mut failures = 0;
for _ in 0..self.addresses.len() {
// we should have received exactly self.addresses number of responses
// (they could be just Err failure responses, but should exist nonetheless)
match feedback_rx.try_next() {
Ok(Some(response)) => {
match response.response {
Err(err) => {
failures += 1;
warn!("we failed to broadcast to {} - {}", response.source, err)
}
Ok(Some(res)) => {
// TODO: figure out what to do with the replies exactly in the broadcast case...
if let OffchainDkgMessage::ErrorResponse { message, .. } = res {
warn!(
"we received an error response from {} - {}",
response.source, message
)
} else {
info!(
"{} provided a non-error response to our broadcast! - {}",
response.source, res
)
}
}
// the expected case
Ok(None) => debug!("{} didn't provide any reply", response.source),
}
}
Err(_) | Ok(None) => {
error!("somehow we received fewer feedback responses than sent messages")
}
}
}
// the channel should have been drained and all sender should have been dropped
debug_assert!(matches!(feedback_rx.try_next(), Ok(None)));
// if we failed to send to everyone, return an error, otherwise, assuming at least a single
// receiver got our dealing, it can be gossiped through (assuming the problem was on our side,
// i.e. the other receivers will actually want to receive the dealing)
if failures == self.addresses.len() {
error!("we failed to broadcast to every single receiver");
return Err(DkgError::FullBroadcastFailure { total: failures });
} else {
Ok(())
}
}
#[allow(dead_code)]
pub(crate) async fn broadcast(&self, msg: OffchainDkgMessage) {
if self.addresses.is_empty() {
warn!("attempting to broadcast {} while no remotes are known", msg);
return;
}
debug!("broadcasting {} to {} remotes", msg, self.addresses.len());
stream::iter(self.create_broadcast_configs(msg, None))
.for_each_concurrent(self.concurrency_level, |cfg| cfg.send())
.await
}
}
// internal struct to have per-connection config on hand
struct BroadcastConfig {
address: SocketAddr,
connection_timeout: Duration,
response_timeout: Option<Duration>,
send_timeout: Duration,
feedback_sender: Option<FeedbackSender>,
message: OffchainDkgMessage,
}
impl BroadcastConfig {
async fn send(self) {
let response = send_message(
self.address,
&self.message,
self.connection_timeout,
self.send_timeout,
self.response_timeout,
)
.await;
if let Some(feedback_sender) = self.feedback_sender {
// this can only fail if the receiver is disconnected which should never be the case
// thus we can ignore the possible error
let _ = feedback_sender.unbounded_send(SendResponse {
source: self.address,
response,
});
} else if let Err(err) = response {
// if we're not forwarding feedback, at least emit a warning about the failure
warn!(
"failed to broadcast {} to {} - {}",
self.message, self.address, err
)
}
}
}
// this connection only exists for a single message
pub(crate) struct EphemeralConnection {
conn: Framed<TcpStream, DkgCodec>,
}
impl EphemeralConnection {
pub(crate) async fn connect(
address: SocketAddr,
connection_timeout: Duration,
) -> io::Result<Self> {
trace!("attempting to connect to {}", address);
let conn = match timeout(connection_timeout, TcpStream::connect(address)).await {
Err(_timeout) => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("timed out while attempting to send message to {}", address),
))
}
Ok(conn_res) => conn_res?,
};
let framed_conn = Framed::new(conn, DkgCodec);
Ok(Self { conn: framed_conn })
}
pub(crate) fn remote(&self) -> io::Result<SocketAddr> {
self.conn.get_ref().peer_addr()
}
pub(crate) async fn send(
&mut self,
message: &OffchainDkgMessage,
send_timeout: Duration,
response_timeout: Option<Duration>,
) -> Result<Option<OffchainDkgMessage>, DkgError> {
trace!("attempting to send to {}", self.remote()?);
match timeout(send_timeout, self.conn.send(message)).await {
Err(_timeout) => {
return Err(DkgError::Networking(io::Error::new(
io::ErrorKind::Other,
"timed out while attempting to send message",
)))
}
Ok(res) => res?,
}
if let Some(response_timeout) = response_timeout {
match timeout(response_timeout, self.conn.next()).await {
Err(_elapsed) => Ok(None),
Ok(response) => response.transpose(),
}
} else {
Ok(None)
}
}
}
pub(crate) async fn send_message(
address: SocketAddr,
message: &OffchainDkgMessage,
connection_timeout: Duration,
send_timeout: Duration,
response_timeout: Option<Duration>,
) -> Result<Option<OffchainDkgMessage>, DkgError> {
let mut conn = EphemeralConnection::connect(address, connection_timeout).await?;
conn.send(message, send_timeout, response_timeout).await
}
@@ -0,0 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod publisher;
pub(crate) mod watcher;
pub(crate) use publisher::Publisher;
pub(crate) use watcher::Watcher;
@@ -0,0 +1,65 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::error::DkgError;
use crate::Client;
use coconut_dkg_common::types::{
EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, EpochId, NodeIndex,
};
use contracts_common::commitment::ContractSafeCommitment;
use validator_client::nymd::{AccountId, SigningCosmWasmClient};
pub(crate) struct Publisher<C> {
client: Client<C>,
}
impl<C> Publisher<C>
where
C: SigningCosmWasmClient + Send + Sync,
{
pub(crate) fn new(client: Client<C>) -> Self {
Publisher { client }
}
pub(crate) async fn get_address(&self) -> AccountId {
self.client.address().await
}
pub(crate) async fn register_dealer(
&self,
identity: EncodedEd25519PublicKey,
bte_key: EncodedBTEPublicKeyWithProof,
owner_signature: String,
listening_address: String,
) -> Result<NodeIndex, DkgError> {
self.client
.register_dealer(identity, bte_key, owner_signature, listening_address)
.await?;
// once we figure out how to properly deserialize `data` field from the response use that
// instead of this query
let self_details = self.client.get_self_registered_dealer_details().await?;
if let Some(details) = self_details.details {
if self_details.dealer_type.is_current() {
return Ok(details.assigned_index);
}
}
Err(DkgError::NodeIndexRecoveryError)
}
pub(crate) async fn submit_dealing_commitment(
&self,
epoch_id: EpochId,
commitment: ContractSafeCommitment,
) -> Result<(), DkgError> {
info!(
"submitting dealing commitment for epoch {} to the chain. Commitment is: {}",
epoch_id, commitment
);
self.client
.submit_dealing_commitment(epoch_id, commitment)
.await?;
Ok(())
}
}
@@ -0,0 +1,77 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_dkg_common::types::{Addr, BlockHeight, DealerDetails, Epoch};
use contracts_common::commitment::ContractSafeCommitment;
use std::fmt::{Display, Formatter};
#[derive(Debug)]
pub(crate) struct Event {
pub(crate) height: BlockHeight,
pub(crate) 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 at height {}. {}",
self.height, self.event_type
)
}
}
#[derive(Debug)]
pub(crate) enum CommitmentChange {
Addition {
address: Addr,
commitment: ContractSafeCommitment,
},
Removal {
address: Addr,
},
Update {
address: Addr,
commitment: ContractSafeCommitment,
},
}
#[derive(Debug)]
pub(crate) enum DealerChange {
Addition { details: DealerDetails },
Removal { address: Addr },
}
#[derive(Debug)]
pub(crate) enum EventType {
NoChange,
NewKeySubmission,
DealerSetChange { changes: Vec<DealerChange> },
KnownCommitmentsChange { changes: Vec<CommitmentChange> },
NewDealingCommitment { epoch: Epoch },
}
impl Display for EventType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "EventType - ")?;
match self {
EventType::NewKeySubmission => write!(f, "NewKeySubmission"),
EventType::DealerSetChange { changes } => {
write!(f, "DealerSetChange with {} changes", changes.len())
}
EventType::KnownCommitmentsChange { changes } => {
write!(f, "KnownCommitmentsChange with {} changes", changes.len())
}
EventType::NewDealingCommitment { epoch } => {
write!(f, "NewDealingCommitment for epoch {}", epoch.id)
}
EventType::NoChange => write!(f, "NoChange"),
}
}
}
@@ -0,0 +1,388 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::error::DkgError;
use crate::dkg::state::StateAccessor;
use crate::Client;
use coconut_dkg_common::dealer::ContractDealingCommitment;
use coconut_dkg_common::types::{Addr, BlockHeight, DealerDetails, Epoch, EpochState};
use contracts_common::commitment::ContractSafeCommitment;
use log::{debug, trace, warn};
use std::collections::HashMap;
use std::time::Duration;
use tokio::time::interval;
use validator_client::nymd::SigningCosmWasmClient;
pub(crate) use event::{CommitmentChange, DealerChange, Event, EventType};
mod event;
pub(crate) struct Watcher<C> {
client: Client<C>,
state_accessor: StateAccessor,
polling_rate: Duration,
}
impl<C> Watcher<C>
where
C: SigningCosmWasmClient + Send + Sync,
{
pub(crate) fn new(
client: Client<C>,
state_accessor: StateAccessor,
polling_rate: Duration,
) -> Self {
Watcher {
client,
state_accessor,
polling_rate,
}
}
async fn self_addr(&self) -> Addr {
// note: normally we want to avoid the unchecked API, however, in this case it's fine as the
// `AccountId` that is coming from the client is valid as it has been derived directly from the provided mnemonic,
// and hence we are certain it is correctly formed
Addr::unchecked(self.client.address().await.as_ref())
}
async fn check_for_dealers_change(
&self,
contract_dealers: HashMap<Addr, DealerDetails>,
current_height: BlockHeight,
) -> Result<(), DkgError> {
// get current state
let known_dealers = self.state_accessor.get_known_dealers().await;
let bad_dealers = self.state_accessor.get_malformed_dealers().await;
// TODO: this would probably have to get generalised since we'd need to use the same logic
// for other possible events
let mut changes = Vec::new();
// check for removed dealers (if our lists contain keys that do not exist in the contract,
// it implies they got purged from there)
for dealer in bad_dealers
.keys()
.chain(known_dealers.values().map(|dealer| &dealer.chain_address))
{
if !contract_dealers.contains_key(dealer) {
debug!("detected dealer that should get removed - {}", dealer);
changes.push(DealerChange::Removal {
address: dealer.clone(),
});
}
}
// check for new dealers
for (dealer, details) in contract_dealers {
let is_bad = bad_dealers.contains_key(&dealer);
// ideally we would have been accessing the hashmap by address, but it would make
// other parts inconvenient. (TODO: or would it?)
// 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);
// we had absolutely no idea about this dealer existing
if !is_bad && !is_known {
debug!("detected dealer that should get added - {}", dealer);
changes.push(DealerChange::Addition { details });
}
}
if !changes.is_empty() {
trace!(
"pushing {} dealer set changes onto the event queue",
changes.len()
);
self.state_accessor
.push_contract_change_event(Event::new(
current_height,
EventType::DealerSetChange { changes },
))
.await;
}
Ok(())
}
async fn check_for_own_key_submission(
&self,
contract_dealers: &HashMap<Addr, DealerDetails>,
current_height: BlockHeight,
) -> Result<(), DkgError> {
let address = self.self_addr().await;
if !contract_dealers.contains_key(&address) {
// our key is not present in contract dealers, check if we think we have submitted it
if !self.state_accessor.has_submitted_keys().await {
// if we just transitioned into `PublicKeySubmission` and we haven't submitted our own keys
// we should emit event to do just that
debug!("we never registered our own dkg keys");
self.state_accessor
.push_new_key_submission_event(current_height)
.await;
} else {
// check if we got blacklisted, since we think we have submitted our own key...
let blacklisting = self.client.get_blacklisting(address.into_string()).await?;
if blacklisting.is_blacklisted(current_height) {
warn!("our dealer is blacklisted - {}. We cannot participate in this round of DKG", blacklisting.unchecked_get_blacklisting());
// TODO: what to do about it? can we do anything about it?
} else {
// we've been blacklisted in the past, but it has already expired
debug!(
"our dealer has been blacklisted in the past, but it has already expired"
);
self.state_accessor
.push_new_key_submission_event(current_height)
.await;
}
}
} else {
// TODO: change to trace
debug!("our dkg key is already registered in the dkg contract")
}
Ok(())
}
async fn public_key_submission_actions(&self) -> Result<(), DkgError> {
let current_height = self.client.current_block_height().await?.value();
let contract_dealers = self
.client
.get_current_dealers()
.await?
.into_iter()
.map(|dealer| (dealer.address.clone(), dealer))
.collect::<HashMap<_, _>>();
self.check_for_own_key_submission(&contract_dealers, current_height)
.await?;
self.check_for_dealers_change(contract_dealers, current_height)
.await
}
async fn check_for_own_dealing_commitment(
&self,
contract_commitments: &HashMap<Addr, ContractSafeCommitment>,
current_height: BlockHeight,
current_epoch: Epoch,
) -> Result<(), DkgError> {
let address = self.self_addr().await;
if !contract_commitments.contains_key(&address) {
// our commitment is not present in contract commitments, check if we think we have submitted it
if !self
.state_accessor
.has_submitted_dealings_commitment()
.await
{
// if we just transitioned into `DealingExchange` and we haven't generated and submitted
// the dealing commitment we should emit event to do just that
debug!("we never submitted our dealing commitment");
self.state_accessor
.push_new_dealing_commitment_submission_event(current_height, current_epoch)
.await;
} else {
// check if we got blacklisted, since we think we have submitted our own commitment...
let blacklisting = self.client.get_blacklisting(address.into_string()).await?;
if blacklisting.is_blacklisted(current_height) {
warn!("our dealer is blacklisted - {}. We cannot participate in this round of DKG", blacklisting.unchecked_get_blacklisting());
// TODO: what to do about it? can we do anything about it?
} else {
// we've been blacklisted in the past, but it has already expired
debug!(
"our dealer has been blacklisted in the past, but it has already expired"
);
self.state_accessor
.push_new_dealing_commitment_submission_event(current_height, current_epoch)
.await;
}
}
} else {
// TODO: change to trace
debug!("our dkg dealing commitment is already registered in the dkg contract")
}
Ok(())
}
// TODO: simplify it (and maybe combine with the check of keys)
async fn check_for_commitments_change(
&self,
contract_commitments: &HashMap<Addr, ContractSafeCommitment>,
current_height: BlockHeight,
current_epoch: Epoch,
) -> Result<(), DkgError> {
// get current state
let known_commitments = self.state_accessor.get_known_commitments().await;
let mut changes = Vec::new();
// check for removed commitments (if our lists contain addresses that do not exist in the contract,
// it implies they got purged from there)
for (dealer, known_commitment) in &known_commitments {
match contract_commitments.get(&dealer) {
Some(commitment) => {
// that one is a weird one. perhaps this dealer (i.e. the one RUNNING this code)
// was inactive for a long time and just restored his state while the dealer
// whose commitment we have, got blacklisted and then it expired thus allowing
// him to submit new commitment? it seems rather unlikely or borderline impossible,
// but let's handle this case just in case...
if !known_commitment.is_same_as(commitment) {
debug!(
"detected dealer's commitment that should get updated - {}",
dealer
);
changes.push(CommitmentChange::Update {
address: dealer.clone(),
commitment: commitment.clone(),
});
}
}
None => {
debug!(
"detected dealer's commitment that should get removed - {}",
dealer
);
changes.push(CommitmentChange::Removal {
address: dealer.clone(),
});
}
}
}
// check for new dealers
for (dealer, commitment) in contract_commitments {
// we had absolutely no idea about commitment from this dealer existing
if !known_commitments.contains_key(dealer) {
debug!(
"detected dealer's commitment that should get added - {}",
dealer
);
changes.push(CommitmentChange::Addition {
address: dealer.clone(),
commitment: commitment.clone(),
});
}
}
if !changes.is_empty() {
trace!(
"pushing {} commitment changes onto the event queue",
changes.len()
);
self.state_accessor
.push_contract_change_event(Event::new(
current_height,
EventType::KnownCommitmentsChange { changes },
))
.await;
}
Ok(())
}
async fn dealing_exchange_actions(&self, epoch: Epoch) -> Result<(), DkgError> {
let current_height = self.client.current_block_height().await?.value();
let contract_commitments = self
.client
.get_epoch_dealings_commitments(epoch.id)
.await?
.into_iter()
.map(|commitment| (commitment.dealer, commitment.commitment))
.collect::<HashMap<_, _>>();
self.check_for_own_dealing_commitment(&contract_commitments, current_height, epoch)
.await?;
self.check_for_commitments_change(&contract_commitments, current_height, epoch)
.await
}
async fn perform_epoch_state_based_actions(&self, epoch: Epoch) -> Result<(), DkgError> {
match epoch.state {
EpochState::PublicKeySubmission { .. } => self.public_key_submission_actions().await,
EpochState::DealingExchange { .. } => self.dealing_exchange_actions(epoch).await,
EpochState::ComplaintSubmission { .. } => todo!(),
EpochState::ComplaintVoting { .. } => todo!(),
EpochState::VerificationKeySubmission { .. } => todo!(),
EpochState::VerificationKeyMismatchSubmission { .. } => todo!(),
EpochState::VerificationKeyMismatchVoting { .. } => todo!(),
EpochState::InProgress { .. } => todo!(),
}
}
// TODO: this is not entirely accurate as for example if the epoch is already in the 'dealings exchange'
// state, the dealer is not going to be allowed to submit its public keys
fn determine_required_actions(
&self,
stored_epoch: Epoch,
current_epoch: Epoch,
) -> Vec<EpochState> {
// if the current epoch has higher id than the one we have stored, we have to "start from scratch"
if current_epoch.id > stored_epoch.id {
let mut states = vec![EpochState::PublicKeySubmission];
// unwrap is fine as we always have a non-empty vec
while states.last().unwrap() != &current_epoch.state {
let next_state = states.last().unwrap().next().expect("somehow reached the end of state diff -> this should be impossible under any circumstances!");
states.push(next_state);
}
return states;
}
todo!()
}
async fn poll_contract(&self) -> Result<(), DkgError> {
trace!("polling the dkg smart contract for any changes");
// 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?;
debug!(
"contract epoch is in {:?} state, while our stored epoch is in {:?}",
current_epoch.state, prior_epoch.state
);
if prior_epoch > current_epoch {
error!("Somehow our stored epoch is more advanced than the one in the smart contract. Skipping this polling interval. Stored: {}, public in contract: {}", prior_epoch, current_epoch);
return Ok(());
}
// this is not entirely true, but for time being let's just use it to test basic event propagation
self.perform_epoch_state_based_actions(current_epoch)
.await?;
if prior_epoch.state != current_epoch.state {
error!("our known epoch state is different from the current one and we haven't yet implemented handling for that!")
}
Ok(())
}
pub(crate) async fn run(&self) {
debug!("Starting dkg contract poller");
let mut interval = interval(self.polling_rate);
loop {
interval.tick().await;
if let Err(err) = self.poll_contract().await {
warn!(
"failed to get the current state of the DKG contract - {}",
err
)
}
}
}
}
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::events::{DispatcherSender, Event};
use crate::dkg::main_loop::dealing_commitment::CommittableEpochDealing;
use crate::dkg::smart_contract::watcher;
use crate::dkg::state::{
DkgParticipant, DkgState, IdentityBytes, KnownCommitment, MalformedDealer, ReceivedDealing,
};
use coconut_dkg_common::types::{Addr, BlockHeight, Epoch};
use contracts_common::commitment::MessageCommitment;
use crypto::asymmetric::identity;
use std::collections::HashMap;
use std::net::SocketAddr;
// essentially some intermediary that allows either pushing events to the dispatcher or operating
// directly on the dkg state
// note: it should get renamed, but at the time of writing it, I couldn't come up with anything...
#[derive(Debug, Clone)]
pub(crate) struct StateAccessor {
dkg_state: DkgState,
dispatcher_sender: DispatcherSender,
}
impl StateAccessor {
pub(crate) fn new(dkg_state: DkgState, dispatcher_sender: DispatcherSender) -> Self {
StateAccessor {
dkg_state,
dispatcher_sender,
}
}
pub(crate) async fn push_event(&self, event: Event) {
if let Err(err) = self.dispatcher_sender.unbounded_send(event) {
log::error!("Our event dispatcher failed to receive {} event - it has presumably crashed. Shutting down the API after saving DKG state", err.into_inner());
self.dkg_state.save_to_file().await;
std::process::exit(1);
}
}
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 push_new_key_submission_event(&self, block_height: BlockHeight) {
self.push_event(Event::new_contract_change_event(watcher::Event::new(
block_height,
watcher::EventType::NewKeySubmission,
)))
.await
}
pub(crate) async fn push_new_dealing_commitment_submission_event(
&self,
block_height: BlockHeight,
epoch: Epoch,
) {
self.push_event(Event::new_contract_change_event(watcher::Event::new(
block_height,
watcher::EventType::NewDealingCommitment { epoch },
)))
.await
}
pub(crate) async fn has_submitted_keys(&self) -> bool {
self.dkg_state.has_submitted_keys().await
}
pub(crate) async fn has_submitted_dealings_commitment(&self) -> bool {
self.dkg_state.has_submitted_dealings_commitment().await
}
pub(crate) async fn current_epoch(&self) -> Epoch {
self.dkg_state.current_epoch().await
}
pub(crate) async fn get_verified_dealing(
&self,
dealer: identity::PublicKey,
) -> Option<ReceivedDealing> {
self.dkg_state.get_verified_dealing(dealer).await
}
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<IdentityBytes, DkgParticipant> {
self.dkg_state.get_known_dealers().await
}
pub(crate) async fn get_malformed_dealers(&self) -> HashMap<Addr, MalformedDealer> {
self.dkg_state.get_malformed_dealers().await
}
pub(crate) async fn get_known_commitments(&self) -> HashMap<Addr, KnownCommitment> {
self.dkg_state.get_known_commitments().await
}
}
+458
View File
@@ -0,0 +1,458 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dkg::error::DkgError;
use crate::dkg::main_loop::dealing_commitment::CommittableEpochDealing;
use crate::Client;
use coconut_dkg_common::types::{
Addr, BlockHeight, DealerDetails, EncodedBTEPublicKeyWithProof, EncodedEd25519PublicKey, Epoch,
EpochId, NodeIndex,
};
use contracts_common::commitment::MessageCommitment;
use crypto::asymmetric::identity;
use dkg::bte::Ciphertexts;
use dkg::{bte, Dealing, Share};
use futures::lock::Mutex;
use log::debug;
use log::error;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::net::SocketAddr;
use std::sync::Arc;
use validator_client::nymd::{AccountId, SigningCosmWasmClient};
mod accessor;
pub(crate) use accessor::StateAccessor;
type IdentityBytes = [u8; identity::PUBLIC_KEY_LENGTH];
pub(crate) type KnownCommitment = MessageCommitment<CommittableEpochDealing<'static>>;
// note: each dealer is also a receiver which simplifies some logic significantly
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct DkgParticipant {
pub(crate) chain_address: Addr,
pub(crate) node_index: NodeIndex,
pub(crate) bte_public_key: bte::PublicKeyWithProof,
pub(crate) identity: identity::PublicKey,
pub(crate) remote_address: SocketAddr,
}
impl DkgParticipant {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(self.chain_address.as_bytes());
bytes.extend_from_slice(&self.node_index.to_be_bytes());
bytes.extend_from_slice(&self.bte_public_key.to_bytes());
bytes.extend_from_slice(&self.identity.to_bytes());
bytes.extend_from_slice(&self.remote_address.to_string().as_bytes());
bytes
}
pub(crate) fn map_key(&self) -> IdentityBytes {
self.identity.to_bytes()
}
}
// TODO: move it elsewhere and propagate it to the contract
#[derive(Debug)]
pub enum Malformation {
MalformedEd25519PublicKey,
MalformedBTEPublicKey,
InvalidBTEPublicKey,
InvalidHostInformation,
}
impl DkgParticipant {
pub(crate) fn try_parse_from_raw(contract_value: &DealerDetails) -> Result<Self, Malformation> {
// this should be impossible as the contract must have used this key for signature verification
let identity = identity::PublicKey::from_base58_string(&contract_value.ed25519_public_key)
.map_err(|_| Malformation::MalformedEd25519PublicKey)?;
let bte_public_key = bs58::decode(&contract_value.bte_public_key_with_proof)
.into_vec()
.map(|bytes| bte::PublicKeyWithProof::try_from_bytes(&bytes))
.map_err(|_| Malformation::MalformedBTEPublicKey)?
.map_err(|_| Malformation::MalformedBTEPublicKey)?;
if !bte_public_key.verify() {
return Err(Malformation::InvalidBTEPublicKey);
}
let parsed_host = contract_value
.host
.parse()
.map_err(|_| Malformation::InvalidHostInformation)?;
Ok(DkgParticipant {
chain_address: contract_value.address.clone(),
node_index: contract_value.assigned_index,
bte_public_key,
identity,
remote_address: parsed_host,
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) enum MalformedDealer {
Raw(DealerDetails),
Parsed(DkgParticipant),
}
impl MalformedDealer {
pub(crate) fn address(&self) -> &Addr {
match self {
MalformedDealer::Raw(dealer) => &dealer.address,
MalformedDealer::Parsed(dealer) => &dealer.chain_address,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReceivedDealing {
epoch_id: u32,
dealing: Box<Dealing>,
signature: identity::Signature,
}
pub(crate) struct DealerRegistration {
pub(crate) identity: EncodedEd25519PublicKey,
pub(crate) bte_key: EncodedBTEPublicKeyWithProof,
pub(crate) owner_signature: String,
pub(crate) network_address: String,
}
#[derive(Debug, Clone)]
pub(crate) struct DkgState {
inner_state: Arc<Mutex<DkgStateInner>>,
keys: Arc<Keys>,
}
// we don't want to serialize/deserialize those as they are treated differently
#[derive(Debug)]
struct Keys {
identity: identity::KeyPair,
bte_decryption_key: bte::DecryptionKey,
bte_public_key: bte::PublicKeyWithProof,
}
fn empty_ciphertexts() -> Ciphertexts {
// this is super temporary and never meant to be used in the long run
Ciphertexts {
rr: Default::default(),
ss: Default::default(),
zz: Default::default(),
ciphertext_chunks: vec![],
}
}
// we want to avoid every storing the actual share in plain on the disk, so only the encrypted version
// is potentially stored and the actual share is decrypted when actually needed (i.e. during aggregation)
// however, if we don't have to serialize data to the disk and everything is kept in memory, there's no
// point in not using what we already have
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct StateShare {
#[serde(skip)]
share: Option<Share>,
// the reason for the skip is that I want the code to compile and I haven't yet implemented serde for Ciphertexts : )
#[serde(skip, default = "empty_ciphertexts")]
encrypted_share: Ciphertexts,
}
impl StateShare {
pub(crate) fn new(share: Option<Share>, encrypted_share: Ciphertexts) -> Self {
StateShare {
share,
encrypted_share,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
struct DkgStateInner {
submitted_keys: bool,
submitted_commitment: bool,
submitted_verification_keys: bool,
network_address: SocketAddr,
assigned_index: NodeIndex,
last_seen_height: BlockHeight,
current_epoch: Epoch,
expected_epoch_dealing_commitments: HashMap<Addr, KnownCommitment>,
self_share: Option<StateShare>,
// we need to keep track of all bad dealers as well so that we wouldn't attempt to complain about them
// repeatedly
bad_dealers: HashMap<Addr, MalformedDealer>,
current_epoch_dealers: HashMap<IdentityBytes, DkgParticipant>,
verified_epoch_dealings: HashMap<IdentityBytes, ReceivedDealing>,
unconfirmed_dealings: HashMap<IdentityBytes, ReceivedDealing>,
}
impl DkgState {
// this should only ever be called once, during init
pub(crate) async fn initialise_fresh<C>(
nyxd_client: &Client<C>,
network_address: SocketAddr,
identity: identity::KeyPair,
bte_decryption_key: bte::DecryptionKey,
bte_public_key: bte::PublicKeyWithProof,
) -> Result<Self, DkgError>
where
C: SigningCosmWasmClient + Send + Sync,
{
debug!("attempting to initialise fresh dkg state");
let current_epoch = nyxd_client.get_dkg_epoch().await?;
// TODO: IF we didn't load the state from the file, grab all other data from the contract while
// we're at it, like dealers, dealing commitments, etc.
Ok(DkgState {
inner_state: Arc::new(Mutex::new(DkgStateInner {
submitted_keys: false,
submitted_commitment: false,
submitted_verification_keys: false,
network_address,
assigned_index: 0,
last_seen_height: 0,
current_epoch,
expected_epoch_dealing_commitments: HashMap::new(),
self_share: None,
bad_dealers: HashMap::new(),
current_epoch_dealers: HashMap::new(),
verified_epoch_dealings: HashMap::new(),
unconfirmed_dealings: HashMap::new(),
})),
keys: Arc::new(Keys {
identity,
bte_decryption_key,
bte_public_key,
}),
})
}
pub(crate) async fn load_from_file(&self) {
todo!()
}
// some save/load action here
pub(crate) async fn save_to_file(&self) {
todo!()
}
// TODO: obviously this would need to get changed in the future in order to account for having to generate MULTIPLE dealings
pub(crate) async fn generate_dealing(&self) {
//
}
pub(crate) async fn ordered_receivers(&self) -> BTreeMap<NodeIndex, DkgParticipant> {
self.inner_state
.lock()
.await
.current_epoch_dealers
.values()
.map(|participant| (participant.node_index, participant.clone()))
.collect()
}
pub(crate) async fn network_address(&self) -> SocketAddr {
self.inner_state.lock().await.network_address
}
pub(crate) async fn assigned_index(&self) -> NodeIndex {
self.inner_state.lock().await.assigned_index
}
pub(crate) async fn post_key_submission(&self, assigned_index: NodeIndex) {
let mut guard = self.inner_state.lock().await;
guard.submitted_keys = true;
guard.assigned_index = assigned_index;
}
pub(crate) async fn post_dealing_submission(&self, share: Option<StateShare>) {
let mut guard = self.inner_state.lock().await;
guard.submitted_commitment = true;
guard.self_share = share;
}
pub(crate) async fn is_dealers_remote_address(&self, remote: SocketAddr) -> (bool, Epoch) {
let guard = self.inner_state.lock().await;
let epoch = guard.current_epoch;
let dealers = &guard.current_epoch_dealers;
(
dealers
.values()
.any(|dealer| dealer.remote_address.ip() == remote.ip()),
epoch,
)
}
pub(crate) async fn has_submitted_keys(&self) -> bool {
self.inner_state.lock().await.submitted_keys
}
pub(crate) async fn has_submitted_dealings_commitment(&self) -> bool {
self.inner_state.lock().await.submitted_commitment
}
pub(crate) async fn current_epoch(&self) -> Epoch {
self.inner_state.lock().await.current_epoch
}
pub(crate) async fn get_verified_dealing(
&self,
dealer: identity::PublicKey,
) -> Option<ReceivedDealing> {
self.inner_state
.lock()
.await
.verified_epoch_dealings
.get(&dealer.to_bytes())
.cloned()
}
pub(crate) async fn get_known_dealers(&self) -> HashMap<IdentityBytes, DkgParticipant> {
self.inner_state.lock().await.current_epoch_dealers.clone()
}
pub(crate) async fn get_malformed_dealers(&self) -> HashMap<Addr, MalformedDealer> {
self.inner_state.lock().await.bad_dealers.clone()
}
pub(crate) async fn update_last_seen_height(&self, new_last_seen: BlockHeight) {
self.inner_state.lock().await.last_seen_height = new_last_seen;
}
pub(crate) async fn get_known_commitments(&self) -> HashMap<Addr, KnownCommitment> {
self.inner_state
.lock()
.await
.expected_epoch_dealing_commitments
.clone()
}
pub(crate) async fn try_add_new_dealer(&self, dealer: DkgParticipant) {
// TODO: perhaps we should panic or something instead since this should have never occurred in the first place?
if let Some(old_dealer) = self
.inner_state
.lock()
.await
.current_epoch_dealers
.insert(dealer.map_key(), dealer)
{
error!(
"We have overwritten {} dealer details",
old_dealer.chain_address
)
}
}
pub(crate) async fn try_add_malformed_dealer(&self, dealer_details: MalformedDealer) {
// TODO: perhaps we should panic or something instead since this should have never occurred in the first place?
if let Some(old_dealer) = self
.inner_state
.lock()
.await
.bad_dealers
.insert(dealer_details.address().clone(), dealer_details)
{
error!(
"We have overwritten {} dealer details",
old_dealer.address()
)
}
}
pub(crate) async fn try_remove_dealer(&self, dealer_address: Addr) {
let mut guard = self.inner_state.lock().await;
// dealer is in either bad dealers or known dealers, never both,
// so if we managed to remove it from the former, we don't need to check the latter
if guard.bad_dealers.remove(&dealer_address).is_none() {
// find storage key associated with the entry we want to remove
let storage_key = guard
.current_epoch_dealers
.values()
.find(|&dealer| dealer.chain_address == dealer_address)
.map(|dealer| dealer.map_key());
match storage_key {
Some(key) => {
guard.current_epoch_dealers.remove(&key);
}
// this should be impossible as in order to get to this point we must have learned about
// this dealer existing somewhere in our state!
None => error!(
"We failed to remove {} dealer details as it somehow doesn't exist!",
dealer_address
),
}
}
}
pub(crate) async fn try_add_new_dealing_commitment(
&self,
dealer: &Addr,
commitment: MessageCommitment<CommittableEpochDealing<'static>>,
) {
// TODO: perhaps we should panic or something instead since this should have never occurred in the first place?
if let Some(old_commitment) = self
.inner_state
.lock()
.await
.expected_epoch_dealing_commitments
.insert(dealer.clone(), commitment)
{
error!(
"We have overwritten {} dealer commitment for the current epoch",
dealer
)
}
}
pub(crate) fn sign(&self, message: &[u8]) -> identity::Signature {
self.keys.identity.private_key().sign(message)
}
pub(crate) async fn prepare_dealer_registration(
&self,
chain_address: AccountId,
) -> DealerRegistration {
let bte_key = bs58::encode(&self.keys.bte_public_key.to_bytes()).into_string();
let network_address = self.network_address().await.to_string();
// chain_address || host || bte_keys
let mut plaintext = chain_address.to_string();
plaintext.push_str(&network_address);
plaintext.push_str(&bte_key);
let owner_signature = self.sign(plaintext.as_bytes()).to_base58_string();
DealerRegistration {
identity: self.keys.identity.public_key().to_base58_string(),
bte_key,
owner_signature,
network_address,
}
}
pub(crate) fn public_bte_key(&self) -> &bte::PublicKey {
self.keys.bte_public_key.public_key()
}
pub(crate) async fn sign_dealing(
&self,
epoch_id: EpochId,
dealing_bytes: &[u8],
) -> identity::Signature {
let mut bytes = Vec::with_capacity(dealing_bytes.len() + std::mem::size_of::<EpochId>());
bytes.extend_from_slice(&epoch_id.to_be_bytes());
bytes.extend_from_slice(dealing_bytes);
self.sign(&bytes)
}
}
+9 -3
View File
@@ -14,6 +14,7 @@ use ::config::NymConfig;
use anyhow::Result;
use clap::{crate_version, App, Arg, ArgMatches};
use contract_cache::ValidatorCache;
use dotenv::dotenv;
use log::{info, warn};
use rocket::fairing::AdHoc;
use rocket::http::Method;
@@ -36,6 +37,7 @@ use validator_client::nymd::SigningNymdClient;
pub(crate) mod config;
pub(crate) mod contract_cache;
mod dkg;
mod network_monitor;
mod node_status_api;
pub(crate) mod nymd_client;
@@ -557,12 +559,16 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
async fn main() -> Result<()> {
println!("Starting validator api...");
dotenv().ok();
cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] {
// instriment tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time
// instrument tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time
console_subscriber::init();
}}
setup_logging();
let args = parse_args();
run_validator_api(args).await
// let args = parse_args();
// run_validator_api(args).await
crate::dkg::dkg_only_main().await
}
+123 -2
View File
@@ -5,7 +5,13 @@ use crate::config::Config;
use crate::rewarded_set_updater::error::RewardingError;
#[cfg(feature = "coconut")]
use async_trait::async_trait;
use coconut_dkg_common::dealer::{ContractDealingCommitment, DealerDetailsResponse};
use coconut_dkg_common::types::{
BlacklistedDealer, BlacklistingResponse, Coin, DealerDetails, EncodedBTEPublicKeyWithProof,
EncodedEd25519PublicKey, Epoch as DkgEpoch, EpochId,
};
use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT};
use contracts_common::commitment::ContractSafeCommitment;
use mixnet_contract_common::Interval;
use mixnet_contract_common::{
reward_params::EpochRewardParams, ContractStateParams, Delegation, ExecuteMsg, GatewayBond,
@@ -16,10 +22,13 @@ use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::time::sleep;
use validator_client::nymd::cosmwasm_client::types::ExecuteResult;
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,
AccountId, CosmWasmClient, CosmosCoin, Fee, Height, QueryNymdClient, SigningCosmWasmClient,
SigningNymdClient, TendermintTime,
};
use validator_client::ValidatorClientError;
@@ -115,6 +124,15 @@ impl<C> Client<C> {
Ok(time)
}
pub(crate) async fn current_block_height(&self) -> Result<Height, ValidatorClientError>
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<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
@@ -267,6 +285,13 @@ impl<C> Client<C> {
.await
}
pub(crate) async fn address(&self) -> AccountId
where
C: SigningCosmWasmClient + Sync,
{
self.0.read().await.nymd.address().clone()
}
#[allow(dead_code)]
pub(crate) async fn advance_current_epoch(&self) -> Result<(), ValidatorClientError>
where
@@ -422,3 +447,99 @@ where
Ok(self.0.read().await.nymd.get_tx(tx_hash).await?)
}
}
// dkg-related impl block
impl<C> Client<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
pub(crate) async fn get_dkg_epoch(&self) -> Result<DkgEpoch, NymdError> {
self.0.read().await.nymd.get_current_dkg_epoch().await
}
pub(crate) async fn get_self_registered_dealer_details(
&self,
) -> Result<DealerDetailsResponse, NymdError> {
let self_address = &self.address().await;
self.0
.read()
.await
.nymd
.get_dealer_details(self_address)
.await
}
pub(crate) async fn get_current_dealers(
&self,
) -> Result<Vec<DealerDetails>, ValidatorClientError> {
self.0.read().await.get_all_nymd_current_dealers().await
}
pub(crate) async fn get_past_dealers(
&self,
) -> Result<Vec<DealerDetails>, ValidatorClientError> {
self.0.read().await.get_all_nymd_past_dealers().await
}
pub(crate) async fn get_blacklisted_dealers(
&self,
) -> Result<Vec<BlacklistedDealer>, ValidatorClientError> {
self.0.read().await.get_all_nymd_blacklisted_dealers().await
}
pub(crate) async fn get_blacklisting(
&self,
address: String,
) -> Result<BlacklistingResponse, NymdError> {
self.0.read().await.nymd.get_blacklisting(address).await
}
pub(crate) async fn get_minimum_deposit(&self) -> Result<Coin, NymdError> {
self.0
.read()
.await
.nymd
.get_deposit_amount()
.await
.map(|res| res.amount)
}
pub(crate) async fn get_epoch_dealings_commitments(
&self,
epoch_id: EpochId,
) -> Result<Vec<ContractDealingCommitment>, ValidatorClientError> {
self.0
.read()
.await
.get_all_nymd_epoch_dealings_commitments(epoch_id)
.await
}
pub(crate) async fn register_dealer(
&self,
identity: EncodedEd25519PublicKey,
bte_key: EncodedBTEPublicKeyWithProof,
owner_signature: String,
listening_address: String,
) -> Result<ExecuteResult, NymdError> {
self.0
.write()
.await
.nymd
.register_dealer(identity, bte_key, owner_signature, listening_address, None)
.await
}
pub(crate) async fn submit_dealing_commitment(
&self,
epoch_id: u32,
commitment: ContractSafeCommitment,
) -> Result<ExecuteResult, NymdError> {
self.0
.write()
.await
.nymd
.submit_dealing_commitment(epoch_id, commitment, None)
.await
}
}