Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f66d87525c | |||
| 625c9b376f | |||
| 9ceae4667a | |||
| dc2a386cd6 | |||
| 34b1fc8989 | |||
| 487a3a6cd8 | |||
| b919bd3d35 | |||
| 3b14511e16 | |||
| 11b9e6da43 | |||
| aee491173d | |||
| 7d92f0b70a | |||
| 00421c8b06 | |||
| 1165a61ca6 | |||
| 106e179f6c | |||
| c3034738f5 | |||
| e23f0bb952 | |||
| 8bc12bc079 | |||
| 34aca1d4bc | |||
| a3d6fc23f8 | |||
| 3328e06f45 | |||
| 0e853e7f32 | |||
| 7844a583bf | |||
| 4c4f9d7739 | |||
| 691049b85b | |||
| 50f26940be | |||
| 9f4029eb85 | |||
| fb09e97eb6 | |||
| 1318b4425d | |||
| 43f1194268 | |||
| bd216453cc | |||
| 7271d19afa | |||
| f85ac03cda | |||
| ff9cdba941 | |||
| 48e5fcd244 | |||
| 9d84e15a92 | |||
| 160046b359 | |||
| 72665d3878 | |||
| 5fb7ebeb63 | |||
| 3963aebc97 | |||
| 6554916fae | |||
| d71f860751 | |||
| 6541d4e0d3 | |||
| 1efc0847b5 | |||
| 1f0360c828 | |||
| 0ff602c7a7 | |||
| c541977c40 | |||
| 2dfb60e296 | |||
| 0d963aeb1f | |||
| 10d2a6eb8b | |||
| d148726b4b | |||
| ac7ed0d0dd | |||
| 4f561ba53b | |||
| 9f39aec198 | |||
| 22f4daaee4 | |||
| e9096bcfc4 | |||
| 77320e12c4 | |||
| 67a9334328 | |||
| 24f4b19e51 | |||
| 9d2b7d6940 | |||
| 1a8058aff5 |
Generated
+34
-2
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
Generated
+121
-87
@@ -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,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
|
||||
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Generated
+3
@@ -725,7 +725,10 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
name = "contracts-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)?))
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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() != ¤t_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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user