Feature/dkg rerun (#2839)

* Reset contract state when dkg needs rerun

* Reset nym-api for rerun

* Gateway updates signer APIs at runtime

* Fix clippy

* Add epoch id

* Use IndexedMap for shares

* Query with epoch id

* Add Clone to client traits

* Pass nyxd client instead of api data

* Get the specific epoch vk

* Make wasm work

* Remove wasm test runs

As there are no wasm tests and the target_arch macros are not compatible
with the cargo test environment, we can safely remove (for now) the wasm
test target runs.

* Put epoch_id in storage pk

* Gateway uses old keys but current verifiers

* Add group contract to env

* Move group msg in common

* Only run DKG if part of group

* Clippy test

* Rename wasm_storage to wasm_mockups

* Update changelog
This commit is contained in:
Bogdan-Ștefan Neacşu
2023-01-19 11:15:07 +02:00
committed by GitHub
parent d6f87c40ed
commit 64c963e36e
98 changed files with 754 additions and 354 deletions
-5
View File
@@ -29,11 +29,6 @@ jobs:
command: build
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --features=coconut
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path clients/webassembly/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
+8
View File
@@ -2,6 +2,14 @@
Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- dkg rerun from scratch and dkg-specific epochs ([#2839])
[#2839]: https://github.com/nymtech/nym/pull/2839
## [v1.1.6] (2023-01-17)
### Added
Generated
+13
View File
@@ -2368,6 +2368,15 @@ dependencies = [
"subtle 2.4.1",
]
[[package]]
name = "group-contract-common"
version = "0.1.0"
dependencies = [
"cw4",
"schemars",
"serde",
]
[[package]]
name = "h2"
version = "0.3.15"
@@ -3447,6 +3456,7 @@ dependencies = [
"crypto",
"cw-utils",
"cw3",
"cw4",
"dirs",
"dkg",
"dotenv",
@@ -3802,6 +3812,7 @@ dependencies = [
"tokio",
"toml",
"url",
"validator-client",
]
[[package]]
@@ -6860,9 +6871,11 @@ dependencies = [
"cosmrs",
"cosmwasm-std",
"cw3",
"cw4",
"execute",
"flate2",
"futures",
"group-contract-common",
"itertools",
"log",
"mixnet-contract-common",
+1
View File
@@ -34,6 +34,7 @@ members = [
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/coconut-dkg",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/vesting-contract",
+1 -4
View File
@@ -4,7 +4,7 @@ no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-main clippy-coconut clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect
cargo-test: test-main test-contracts test-wallet test-connect test-coconut test-wasm-client
cargo-test: test-main test-contracts test-wallet test-connect test-coconut
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive test-coconut-expensive
build: build-contracts build-wallet build-main build-connect build-wasm-client
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client
@@ -68,9 +68,6 @@ test-wallet:
test-wallet-expensive:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features -- --ignored
test-wasm-client:
cargo test --workspace --manifest-path clients/webassembly/Cargo.toml --all-features
test-connect:
cargo test --manifest-path nym-connect/Cargo.toml --all-features
@@ -25,6 +25,8 @@ use client_connections::{ConnectionCommandReceiver, ConnectionCommandSender, Lan
use crypto::asymmetric::{encryption, identity};
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
#[cfg(target_arch = "wasm32")]
use gateway_client::wasm_mockups::CosmWasmClient;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
@@ -39,6 +41,8 @@ use std::time::Duration;
use tap::TapFallible;
use task::{TaskClient, TaskManager};
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::CosmWasmClient;
use super::received_buffer::ReceivedBufferMessage;
@@ -129,7 +133,7 @@ impl From<bool> for CredentialsToggle {
}
}
pub struct BaseClientBuilder<'a, B> {
pub struct BaseClientBuilder<'a, B, C: Clone> {
// due to wasm limitations I had to split it like this : (
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
@@ -137,20 +141,21 @@ pub struct BaseClientBuilder<'a, B> {
nym_api_endpoints: Vec<Url>,
reply_storage_backend: B,
bandwidth_controller: Option<BandwidthController>,
bandwidth_controller: Option<BandwidthController<C>>,
key_manager: KeyManager,
}
impl<'a, B> BaseClientBuilder<'a, B>
impl<'a, B, C> BaseClientBuilder<'a, B, C>
where
B: ReplyStorageBackend + Send + Sync + 'static,
C: CosmWasmClient + Sync + Send + Clone + 'static,
{
pub fn new_from_base_config<T>(
base_config: &'a Config<T>,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController>,
bandwidth_controller: Option<BandwidthController<C>>,
reply_storage_backend: B,
) -> BaseClientBuilder<'a, B> {
) -> BaseClientBuilder<'a, B, C> {
BaseClientBuilder {
gateway_config: base_config.get_gateway_endpoint_config(),
debug_config: base_config.get_debug_config(),
@@ -166,11 +171,11 @@ where
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController>,
bandwidth_controller: Option<BandwidthController<C>>,
reply_storage_backend: B,
credentials_toggle: CredentialsToggle,
nym_api_endpoints: Vec<Url>,
) -> BaseClientBuilder<'a, B> {
) -> BaseClientBuilder<'a, B, C> {
BaseClientBuilder {
gateway_config,
debug_config,
@@ -279,7 +284,7 @@ where
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: TaskClient,
) -> Result<GatewayClient, ClientCoreError> {
) -> Result<GatewayClient<C>, ClientCoreError> {
let gateway_id = self.gateway_config.gateway_id.clone();
if gateway_id.is_empty() {
return Err(ClientCoreError::GatewayIdUnknown);
@@ -365,7 +370,7 @@ where
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
gateway_client: GatewayClient,
gateway_client: GatewayClient<C>,
shutdown: TaskClient,
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
+13 -4
View File
@@ -2,9 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
#[cfg(target_arch = "wasm32")]
use gateway_client::wasm_mockups::CosmWasmClient;
use gateway_client::GatewayClient;
use log::*;
use nymsphinx::forwarding::packet::MixPacket;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::CosmWasmClient;
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
@@ -13,10 +17,10 @@ pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 32;
const MAX_FAILURE_COUNT: usize = 100;
pub struct MixTrafficController {
pub struct MixTrafficController<C: Clone> {
// TODO: most likely to be replaced by some higher level construct as
// later on gateway_client will need to be accessible by other entities
gateway_client: GatewayClient,
gateway_client: GatewayClient<C>,
mix_rx: BatchMixMessageReceiver,
// TODO: this is temporary work-around.
@@ -24,8 +28,13 @@ pub struct MixTrafficController {
consecutive_gateway_failure_count: usize,
}
impl MixTrafficController {
pub fn new(gateway_client: GatewayClient) -> (MixTrafficController, BatchMixMessageSender) {
impl<C> MixTrafficController<C>
where
C: CosmWasmClient + Sync + Send + Clone + 'static,
{
pub fn new(
gateway_client: GatewayClient<C>,
) -> (MixTrafficController<C>, BatchMixMessageSender) {
let (sphinx_message_sender, sphinx_message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
(
+5 -1
View File
@@ -8,6 +8,8 @@ use crate::{
};
use config::NymConfig;
use crypto::asymmetric::identity;
#[cfg(target_arch = "wasm32")]
use gateway_client::wasm_mockups::SigningNyxdClient;
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
use rand::{seq::SliceRandom, thread_rng};
@@ -15,6 +17,8 @@ use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::SigningNyxdClient;
pub(super) async fn query_gateway_details(
validator_servers: Vec<Url>,
@@ -56,7 +60,7 @@ pub(super) async fn register_with_gateway(
our_identity: Arc<identity::KeyPair>,
) -> Result<Arc<SharedKeys>, ClientCoreError> {
let timeout = Duration::from_millis(1500);
let mut gateway_client = GatewayClient::new_init(
let mut gateway_client: GatewayClient<SigningNyxdClient> = GatewayClient::new_init(
gateway.clients_address(),
gateway.identity_key,
gateway.owner.clone(),
+4 -1
View File
@@ -13,6 +13,7 @@ use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use credentials::coconut::utils::obtain_aggregate_signature;
use crypto::asymmetric::{encryption, identity};
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
use validator_client::nyxd::traits::DkgQueryClient;
use validator_client::nyxd::tx::Hash;
use validator_client::{CoconutApiClient, Config};
@@ -80,7 +81,8 @@ pub(crate) async fn get_credential(state: &State, shared_storage: PersistentStor
let network_details = NymNetworkDetails::new_from_env();
let config = Config::try_from_nym_network_details(&network_details)?;
let client = validator_client::Client::new_query(config)?;
let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client).await?;
let epoch_id = client.nyxd.get_current_epoch().await?.epoch_id;
let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client, epoch_id).await?;
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let bandwidth_credential_attributes = BandwidthVoucher::new(
@@ -106,6 +108,7 @@ pub(crate) async fn get_credential(state: &State, shared_storage: PersistentStor
bandwidth_credential_attributes.get_private_attributes()[0].to_bs58(),
bandwidth_credential_attributes.get_private_attributes()[1].to_bs58(),
signature.to_bs58(),
epoch_id.to_string(),
)
.await?;
+21 -34
View File
@@ -21,6 +21,7 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
use nymsphinx::receiver::ReconstructedMessage;
use task::TaskManager;
use validator_client::nyxd::QueryNyxdClient;
pub(crate) mod config;
@@ -44,42 +45,28 @@ impl SocketClient {
}
}
async fn create_bandwidth_controller(config: &Config) -> BandwidthController {
#[cfg(feature = "coconut")]
let bandwidth_controller = {
let details = network_defaults::NymNetworkDetails::new_from_env();
let mut client_config =
validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config");
let nyxd_url = config
.get_base()
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
let api_url = config
.get_base()
.get_nym_api_endpoints()
.pop()
.expect("No validator api endpoint provided");
// overwrite env configuration with config URLs
client_config = client_config.with_urls(nyxd_url, api_url);
let client = validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
let coconut_api_clients =
validator_client::CoconutApiClient::all_coconut_api_clients(&client)
.await
.expect("Could not query api clients");
BandwidthController::new(
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
async fn create_bandwidth_controller(config: &Config) -> BandwidthController<QueryNyxdClient> {
let details = network_defaults::NymNetworkDetails::new_from_env();
let mut client_config = validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config");
let nyxd_url = config
.get_base()
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
let api_url = config
.get_base()
.get_nym_api_endpoints()
.pop()
.expect("No validator api endpoint provided");
// overwrite env configuration with config URLs
client_config = client_config.with_urls(nyxd_url, api_url);
let client = validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
BandwidthController::new(
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
client,
)
.expect("Could not create bandwidth controller");
bandwidth_controller
}
fn start_websocket_listener(
+21 -34
View File
@@ -20,6 +20,7 @@ use log::*;
use nymsphinx::addressing::clients::Recipient;
use std::error::Error;
use task::{TaskClient, TaskManager};
use validator_client::nyxd::QueryNyxdClient;
pub mod config;
@@ -53,42 +54,28 @@ impl NymClient {
}
}
async fn create_bandwidth_controller(config: &Config) -> BandwidthController {
#[cfg(feature = "coconut")]
let bandwidth_controller = {
let details = network_defaults::NymNetworkDetails::new_from_env();
let mut client_config =
validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config");
let nyxd_url = config
.get_base()
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
let api_url = config
.get_base()
.get_nym_api_endpoints()
.pop()
.expect("No validator api endpoint provided");
// overwrite env configuration with config URLs
client_config = client_config.with_urls(nyxd_url, api_url);
let client = validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
let coconut_api_clients =
validator_client::CoconutApiClient::all_coconut_api_clients(&client)
.await
.expect("Could not query api clients");
BandwidthController::new(
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
async fn create_bandwidth_controller(config: &Config) -> BandwidthController<QueryNyxdClient> {
let details = network_defaults::NymNetworkDetails::new_from_env();
let mut client_config = validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config");
let nyxd_url = config
.get_base()
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
let api_url = config
.get_base()
.get_nym_api_endpoints()
.pop()
.expect("No validator api endpoint provided");
// overwrite env configuration with config URLs
client_config = client_config.with_urls(nyxd_url, api_url);
let client = validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
BandwidthController::new(
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
client,
)
.expect("Could not create bandwidth controller");
bandwidth_controller
}
fn start_socks5_listener(
+2 -1
View File
@@ -11,6 +11,7 @@ use client_core::client::base_client::{
use client_core::client::replies::reply_storage::browser_backend;
use client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
use gateway_client::bandwidth::BandwidthController;
use gateway_client::wasm_mockups::SigningNyxdClient;
use js_sys::Promise;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
@@ -47,7 +48,7 @@ pub struct NymClientBuilder {
on_message: js_sys::Function,
// unimplemented:
bandwidth_controller: Option<BandwidthController>,
bandwidth_controller: Option<BandwidthController<SigningNyxdClient>>,
disabled_credentials: bool,
}
+2 -2
View File
@@ -25,7 +25,7 @@ gateway-requests = { path = "../../../gateway/gateway-requests" }
network-defaults = { path = "../../network-defaults" }
nymsphinx = { path = "../../nymsphinx" }
pemstore = { path = "../../pemstore" }
validator-client = { path = "../validator-client", optional = true }
validator-client = { path = "../validator-client" }
task = { path = "../../task" }
serde = { version = "1.0", features = ["derive"]}
@@ -79,5 +79,5 @@ features = ["js"]
#url = "2.1"
[features]
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut"]
coconut = ["gateway-requests/coconut", "coconut-interface", "credentials/coconut"]
wasm = []
@@ -1,23 +1,26 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "coconut")]
use crate::error::GatewayClientError;
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::Storage;
use crate::wasm_mockups::Storage;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::storage::Storage;
#[cfg(all(target_arch = "wasm32", feature = "coconut"))]
use crate::wasm_storage::StorageError;
use crate::wasm_mockups::StorageError;
#[cfg(all(not(target_arch = "wasm32"), feature = "coconut"))]
use credential_storage::error::StorageError;
#[cfg(target_arch = "wasm32")]
use crate::wasm_mockups::{Client, CosmWasmClient};
#[cfg(feature = "coconut")]
use std::str::FromStr;
#[cfg(feature = "coconut")]
use validator_client::client::CoconutApiClient;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::{nyxd::CosmWasmClient, Client};
#[cfg(feature = "coconut")]
use {
coconut_interface::Base58,
@@ -28,41 +31,34 @@ use {
// TODO: make it nicer for wasm (I don't want to touch it for this experiment)
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::PersistentStorage;
use crate::wasm_mockups::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
#[derive(Clone)]
pub struct BandwidthController<St: Storage = PersistentStorage> {
#[allow(dead_code)]
#[allow(dead_code)]
pub struct BandwidthController<C: Clone, St: Storage = PersistentStorage> {
storage: St,
#[cfg(feature = "coconut")]
coconut_api_clients: Vec<CoconutApiClient>,
nyxd_client: Client<C>,
}
impl<St> BandwidthController<St>
impl<C, St> BandwidthController<C, St>
where
C: CosmWasmClient + Sync + Send + Clone,
St: Storage + Clone + 'static,
{
#[cfg(feature = "coconut")]
pub fn new(storage: St, coconut_api_clients: Vec<CoconutApiClient>) -> Self {
pub fn new(storage: St, nyxd_client: Client<C>) -> Self {
BandwidthController {
storage,
coconut_api_clients,
nyxd_client,
}
}
#[cfg(not(feature = "coconut"))]
pub fn new(storage: St) -> Result<Self, GatewayClientError> {
Ok(BandwidthController { storage })
}
#[cfg(feature = "coconut")]
pub async fn prepare_coconut_credential(
&self,
) -> Result<(coconut_interface::Credential, i64), GatewayClientError> {
let verification_key = obtain_aggregate_verification_key(&self.coconut_api_clients).await?;
let bandwidth_credential = self.storage.get_next_coconut_credential().await?;
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
.map_err(|_| StorageError::InconsistentData)?;
@@ -73,6 +69,19 @@ where
coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?;
let signature =
coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
let epoch_id = u64::from_str(&bandwidth_credential.epoch_id)
.map_err(|_| StorageError::InconsistentData)?;
#[cfg(not(target_arch = "wasm32"))]
let coconut_api_clients = validator_client::CoconutApiClient::all_coconut_api_clients(
&self.nyxd_client,
epoch_id,
)
.await
.expect("Could not query api clients");
#[cfg(target_arch = "wasm32")]
let coconut_api_clients = vec![];
let verification_key = obtain_aggregate_verification_key(&coconut_api_clients).await?;
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
Ok((
@@ -81,6 +90,7 @@ where
voucher_info,
serial_number,
binding_number,
epoch_id,
&signature,
&verification_key,
)?,
@@ -32,9 +32,13 @@ use coconut_interface::Credential;
use credential_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::CosmWasmClient;
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::PersistentStorage;
use crate::wasm_mockups::CosmWasmClient;
#[cfg(target_arch = "wasm32")]
use crate::wasm_mockups::PersistentStorage;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
#[cfg(target_arch = "wasm32")]
@@ -43,7 +47,7 @@ use wasm_utils::websocket::JSWebsocket;
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient {
pub struct GatewayClient<C: Clone> {
authenticated: bool,
disabled_credentials_mode: bool,
bandwidth_remaining: i64,
@@ -55,7 +59,7 @@ pub struct GatewayClient {
connection: SocketState,
packet_router: PacketRouter,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
bandwidth_controller: Option<BandwidthController<C, PersistentStorage>>,
// reconnection related variables
/// Specifies whether client should try to reconnect to gateway on connection failure.
@@ -70,7 +74,10 @@ pub struct GatewayClient {
shutdown: TaskClient,
}
impl GatewayClient {
impl<C> GatewayClient<C>
where
C: CosmWasmClient + Sync + Send + Clone,
{
// TODO: put it all in a Config struct
#[allow(clippy::too_many_arguments)]
pub fn new(
@@ -82,7 +89,7 @@ impl GatewayClient {
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
bandwidth_controller: Option<BandwidthController<C, PersistentStorage>>,
shutdown: TaskClient,
) -> Self {
GatewayClient {
@@ -138,7 +145,7 @@ impl GatewayClient {
let shutdown = TaskClient::dummy();
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
GatewayClient {
GatewayClient::<C> {
authenticated: false,
disabled_credentials_mode: true,
bandwidth_remaining: 0,
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::StorageError;
use crate::wasm_mockups::StorageError;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::error::StorageError;
use gateway_requests::registration::handshake::error::HandshakeError;
+1 -1
View File
@@ -17,7 +17,7 @@ pub mod error;
pub mod packet_router;
pub mod socket_state;
#[cfg(target_arch = "wasm32")]
mod wasm_storage;
pub mod wasm_mockups;
/// Helper method for reading from websocket stream. Helps to flatten the structure.
pub(crate) fn cleanup_socket_message(
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use std::marker::PhantomData;
use thiserror::Error;
#[derive(Error, Debug)]
@@ -14,6 +15,18 @@ pub enum StorageError {
InconsistentData,
}
pub trait CosmWasmClient {}
#[derive(Clone)]
pub struct SigningNyxdClient {}
impl CosmWasmClient for SigningNyxdClient {}
#[derive(Clone)]
pub struct Client<C> {
_phantom: PhantomData<C>,
}
#[derive(Clone)]
pub struct PersistentStorage {}
@@ -24,6 +37,7 @@ pub struct CoconutCredential {
pub serial_number: String,
pub binding_number: String,
pub signature: String,
pub epoch_id: String,
}
#[async_trait]
@@ -17,6 +17,7 @@ mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
vesting-contract = { path = "../../../contracts/vesting" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -39,6 +40,7 @@ bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
cw3 = { version = "0.13.4", optional = true }
cw4 = { version = "0.13.4", optional = true }
prost = { version = "0.10", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
@@ -56,6 +58,7 @@ nyxd-client = [
"config",
"cosmrs",
"cw3",
"cw4",
"prost",
"flate2",
"sha2",
@@ -21,7 +21,9 @@ use crate::nyxd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient
use crate::nyxd::{self, CosmWasmClient, NyxdClient, QueryNyxdClient, SigningNyxdClient};
#[cfg(feature = "nyxd-client")]
use coconut_dkg_common::{
dealer::ContractDealing, types::DealerDetails, verification_key::ContractVKShare,
dealer::ContractDealing,
types::{DealerDetails, EpochId},
verification_key::ContractVKShare,
};
#[cfg(feature = "nyxd-client")]
use coconut_interface::Base58;
@@ -128,7 +130,8 @@ impl Config {
}
#[cfg(feature = "nyxd-client")]
pub struct Client<C> {
#[derive(Clone)]
pub struct Client<C: Clone> {
// TODO: we really shouldn't be storing a mnemonic here, but removing it would be
// non-trivial amount of work and it's out of scope of the current branch
mnemonic: Option<bip39::Mnemonic>,
@@ -218,7 +221,10 @@ impl Client<QueryNyxdClient> {
// nyxd wrappers
#[cfg(feature = "nyxd-client")]
impl<C> Client<C> {
impl<C> Client<C>
where
C: Clone,
{
// 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
@@ -676,6 +682,7 @@ impl<C> Client<C> {
pub async fn get_all_nyxd_verification_key_shares(
&self,
epoch_id: EpochId,
) -> Result<Vec<ContractVKShare>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
@@ -685,7 +692,11 @@ impl<C> Client<C> {
loop {
let mut paged_response = self
.nyxd
.get_vk_shares_paged(start_after.take(), self.verification_key_page_limit)
.get_vk_shares_paged(
epoch_id,
start_after.take(),
self.verification_key_page_limit,
)
.await?;
shares.append(&mut paged_response.shares);
@@ -730,7 +741,10 @@ impl<C> Client<C> {
// validator-api wrappers
#[cfg(feature = "nyxd-client")]
impl<C> Client<C> {
impl<C> Client<C>
where
C: Clone,
{
pub fn change_nym_api(&mut self, new_endpoint: Url) {
self.nym_api.change_url(new_endpoint)
}
@@ -792,14 +806,15 @@ pub struct CoconutApiClient {
#[cfg(feature = "nyxd-client")]
impl CoconutApiClient {
pub async fn all_coconut_api_clients<C>(
pub async fn all_coconut_api_clients<C: Clone>(
nyxd_client: &Client<C>,
epoch_id: EpochId,
) -> Result<Vec<Self>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
Ok(nyxd_client
.get_all_nyxd_verification_key_shares()
.get_all_nyxd_verification_key_shares(epoch_id)
.await?
.into_iter()
.filter_map(Self::try_from)
@@ -65,6 +65,7 @@ pub struct Config {
pub(crate) vesting_contract_address: Option<AccountId>,
pub(crate) bandwidth_claim_contract_address: Option<AccountId>,
pub(crate) coconut_bandwidth_contract_address: Option<AccountId>,
pub(crate) group_contract_address: Option<AccountId>,
pub(crate) multisig_contract_address: Option<AccountId>,
pub(crate) coconut_dkg_contract_address: Option<AccountId>,
// TODO: add this in later commits
@@ -119,6 +120,10 @@ impl Config {
.as_ref(),
prefix,
)?,
group_contract_address: Self::parse_optional_account(
details.contracts.group_contract_address.as_ref(),
prefix,
)?,
multisig_contract_address: Self::parse_optional_account(
details.contracts.multisig_contract_address.as_ref(),
prefix,
@@ -131,8 +136,8 @@ impl Config {
}
}
#[derive(Debug)]
pub struct NyxdClient<C> {
#[derive(Clone, Debug)]
pub struct NyxdClient<C: Clone> {
client: C,
config: Config,
client_address: Option<Vec<AccountId>>,
@@ -209,7 +214,10 @@ impl NyxdClient<SigningNyxdClient> {
}
}
impl<C> NyxdClient<C> {
impl<C> NyxdClient<C>
where
C: Clone,
{
pub fn current_config(&self) -> &Config {
&self.config
}
@@ -276,6 +284,10 @@ impl<C> NyxdClient<C> {
.unwrap()
}
pub fn group_contract_address(&self) -> &AccountId {
self.config.group_contract_address.as_ref().unwrap()
}
// TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits
// note: what unwrap is doing here is just moving a failure that would have normally
// occurred in `connect` when attempting to parse an empty address,
@@ -18,7 +18,7 @@ pub trait CoconutBandwidthQueryClient {
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> CoconutBandwidthQueryClient for NyxdClient<C> {
impl<C: CosmWasmClient + Sync + Send + Clone> CoconutBandwidthQueryClient for NyxdClient<C> {
async fn get_spent_credential(
&self,
blinded_serial_number: String,
@@ -30,7 +30,9 @@ pub trait CoconutBandwidthSigningClient {
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> CoconutBandwidthSigningClient for NyxdClient<C> {
impl<C: SigningCosmWasmClient + Sync + Send + Clone> CoconutBandwidthSigningClient
for NyxdClient<C>
{
async fn deposit(
&self,
amount: Coin,
@@ -8,7 +8,7 @@ use coconut_dkg_common::dealer::{
DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse,
};
use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use coconut_dkg_common::types::Epoch;
use coconut_dkg_common::types::{Epoch, EpochId};
use coconut_dkg_common::verification_key::PagedVKSharesResponse;
use cosmrs::AccountId;
@@ -39,6 +39,7 @@ pub trait DkgQueryClient {
) -> Result<PagedDealingsResponse, NyxdError>;
async fn get_vk_shares_paged(
&self,
epoch_id: EpochId,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedVKSharesResponse, NyxdError>;
@@ -47,7 +48,7 @@ pub trait DkgQueryClient {
#[async_trait]
impl<C> DkgQueryClient for NyxdClient<C>
where
C: CosmWasmClient + Send + Sync,
C: CosmWasmClient + Send + Sync + Clone,
{
async fn get_current_epoch(&self) -> Result<Epoch, NyxdError> {
let request = DkgQueryMsg::GetCurrentEpochState {};
@@ -119,10 +120,12 @@ where
async fn get_vk_shares_paged(
&self,
epoch_id: EpochId,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedVKSharesResponse, NyxdError> {
let request = DkgQueryMsg::GetVerificationKeys {
epoch_id,
limit: page_limit,
start_after,
};
@@ -36,7 +36,7 @@ pub trait DkgSigningClient {
#[async_trait]
impl<C> DkgSigningClient for NyxdClient<C>
where
C: SigningCosmWasmClient + Send + Sync,
C: SigningCosmWasmClient + Send + Sync + Clone,
{
async fn advance_dkg_epoch_state(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::AdvanceEpochState {};
@@ -0,0 +1,28 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nyxd::error::NyxdError;
use crate::nyxd::{CosmWasmClient, NyxdClient};
use group_contract_common::msg::QueryMsg;
use async_trait::async_trait;
use cw4::MemberResponse;
#[async_trait]
pub trait GroupQueryClient {
async fn member(&self, addr: String) -> Result<MemberResponse, NyxdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send + Clone> GroupQueryClient for NyxdClient<C> {
async fn member(&self, addr: String) -> Result<MemberResponse, NyxdError> {
let request = QueryMsg::Member {
addr,
at_height: None,
};
self.client
.query_contract_smart(self.group_contract_address(), &request)
.await
}
}
@@ -395,7 +395,7 @@ pub trait MixnetQueryClient {
#[async_trait]
impl<C> MixnetQueryClient for NyxdClient<C>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync + Send + Clone,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NyxdError>
where
@@ -410,7 +410,7 @@ where
#[async_trait]
impl<C> MixnetQueryClient for crate::Client<C>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync + Send + Clone,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NyxdError>
where
@@ -627,7 +627,7 @@ pub trait MixnetSigningClient {
#[async_trait]
impl<C> MixnetSigningClient for NyxdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
C: SigningCosmWasmClient + Sync + Send + Clone,
{
async fn execute_mixnet_contract(
&self,
@@ -653,7 +653,7 @@ where
#[async_trait]
impl<C> MixnetSigningClient for crate::Client<C>
where
C: SigningCosmWasmClient + Sync + Send,
C: SigningCosmWasmClient + Sync + Send + Clone,
{
async fn execute_mixnet_contract(
&self,
@@ -5,6 +5,7 @@ mod coconut_bandwidth_query_client;
mod coconut_bandwidth_signing_client;
mod dkg_query_client;
mod dkg_signing_client;
mod group_query_client;
mod mixnet_query_client;
mod mixnet_signing_client;
mod multisig_query_client;
@@ -16,6 +17,7 @@ pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use dkg_query_client::DkgQueryClient;
pub use dkg_signing_client::DkgSigningClient;
pub use group_query_client::GroupQueryClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_query_client::MultisigQueryClient;
@@ -20,7 +20,7 @@ pub trait MultisigQueryClient {
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> MultisigQueryClient for NyxdClient<C> {
impl<C: CosmWasmClient + Sync + Send + Clone> MultisigQueryClient for NyxdClient<C> {
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NyxdError> {
let request = QueryMsg::Proposal { proposal_id };
self.client
@@ -38,7 +38,7 @@ pub trait MultisigSigningClient {
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> MultisigSigningClient for NyxdClient<C> {
impl<C: SigningCosmWasmClient + Sync + Send + Clone> MultisigSigningClient for NyxdClient<C> {
async fn propose_release_funds(
&self,
title: String,
@@ -269,7 +269,7 @@ pub trait VestingQueryClient {
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NyxdClient<C> {
impl<C: CosmWasmClient + Sync + Send + Clone> VestingQueryClient for NyxdClient<C> {
async fn query_vesting_contract<T>(&self, query: VestingQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
@@ -129,7 +129,7 @@ pub trait VestingSigningClient {
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NyxdClient<C> {
impl<C: SigningCosmWasmClient + Sync + Send + Clone> VestingSigningClient for NyxdClient<C> {
async fn execute_vesting_contract(
&self,
fee: Option<Fee>,
+11 -2
View File
@@ -18,6 +18,8 @@ pub struct Credential {
theta: Theta,
voucher_value: u64,
voucher_info: String,
#[getset(get = "pub")]
epoch_id: u64,
}
impl Credential {
pub fn new(
@@ -25,12 +27,14 @@ impl Credential {
theta: Theta,
voucher_value: u64,
voucher_info: String,
epoch_id: u64,
) -> Credential {
Credential {
n_params,
theta,
voucher_value,
voucher_info,
epoch_id,
}
}
@@ -68,6 +72,7 @@ impl Credential {
let theta_bytes = self.theta.to_bytes();
let theta_bytes_len = theta_bytes.len();
let voucher_value_bytes = self.voucher_value.to_be_bytes();
let epoch_id_bytes = self.epoch_id.to_be_bytes();
let voucher_info_bytes = self.voucher_info.as_bytes();
let voucher_info_len = voucher_info_bytes.len();
@@ -76,6 +81,7 @@ impl Credential {
bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes());
bytes.extend_from_slice(&theta_bytes);
bytes.extend_from_slice(&voucher_value_bytes);
bytes.extend_from_slice(&epoch_id_bytes);
bytes.extend_from_slice(voucher_info_bytes);
bytes
@@ -103,7 +109,9 @@ impl Credential {
.map_err(|e| CoconutError::Deserialization(e.to_string()))?;
eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]);
let voucher_value = u64::from_be_bytes(eight_byte);
let voucher_info = String::from_utf8(bytes[20 + theta_len as usize..].to_vec())
eight_byte.copy_from_slice(&bytes[20 + theta_len as usize..28 + theta_len as usize]);
let epoch_id = u64::from_be_bytes(eight_byte);
let voucher_info = String::from_utf8(bytes[28 + theta_len as usize..].to_vec())
.map_err(|e| CoconutError::Deserialization(e.to_string()))?;
Ok(Credential {
@@ -111,6 +119,7 @@ impl Credential {
theta,
voucher_value,
voucher_info,
epoch_id,
})
}
}
@@ -166,7 +175,7 @@ mod tests {
binding_number,
)
.unwrap();
let credential = Credential::new(4, theta, voucher_value, voucher_info);
let credential = Credential::new(4, theta, voucher_value, voucher_info, 42);
let serialized_credential = credential.as_bytes();
let deserialized_credential = Credential::from_bytes(&serialized_credential).unwrap();
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, TimeConfiguration};
use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration};
use crate::verification_key::VerificationKeyShare;
use cosmwasm_std::Addr;
use schemars::JsonSchema;
@@ -35,6 +35,8 @@ pub enum ExecuteMsg {
owner: Addr,
},
SurpassedThreshold {},
AdvanceEpochState {},
}
@@ -60,6 +62,7 @@ pub enum QueryMsg {
start_after: Option<String>,
},
GetVerificationKeys {
epoch_id: EpochId,
limit: Option<u32>,
start_after: Option<String>,
},
@@ -13,6 +13,7 @@ pub use cosmwasm_std::{Addr, Coin, Timestamp};
pub type EncodedBTEPublicKeyWithProof = String;
pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str;
pub type NodeIndex = u64;
pub type EpochId = u64;
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
pub const TOTAL_DEALINGS: usize = 2 + 2 + 1;
@@ -72,6 +73,7 @@ impl Default for TimeConfiguration {
#[serde(rename_all = "snake_case")]
pub struct Epoch {
pub state: EpochState,
pub epoch_id: EpochId,
pub time_configuration: TimeConfiguration,
pub finish_timestamp: Timestamp,
}
@@ -79,6 +81,7 @@ pub struct Epoch {
impl Epoch {
pub fn new(
state: EpochState,
epoch_id: u64,
time_configuration: TimeConfiguration,
current_timestamp: Timestamp,
) -> Self {
@@ -98,6 +101,7 @@ impl Epoch {
};
Epoch {
state,
epoch_id,
time_configuration,
finish_timestamp: current_timestamp.plus_seconds(duration),
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::msg::ExecuteMsg;
use crate::types::NodeIndex;
use crate::types::{EpochId, NodeIndex};
use cosmwasm_std::{from_binary, to_binary, Addr, CosmosMsg, StdResult, Timestamp, WasmMsg};
use cw_utils::Expiration;
use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
@@ -16,6 +16,7 @@ pub struct ContractVKShare {
pub announce_address: String,
pub node_index: NodeIndex,
pub owner: Addr,
pub epoch_id: EpochId,
pub verified: bool,
}
@@ -0,0 +1,11 @@
[package]
name = "group-contract-common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cw4 = { version = "0.13.4" }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -0,0 +1 @@
pub mod msg;
@@ -1,3 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -0,0 +1,17 @@
/*
* Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
DROP TABLE coconut_credentials;
CREATE TABLE coconut_credentials
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
voucher_value TEXT NOT NULL,
voucher_info TEXT NOT NULL,
serial_number TEXT NOT NULL,
binding_number TEXT NOT NULL,
signature TEXT NOT NULL UNIQUE,
epoch_id TEXT NOT NULL,
consumed BOOLEAN NOT NULL
);
+3 -2
View File
@@ -34,10 +34,11 @@ impl CoconutCredentialManager {
serial_number: String,
binding_number: String,
signature: String,
epoch_id: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO coconut_credentials(voucher_value, voucher_info, serial_number, binding_number, signature, consumed) VALUES (?, ?, ?, ?, ?, ?)",
voucher_value, voucher_info, serial_number, binding_number, signature, false
"INSERT INTO coconut_credentials(voucher_value, voucher_info, serial_number, binding_number, signature, epoch_id, consumed) VALUES (?, ?, ?, ?, ?, ?, ?)",
voucher_value, voucher_info, serial_number, binding_number, signature, epoch_id, false
)
.execute(&self.connection_pool)
.await?;
+2
View File
@@ -70,6 +70,7 @@ impl Storage for PersistentStorage {
serial_number: String,
binding_number: String,
signature: String,
epoch_id: String,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.insert_coconut_credential(
@@ -78,6 +79,7 @@ impl Storage for PersistentStorage {
serial_number,
binding_number,
signature,
epoch_id,
)
.await?;
+1
View File
@@ -9,5 +9,6 @@ pub struct CoconutCredential {
pub serial_number: String,
pub binding_number: String,
pub signature: String,
pub epoch_id: String,
pub consumed: bool,
}
+6
View File
@@ -12,7 +12,12 @@ pub trait Storage: Send + Sync {
///
/// # Arguments
///
/// * `voucher_value`: How much bandwidth is in the credential.
/// * `voucher_info`: What type of credential it is.
/// * `serial_number`: Serial number of the credential.
/// * `binding_number`: Binding number of the credential.
/// * `signature`: Coconut credential in the form of a signature.
/// * `epoch_id`: The epoch when it was signed.
async fn insert_coconut_credential(
&self,
voucher_value: String,
@@ -20,6 +25,7 @@ pub trait Storage: Send + Sync {
serial_number: String,
binding_number: String,
signature: String,
epoch_id: String,
) -> Result<(), StorageError>;
/// Tries to retrieve one of the stored, unused credentials.
@@ -168,6 +168,7 @@ pub fn prepare_for_spending(
voucher_info: String,
serial_number: PrivateAttribute,
binding_number: PrivateAttribute,
epoch_id: u64,
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
@@ -179,6 +180,7 @@ pub fn prepare_for_spending(
voucher_info,
serial_number,
binding_number,
epoch_id,
signature,
verification_key,
)
+3
View File
@@ -137,12 +137,14 @@ pub async fn obtain_aggregate_signature(
}
// TODO: better type flow
#[allow(clippy::too_many_arguments)]
pub fn prepare_credential_for_spending(
params: &Parameters,
voucher_value: u64,
voucher_info: String,
serial_number: Attribute,
binding_number: Attribute,
epoch_id: u64,
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
@@ -159,5 +161,6 @@ pub fn prepare_credential_for_spending(
theta,
voucher_value,
voucher_info,
epoch_id,
))
}
+11
View File
@@ -36,6 +36,7 @@ pub struct NymContracts {
pub vesting_contract_address: Option<String>,
pub bandwidth_claim_contract_address: Option<String>,
pub coconut_bandwidth_contract_address: Option<String>,
pub group_contract_address: Option<String>,
pub multisig_contract_address: Option<String>,
pub coconut_dkg_contract_address: Option<String>,
}
@@ -95,6 +96,9 @@ impl NymNetworkDetails {
var(var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
.expect("coconut bandwidth contract not set"),
))
.with_group_contract(Some(
var(var_names::GROUP_CONTRACT_ADDRESS).expect("group contract not set"),
))
.with_multisig_contract(Some(
var(var_names::MULTISIG_CONTRACT_ADDRESS).expect("multisig contract not set"),
))
@@ -125,6 +129,7 @@ impl NymNetworkDetails {
coconut_bandwidth_contract_address: parse_optional_str(
mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
group_contract_address: parse_optional_str(mainnet::GROUP_CONTRACT_ADDRESS),
multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(
mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
@@ -193,6 +198,12 @@ impl NymNetworkDetails {
self
}
#[must_use]
pub fn with_group_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.multisig_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_multisig_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.multisig_contract_address = contract.map(Into::into);
+3
View File
@@ -17,6 +17,7 @@ pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
@@ -81,6 +82,7 @@ pub fn export_to_env() {
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
set_var_to_default(var_names::GROUP_CONTRACT_ADDRESS, GROUP_CONTRACT_ADDRESS);
set_var_to_default(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
@@ -125,6 +127,7 @@ pub fn export_to_env_if_not_set() {
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(var_names::GROUP_CONTRACT_ADDRESS, GROUP_CONTRACT_ADDRESS);
set_var_conditionally_to_default(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
+1
View File
@@ -14,6 +14,7 @@ pub const MIXNET_CONTRACT_ADDRESS: &str = "MIXNET_CONTRACT_ADDRESS";
pub const VESTING_CONTRACT_ADDRESS: &str = "VESTING_CONTRACT_ADDRESS";
pub const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "BANDWIDTH_CLAIM_CONTRACT_ADDRESS";
pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT_ADDRESS";
pub const GROUP_CONTRACT_ADDRESS: &str = "GROUP_CONTRACT_ADDRESS";
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
+3
View File
@@ -89,6 +89,9 @@ pub fn theta_from_keys_and_attributes(
}
pub fn transpose_matrix<T: Debug>(matrix: Vec<Vec<T>>) -> Vec<Vec<T>> {
if matrix.is_empty() {
return vec![];
}
let len = matrix[0].len();
let mut iters: Vec<_> = matrix.into_iter().map(|d| d.into_iter()).collect();
(0..len)
+2 -1
View File
@@ -23,4 +23,5 @@ thiserror = "1.0.23"
[dev-dependencies]
cw-multi-test = { version = "0.13.4" }
cw4-group = { path = "../multisig/cw4-group" }
cw4-group = { path = "../multisig/cw4-group" }
group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
+3
View File
@@ -3,3 +3,6 @@
// Wait time for the verification to take place (currently 24h)
pub(crate) const BLOCK_TIME_FOR_VERIFICATION_SECS: u64 = 86400;
pub(crate) const VK_SHARES_PK_NAMESPACE: &str = "vksp";
pub(crate) const VK_SHARES_EPOCH_ID_IDX_NAMESPACE: &str = "vkse";
+9 -5
View File
@@ -9,7 +9,7 @@ use crate::dealings::queries::query_dealings_paged;
use crate::dealings::transactions::try_commit_dealings;
use crate::epoch_state::queries::{query_current_epoch, query_current_epoch_threshold};
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::advance_epoch_state;
use crate::epoch_state::transactions::{advance_epoch_state, try_surpassed_threshold};
use crate::error::ContractError;
use crate::state::{State, MULTISIG, STATE};
use crate::verification_key_shares::queries::query_vk_shares_paged;
@@ -54,6 +54,7 @@ pub fn instantiate(
deps.storage,
&Epoch::new(
EpochState::default(),
0,
msg.time_configuration.unwrap_or_default(),
env.block.time,
),
@@ -84,6 +85,7 @@ pub fn execute(
ExecuteMsg::VerifyVerificationKeyShare { owner } => {
try_verify_verification_key_share(deps, info, owner)
}
ExecuteMsg::SurpassedThreshold {} => try_surpassed_threshold(deps, env),
ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env),
}
}
@@ -109,9 +111,11 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
limit,
start_after,
} => to_binary(&query_dealings_paged(deps, idx, start_after, limit)?)?,
QueryMsg::GetVerificationKeys { limit, start_after } => {
to_binary(&query_vk_shares_paged(deps, start_after, limit)?)?
}
QueryMsg::GetVerificationKeys {
epoch_id,
limit,
start_after,
} => to_binary(&query_vk_shares_paged(deps, epoch_id, start_after, limit)?)?,
};
Ok(response)
@@ -132,8 +136,8 @@ mod tests {
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, Addr};
use cw4::Member;
use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg;
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
use group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg;
fn instantiate_with_group(app: &mut App, members: &[Addr]) -> Addr {
let group_code_id = app.store_code(Box::new(ContractWrapper::new(
@@ -1,11 +1,49 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage::current_dealers;
use crate::dealers::storage::{current_dealers, past_dealers};
use crate::dealings::storage::DEALINGS_BYTES;
use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD};
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::STATE;
use coconut_dkg_common::types::{Epoch, EpochState};
use cosmwasm_std::{DepsMut, Env, Order, Response};
use cosmwasm_std::{DepsMut, Env, Order, Response, Storage};
fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
THRESHOLD.remove(storage);
let dealers: Vec<_> = current_dealers()
.keys(storage, None, None, Order::Ascending)
.collect::<Result<_, _>>()?;
for dealer_addr in dealers {
let details = current_dealers().load(storage, &dealer_addr)?;
for dealings in DEALINGS_BYTES {
dealings.remove(storage, &details.address);
}
current_dealers().remove(storage, &dealer_addr)?;
past_dealers().save(storage, &dealer_addr, &details)?;
}
Ok(())
}
fn dealers_still_active(deps: &DepsMut<'_>) -> Result<usize, ContractError> {
let state = STATE.load(deps.storage)?;
let mut still_active = 0;
for dealer_addr in current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten()
{
if state
.group_addr
.is_voting_member(&deps.querier, &dealer_addr, None)?
.is_some()
{
still_active += 1;
}
}
Ok(still_active)
}
pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response, ContractError> {
let epoch = CURRENT_EPOCH.load(deps.storage)?;
@@ -18,24 +56,71 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
));
}
let current_epoch = CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |mut epoch| {
// TODO: When defaulting to the first state, some action will probably need to be taken on the
// rest of the contract, as we're starting with a new set of signers
epoch = Epoch::new(
epoch.state.next().unwrap_or_default(),
epoch.time_configuration,
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
let next_epoch = if let Some(state) = current_epoch.state.next() {
// We are during DKG process
if state == EpochState::DealingExchange {
let current_dealer_count = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.count();
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
let threshold = (2 * current_dealer_count as u64 + 3 - 1) / 3;
THRESHOLD.save(deps.storage, &threshold)?;
}
Epoch::new(
state,
current_epoch.epoch_id,
current_epoch.time_configuration,
env.block.time,
);
Ok(epoch)
})?;
if current_epoch.state == EpochState::DealingExchange {
let current_dealer_count = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.count();
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
let threshold = (2 * current_dealer_count as u64 + 3 - 1) / 3;
THRESHOLD.save(deps.storage, &threshold)?;
)
} else if dealers_still_active(&deps)?
== STATE
.load(deps.storage)?
.group_addr
.list_members(&deps.querier, None, None)?
.len()
{
// The dealer set hasn't changed, so we only extend the finish timestamp
Epoch::new(
current_epoch.state,
current_epoch.epoch_id,
current_epoch.time_configuration,
env.block.time,
)
} else {
// Dealer set changed, we need to redo DKG from scratch
reset_epoch_state(deps.storage)?;
Epoch::new(
EpochState::default(),
current_epoch.epoch_id + 1,
current_epoch.time_configuration,
env.block.time,
)
};
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
Ok(Response::default())
}
pub(crate) fn try_surpassed_threshold(
deps: DepsMut<'_>,
env: Env,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::InProgress)?;
let threshold = THRESHOLD.load(deps.storage)?;
if dealers_still_active(&deps)? < threshold as usize {
reset_epoch_state(deps.storage)?;
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
Ok(Epoch::new(
EpochState::default(),
epoch.epoch_id + 1,
epoch.time_configuration,
env.block.time,
))
})?;
}
Ok(Response::default())
}
@@ -186,17 +271,6 @@ pub(crate) mod tests {
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
EarlyEpochStateAdvancement(50)
);
env.block.time = env.block.time.plus_seconds(100);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::PublicKeySubmission);
assert_eq!(
epoch.finish_timestamp,
env.block
.time
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs)
);
}
#[test]
@@ -37,7 +37,7 @@ pub(crate) mod test {
CURRENT_EPOCH
.save(
deps.as_mut().storage,
&Epoch::new(fixed_state, TimeConfiguration::default(), env.block.time),
&Epoch::new(fixed_state, 0, TimeConfiguration::default(), env.block.time),
)
.unwrap();
for against_state in EpochState::default().all_until(EpochState::InProgress) {
@@ -14,6 +14,7 @@ pub fn vk_share_fixture(index: u64) -> ContractVKShare {
announce_address: format!("localhost:{}", index),
node_index: index,
owner: Addr::unchecked(format!("owner{}", index)),
epoch_id: 0,
verified: index % 2 == 0,
}
}
@@ -2,13 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use crate::verification_key_shares::storage;
use crate::verification_key_shares::storage::VK_SHARES;
use crate::verification_key_shares::storage::vk_shares;
use coconut_dkg_common::types::EpochId;
use coconut_dkg_common::verification_key::PagedVKSharesResponse;
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
pub fn query_vk_shares_paged(
deps: Deps<'_>,
epoch_id: EpochId,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedVKSharesResponse> {
@@ -20,9 +22,12 @@ pub fn query_vk_shares_paged(
.map(|addr| deps.api.addr_validate(&addr))
.transpose()?;
let start = addr.as_ref().map(Bound::exclusive);
let start = addr.as_ref().map(|addr| Bound::exclusive((addr, epoch_id)));
let shares = VK_SHARES
let shares = vk_shares()
.idx
.epoch_id
.prefix(epoch_id)
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|(_, share)| share))
@@ -50,7 +55,7 @@ pub(crate) mod tests {
#[test]
fn vk_shares_empty_on_init() {
let deps = init_contract();
let response = query_vk_shares_paged(deps.as_ref(), None, Option::from(2)).unwrap();
let response = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(2)).unwrap();
assert_eq!(0, response.shares.len());
}
@@ -61,12 +66,12 @@ pub(crate) mod tests {
for n in 0..1000 {
let vk_share = vk_share_fixture(n);
let sender = Addr::unchecked(format!("owner{}", n));
VK_SHARES
.save(&mut deps.storage, &sender, &vk_share)
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
.unwrap();
}
let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.shares.len() as u32);
}
@@ -76,13 +81,13 @@ pub(crate) mod tests {
for n in 0..1000 {
let vk_share = vk_share_fixture(n);
let sender = Addr::unchecked(format!("owner{}", n));
VK_SHARES
.save(&mut deps.storage, &sender, &vk_share)
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
.unwrap();
}
// query without explicitly setting a limit
let page1 = query_vk_shares_paged(deps.as_ref(), None, None).unwrap();
let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, None).unwrap();
assert_eq!(
VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT,
@@ -96,14 +101,15 @@ pub(crate) mod tests {
for n in 0..1000 {
let vk_share = vk_share_fixture(n);
let sender = Addr::unchecked(format!("owner{}", n));
VK_SHARES
.save(&mut deps.storage, &sender, &vk_share)
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
.unwrap();
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT;
let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
let page1 =
query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
let expected_limit = VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT;
@@ -116,12 +122,12 @@ pub(crate) mod tests {
let vk_share = vk_share_fixture(1);
let sender = Addr::unchecked(format!("owner{}", 1));
VK_SHARES
.save(&mut deps.storage, &sender, &vk_share)
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
.unwrap();
let per_page = 2;
let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.shares.len());
@@ -129,28 +135,29 @@ pub(crate) mod tests {
// save another
let vk_share = vk_share_fixture(2);
let sender = Addr::unchecked(format!("owner{}", 2));
VK_SHARES
.save(&mut deps.storage, &sender, &vk_share)
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
.unwrap();
// page1 should have 2 results on it
let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.shares.len());
let vk_share = vk_share_fixture(3);
let sender = Addr::unchecked(format!("owner{}", 3));
VK_SHARES
.save(&mut deps.storage, &sender, &vk_share)
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
.unwrap();
// page1 still has 2 results
let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.shares.len());
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_vk_shares_paged(
deps.as_ref(),
0,
Option::from(start_after.to_string()),
Option::from(per_page),
)
@@ -160,12 +167,13 @@ pub(crate) mod tests {
let vk_share = vk_share_fixture(4);
let sender = Addr::unchecked(format!("owner{}", 4));
VK_SHARES
.save(&mut deps.storage, &sender, &vk_share)
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
.unwrap();
let page2 = query_vk_shares_paged(
deps.as_ref(),
0,
Option::from(start_after.to_string()),
Option::from(per_page),
)
@@ -3,13 +3,35 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE};
use coconut_dkg_common::types::EpochId;
use coconut_dkg_common::verification_key::ContractVKShare;
use cosmwasm_std::Addr;
use cw_storage_plus::Map;
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT: u32 = 50;
type VKShareKey<'a> = &'a Addr;
type VKShareKey<'a> = (&'a Addr, EpochId);
pub(crate) const VK_SHARES: Map<'_, VKShareKey<'_>, ContractVKShare> = Map::new("vks");
pub(crate) struct VkShareIndex<'a> {
pub(crate) epoch_id: MultiIndex<'a, EpochId, ContractVKShare, VKShareKey<'a>>,
}
impl<'a> IndexList<ContractVKShare> for VkShareIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<ContractVKShare>> + '_> {
let v: Vec<&dyn Index<ContractVKShare>> = vec![&self.epoch_id];
Box::new(v.into_iter())
}
}
pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare, VkShareIndex<'a>> {
let indexes = VkShareIndex {
epoch_id: MultiIndex::new(
|d| d.epoch_id,
VK_SHARES_PK_NAMESPACE,
VK_SHARES_EPOCH_ID_IDX_NAMESPACE,
),
};
IndexedMap::new(VK_SHARES_PK_NAMESPACE, indexes)
}
@@ -3,10 +3,11 @@
use crate::constants::BLOCK_TIME_FOR_VERIFICATION_SECS;
use crate::dealers::storage as dealers_storage;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::{MULTISIG, STATE};
use crate::verification_key_shares::storage::VK_SHARES;
use crate::verification_key_shares::storage::vk_shares;
use coconut_dkg_common::types::EpochState;
use coconut_dkg_common::verification_key::{to_cosmos_msg, ContractVKShare, VerificationKeyShare};
use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response};
@@ -22,7 +23,11 @@ pub fn try_commit_verification_key_share(
let details = dealers_storage::current_dealers()
.load(deps.storage, &info.sender)
.map_err(|_| ContractError::NotADealer)?;
if VK_SHARES.may_load(deps.storage, &info.sender)?.is_some() {
let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
if vk_shares()
.may_load(deps.storage, (&info.sender, epoch_id))?
.is_some()
{
return Err(ContractError::AlreadyCommitted {
commitment: String::from("verification key share"),
});
@@ -33,9 +38,10 @@ pub fn try_commit_verification_key_share(
node_index: details.assigned_index,
announce_address: details.announce_address,
owner: info.sender.clone(),
epoch_id: CURRENT_EPOCH.load(deps.storage)?.epoch_id,
verified: false,
};
VK_SHARES.save(deps.storage, &info.sender, &data)?;
vk_shares().save(deps.storage, (&info.sender, epoch_id), &data)?;
let msg = to_cosmos_msg(
info.sender,
@@ -55,8 +61,9 @@ pub fn try_verify_verification_key_share(
owner: Addr,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::VerificationKeyFinalization)?;
let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
MULTISIG.assert_admin(deps.as_ref(), &info.sender)?;
VK_SHARES.update(deps.storage, &owner, |vk_share| {
vk_shares().update(deps.storage, (&owner, epoch_id), |vk_share| {
vk_share
.map(|mut share| {
share.verified = true;
+1
View File
@@ -10,6 +10,7 @@ bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" }
multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
@@ -7,9 +7,9 @@ use coconut_bandwidth_contract_common::{
spend_credential::SpendCredentialData,
};
use cosmwasm_std::{coins, Addr, Coin, Decimal};
use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg;
use cw_multi_test::Executor;
use cw_utils::{Duration, Threshold};
use group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg;
use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg;
pub const TEST_COIN_DENOM: &str = "unym";
@@ -15,9 +15,9 @@ use coconut_dkg_common::msg::QueryMsg::GetVerificationKeys;
use coconut_dkg_common::verification_key::PagedVKSharesResponse;
use cosmwasm_std::{coins, Addr, Decimal};
use cw4::Member;
use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg;
use cw_multi_test::Executor;
use cw_utils::{Duration, Threshold};
use group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg;
use multisig_contract_common::msg::ExecuteMsg::{Execute, Vote};
use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg;
@@ -152,6 +152,7 @@ fn dkg_proposal() {
.query_wasm_smart(
coconut_dkg_contract_addr.clone(),
&GetVerificationKeys {
epoch_id: 0,
limit: None,
start_after: None,
},
@@ -199,6 +200,7 @@ fn dkg_proposal() {
.query_wasm_smart(
coconut_dkg_contract_addr,
&GetVerificationKeys {
epoch_id: 0,
limit: None,
start_after: None,
},
@@ -28,6 +28,7 @@ schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" }
[dev-dependencies]
@@ -502,7 +502,7 @@ mod tests {
// uploads code and returns address of group contract
fn instantiate_group(app: &mut App, members: Vec<Member>) -> Addr {
let group_id = app.store_code(contract_group());
let msg = cw4_group::msg::InstantiateMsg {
let msg = group_contract_common::msg::InstantiateMsg {
admin: Some(OWNER.into()),
members,
};
@@ -1410,7 +1410,7 @@ mod tests {
// adds NEWBIE with 2 power -> with snapshot, invalid vote
// removes VOTER3 -> with snapshot, can vote on proposal
let newbie: &str = "newbie";
let update_msg = cw4_group::msg::ExecuteMsg::UpdateMembers {
let update_msg = group_contract_common::msg::ExecuteMsg::UpdateMembers {
remove: vec![VOTER3.into()],
add: vec![member(VOTER2, 21), member(newbie, 2)],
};
@@ -1623,7 +1623,7 @@ mod tests {
// admin changes the group (3 -> 0, 2 -> 9, 0 -> 29) - total = 56, require 29 to pass
let newbie: &str = "newbie";
let update_msg = cw4_group::msg::ExecuteMsg::UpdateMembers {
let update_msg = group_contract_common::msg::ExecuteMsg::UpdateMembers {
remove: vec![VOTER3.into()],
add: vec![member(VOTER2, 9), member(newbie, 29)],
};
@@ -1702,7 +1702,7 @@ mod tests {
// admin changes the group (3 -> 0, 2 -> 9, 0 -> 28) - total = 55, require 28 to pass
let newbie: &str = "newbie";
let update_msg = cw4_group::msg::ExecuteMsg::UpdateMembers {
let update_msg = group_contract_common::msg::ExecuteMsg::UpdateMembers {
remove: vec![VOTER3.into()],
add: vec![member(VOTER2, 9), member(newbie, 29)],
};
+2
View File
@@ -24,6 +24,8 @@ crate-type = ["cdylib", "rlib"]
library = []
[dependencies]
group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
cw-utils = { version = "0.13.4" }
cw2 = { version = "0.13.4" }
cw4 = { version = "0.13.4" }
+1 -1
View File
@@ -13,8 +13,8 @@ use cw_storage_plus::Bound;
use cw_utils::maybe_addr;
use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state::{ADMIN, HOOKS, MEMBERS, TOTAL};
use group_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
// version info for migration info
const CONTRACT_NAME: &str = "crates.io:cw4-group";
+1 -1
View File
@@ -5,7 +5,7 @@ use std::ops::Deref;
use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg};
use cw4::{Cw4Contract, Member};
use crate::msg::ExecuteMsg;
use group_contract_common::msg::ExecuteMsg;
/// Cw4GroupContract is a wrapper around Cw4Contract that provides a lot of helpers
/// for working with cw4-group contracts.
-1
View File
@@ -1,7 +1,6 @@
pub mod contract;
pub mod error;
pub mod helpers;
pub mod msg;
pub mod state;
pub use crate::error::ContractError;
+4 -1
View File
@@ -13,9 +13,12 @@ MIXNET_CONTRACT_ADDRESS=n1xsml6tm6pnx8nsuz3a54cznh55g6nuwu9ug9t92ucjkq5ewp5w9syx
VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1lp5zex6685kd22agzskhqsylpnssxnweyuvsz4edr4p4ta92qf3q3dhmsx
GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
MULTISIG_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
COCONUT_DKG_CONTRACT_ADDRESS=n15f5e6ffz33khfzaadaug9axjt5r38xfe84v9jvlhfs0y7mv9sxvs2xt0u6
COCONUT_DKG_CONTRACT_ADDRESS=n1vwtgazgpancsfel04y7syc95ausmat47cjtldqzkdmx6phyrwx2qvkv32p
REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
NYXD="http://127.0.0.1:26657"
NYM_API="http://127.0.0.1:8000"
DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600"
+1
View File
@@ -13,6 +13,7 @@ MIXNET_CONTRACT_ADDRESS=n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_DKG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
+1
View File
@@ -13,6 +13,7 @@ MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjy
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n12ckdkm3q7eytefs7rwu4ue3t9hxgvl9v08jddmtwgct2ve0pv50q0t8dlt
GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
MULTISIG_CONTRACT_ADDRESS=n14krxe8ukzagwhvec0rmteexu62w8k9kp9sra9ww6em2hnmzcukqsa0utc8
COCONUT_DKG_CONTRACT_ADDRESS=n1rl5n6cxuz2hdy3f7d9hsnw8zn0zwwwr0r4dxfz7tktgpgkcnz9zshmvksc
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
+1
View File
@@ -13,6 +13,7 @@ MIXNET_CONTRACT_ADDRESS=n1rjzps6qrmdqmf0xz4cn4x4rcmqeqzq6hnzqg4wcvd0r2lyasdq5sep
VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw
GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs
COCONUT_DKG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs
REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq
-21
View File
@@ -44,25 +44,4 @@ pub(crate) enum GatewayError {
expected_prefix: String,
actual_prefix: String,
},
#[cfg(feature = "coconut")]
#[error("could not obtain all coconut coconut verifiers details: {source}")]
CoconutVerifiersQueryFailure {
#[source]
source: ValidatorClientError,
},
#[cfg(feature = "coconut")]
#[error("failed to aggregate coconut verification keys: {source}")]
CoconutVerificationKeyAggregationFailure {
#[source]
source: credentials::error::Error,
},
#[cfg(feature = "coconut")]
#[error("failed to create coconut verifier: {source}")]
CoconutVerifierCreationFailure {
#[source]
source: crate::node::client_handling::websocket::connection_handler::RequestHandlingError,
},
}
@@ -67,6 +67,10 @@ pub(crate) enum RequestHandlingError {
#[cfg(feature = "coconut")]
#[error("Coconut interface error - {0}")]
CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError),
#[cfg(feature = "coconut")]
#[error("Credential error - {0}")]
CredentialError(#[from] credentials::error::Error),
}
impl RequestHandlingError {
@@ -208,12 +212,28 @@ where
iv,
)?;
if !credential.verify(
self.inner
.coconut_verifier
.as_ref()
.aggregated_verification_key(),
) {
// Get the latest coconut signers and their VK
let credential_api_clients = self
.inner
.coconut_verifier
.all_coconut_api_clients(*credential.epoch_id())
.await?;
let current_api_clients = self
.inner
.coconut_verifier
.all_current_coconut_api_clients()
.await?;
if credential_api_clients.is_empty() || current_api_clients.is_empty() {
return Err(RequestHandlingError::NotEnoughNymAPIs {
received: 0,
needed: 1,
});
}
let aggregated_verification_key =
credentials::obtain_aggregate_verification_key(&credential_api_clients).await?;
if !credential.verify(&aggregated_verification_key) {
return Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("credential failed to verify on gateway"),
));
@@ -221,7 +241,7 @@ where
self.inner
.coconut_verifier
.release_funds(&credential)
.release_funds(current_api_clients, &credential)
.await?;
let bandwidth = Bandwidth::from(credential);
@@ -1,9 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_interface::{Credential, VerificationKey};
use coconut_interface::Credential;
use log::*;
use std::time::{Duration, SystemTime};
use validator_client::nyxd::traits::DkgQueryClient;
use validator_client::{
nyxd::{
cosmwasm_client::logs::{find_attribute, BANDWIDTH_PROPOSAL_ID},
@@ -19,24 +20,12 @@ const ONE_HOUR_SEC: u64 = 3600;
const MAX_FEEGRANT_UNYM: u128 = 10000;
pub(crate) struct CoconutVerifier {
nym_api_clients: Vec<CoconutApiClient>,
nyxd_client: Client<SigningNyxdClient>,
mix_denom_base: String,
aggregated_verification_key: VerificationKey,
}
impl CoconutVerifier {
pub fn new(
api_clients: Vec<CoconutApiClient>,
nyxd_client: Client<SigningNyxdClient>,
aggregated_verification_key: VerificationKey,
) -> Result<Self, RequestHandlingError> {
if api_clients.is_empty() {
return Err(RequestHandlingError::NotEnoughNymAPIs {
received: 0,
needed: 1,
});
}
pub fn new(nyxd_client: Client<SigningNyxdClient>) -> Self {
let mix_denom_base = nyxd_client
.nyxd
.current_chain_details()
@@ -44,19 +33,31 @@ impl CoconutVerifier {
.base
.clone();
Ok(CoconutVerifier {
nym_api_clients: api_clients,
CoconutVerifier {
nyxd_client,
mix_denom_base,
aggregated_verification_key,
})
}
}
pub fn aggregated_verification_key(&self) -> &VerificationKey {
&self.aggregated_verification_key
pub async fn all_current_coconut_api_clients(
&self,
) -> Result<Vec<CoconutApiClient>, RequestHandlingError> {
let epoch_id = self.nyxd_client.nyxd.get_current_epoch().await?.epoch_id;
self.all_coconut_api_clients(epoch_id).await
}
pub async fn release_funds(&self, credential: &Credential) -> Result<(), RequestHandlingError> {
pub async fn all_coconut_api_clients(
&self,
epoch_id: u64,
) -> Result<Vec<CoconutApiClient>, RequestHandlingError> {
Ok(CoconutApiClient::all_coconut_api_clients(&self.nyxd_client, epoch_id).await?)
}
pub async fn release_funds(
&self,
api_clients: Vec<CoconutApiClient>,
credential: &Credential,
) -> Result<(), RequestHandlingError> {
// Use a custom multiplier for revoke, as the default one (1.3)
// isn't enough
let revoke_fee = Some(Fee::Auto(Some(1.5)));
@@ -96,7 +97,7 @@ impl CoconutVerifier {
proposal_id,
self.nyxd_client.nyxd.address().clone(),
);
for client in self.nym_api_clients.iter() {
for client in api_clients {
self.nyxd_client
.nyxd
.grant_allowance(
@@ -14,9 +14,6 @@ use tokio_tungstenite::WebSocketStream;
pub(crate) use self::authenticated::AuthenticatedHandler;
pub(crate) use self::fresh::FreshHandler;
#[cfg(feature = "coconut")]
pub(crate) use self::authenticated::RequestHandlingError;
mod authenticated;
#[cfg(feature = "coconut")]
pub(crate) mod coconut;
+2 -13
View File
@@ -28,11 +28,9 @@ use task::{TaskClient, TaskManager};
#[cfg(feature = "coconut")]
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
#[cfg(feature = "coconut")]
use credentials::coconut::utils::obtain_aggregate_verification_key;
#[cfg(feature = "coconut")]
use network_defaults::NymNetworkDetails;
#[cfg(feature = "coconut")]
use validator_client::{Client, CoconutApiClient};
use validator_client::Client;
pub(crate) mod client_handling;
pub(crate) mod mixnet_handling;
@@ -321,16 +319,7 @@ where
#[cfg(feature = "coconut")]
let coconut_verifier = {
let nyxd_client = self.random_nyxd_client();
let api_clients = CoconutApiClient::all_coconut_api_clients(&nyxd_client)
.await
.map_err(|source| GatewayError::CoconutVerifiersQueryFailure { source })?;
let validators_verification_key = obtain_aggregate_verification_key(&api_clients)
.await
.map_err(
|source| GatewayError::CoconutVerificationKeyAggregationFailure { source },
)?;
CoconutVerifier::new(api_clients, nyxd_client, validators_verification_key)
.map_err(|source| GatewayError::CoconutVerifierCreationFailure { source })?
CoconutVerifier::new(nyxd_client)
};
let mix_forwarding_channel = self.start_packet_forwarder(shutdown.subscribe());
+2
View File
@@ -76,6 +76,7 @@ credentials = { path = "../common/credentials", optional = true }
crypto = { path = "../common/crypto" }
logging = { path = "../common/logging"}
cw3 = { version = "0.13.4", optional = true }
cw4 = { version = "0.13.4", optional = true }
dkg = { path = "../common/crypto/dkg", optional = true }
gateway-client = { path = "../common/client-libs/gateway-client" }
inclusion-probability = { path = "../common/inclusion-probability" }
@@ -99,6 +100,7 @@ coconut = [
"coconut-interface",
"credentials",
"cw3",
"cw4",
"gateway-client/coconut",
"credentials/coconut",
"nym-api-requests/coconut",
+4 -2
View File
@@ -4,10 +4,11 @@
use crate::coconut::error::Result;
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId};
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use contracts_common::dealings::ContractSafeBytes;
use cw3::ProposalResponse;
use cw4::MemberResponse;
use dkg::Threshold;
use validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
use validator_client::nyxd::{AccountId, Fee, TxResponse};
@@ -23,11 +24,12 @@ pub trait Client {
blinded_serial_number: String,
) -> Result<SpendCredentialResponse>;
async fn get_current_epoch(&self) -> Result<Epoch>;
async fn group_member(&self, addr: String) -> Result<MemberResponse>;
async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>>;
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse>;
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>>;
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>>;
async fn get_verification_key_shares(&self) -> Result<Vec<ContractVKShare>>;
async fn get_verification_key_shares(&self, epoch_id: EpochId) -> Result<Vec<ContractVKShare>>;
async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option<Fee>)
-> Result<()>;
async fn execute_proposal(&self, proposal_id: u64) -> Result<()>;
+5 -3
View File
@@ -3,13 +3,14 @@
use crate::coconut::error::Result;
use crate::nyxd;
use coconut_dkg_common::types::EpochId;
use coconut_interface::VerificationKey;
use credentials::coconut::utils::obtain_aggregate_verification_key;
use validator_client::CoconutApiClient;
#[async_trait]
pub trait APICommunicationChannel {
async fn aggregated_verification_key(&self) -> Result<VerificationKey>;
async fn aggregated_verification_key(&self, epoch_id: EpochId) -> Result<VerificationKey>;
}
pub(crate) struct QueryCommunicationChannel {
@@ -24,9 +25,10 @@ impl QueryCommunicationChannel {
#[async_trait]
impl APICommunicationChannel for QueryCommunicationChannel {
async fn aggregated_verification_key(&self) -> Result<VerificationKey> {
async fn aggregated_verification_key(&self, epoch_id: EpochId) -> Result<VerificationKey> {
let client = self.nyxd_client.0.read().await;
let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client).await?;
let coconut_api_clients =
CoconutApiClient::all_coconut_api_clients(&client, epoch_id).await?;
let vk = obtain_aggregate_verification_key(&coconut_api_clients).await?;
Ok(vk)
}
+11 -3
View File
@@ -4,10 +4,11 @@
use crate::coconut::client::Client;
use crate::coconut::error::CoconutError;
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, NodeIndex};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId, NodeIndex};
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use contracts_common::dealings::ContractSafeBytes;
use cw3::ProposalResponse;
use cw4::MemberResponse;
use dkg::Threshold;
use validator_client::nyxd::cosmwasm_client::logs::{find_attribute, NODE_INDEX};
use validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
@@ -31,7 +32,7 @@ impl DkgClient {
}
}
pub(crate) async fn _get_address(&self) -> AccountId {
pub(crate) async fn get_address(&self) -> AccountId {
self.inner.address().await
}
@@ -46,6 +47,12 @@ impl DkgClient {
ret
}
pub(crate) async fn group_member(&self) -> Result<MemberResponse, CoconutError> {
self.inner
.group_member(self.get_address().await.to_string())
.await
}
pub(crate) async fn get_current_epoch_threshold(
&self,
) -> Result<Option<Threshold>, CoconutError> {
@@ -78,8 +85,9 @@ impl DkgClient {
pub(crate) async fn get_verification_key_shares(
&self,
epoch_id: EpochId,
) -> Result<Vec<ContractVKShare>, CoconutError> {
self.inner.get_verification_key_shares().await
self.inner.get_verification_key_shares(epoch_id).await
}
pub(crate) async fn list_proposals(&self) -> Result<Vec<ProposalResponse>, CoconutError> {
+17 -3
View File
@@ -61,7 +61,7 @@ impl<R: RngCore + Clone> DkgController<R> {
config.secret_key_path(),
config.verification_key_path(),
)) {
coconut_keypair.set(coconut_keypair_value).await;
coconut_keypair.set(Some(coconut_keypair_value)).await;
}
let persistent_state =
PersistentState::load_from_file(config.persistent_state_path()).unwrap_or_default();
@@ -86,8 +86,19 @@ impl<R: RngCore + Clone> DkgController<R> {
match self.dkg_client.get_current_epoch().await {
Err(err) => warn!("Could not get current epoch state {err}"),
Ok(epoch) => {
if self
.dkg_client
.group_member()
.await
.map(|resp| resp.weight.is_none())
.unwrap_or(true)
{
debug!("Not a member of the group, DKG won't be run");
return;
}
if let Err(err) = self.state.is_consistent(epoch.state).await {
error!("Epoch state is corrupted - {err}, the process should be terminated");
return;
}
let ret = match epoch.state {
EpochState::PublicKeySubmission => {
@@ -115,7 +126,10 @@ impl<R: RngCore + Clone> DkgController<R> {
verification_key_finalization(&self.dkg_client, &mut self.state).await
}
// Just wait, in case we need to redo dkg at some point
EpochState::InProgress => Ok(()),
EpochState::InProgress => {
self.state.set_was_in_progress();
Ok(())
}
};
if let Err(err) = ret {
warn!("Could not handle this iteration for the epoch state: {err}");
@@ -132,7 +146,7 @@ impl<R: RngCore + Clone> DkgController<R> {
{
if current_timestamp.as_secs() >= epoch.finish_timestamp.seconds() {
// We try advancing the epoch state, on a best-effort basis
info!("Trying to advance the epoch");
info!("DKG: Trying to advance the epoch");
self.dkg_client.advance_epoch_state().await.ok();
}
}
+13 -5
View File
@@ -4,23 +4,31 @@
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::state::State;
use crate::coconut::error::CoconutError;
use coconut_dkg_common::dealer::DealerType;
pub(crate) async fn public_key_submission(
dkg_client: &DkgClient,
state: &mut State,
) -> Result<(), CoconutError> {
if state.was_in_progress() {
state.reset_persistent().await;
}
if state.node_index().is_some() {
return Ok(());
}
let bte_key = bs58::encode(&state.dkg_keypair().public_key().to_bytes()).into_string();
let index = if let Some(details) = dkg_client
.get_self_registered_dealer_details()
.await?
.details
{
let dealer_details = dkg_client.get_self_registered_dealer_details().await?;
let index = if let Some(details) = dealer_details.details {
if dealer_details.dealer_type == DealerType::Past {
// If it was a dealer in a previous epoch, re-register it for this epoch
dkg_client
.register_dealer(bte_key, state.announce_address().to_string())
.await?;
}
details.assigned_index
} else {
// First time registration
dkg_client
.register_dealer(bte_key, state.announce_address().to_string())
.await?
+26 -1
View File
@@ -170,6 +170,7 @@ pub(crate) struct PersistentState {
proposal_id: Option<u64>,
voted_vks: bool,
executed_proposal: bool,
was_in_progress: bool,
}
impl From<&State> for PersistentState {
@@ -183,6 +184,7 @@ impl From<&State> for PersistentState {
proposal_id: s.proposal_id,
voted_vks: s.voted_vks,
executed_proposal: s.executed_proposal,
was_in_progress: s.was_in_progress,
}
}
}
@@ -211,6 +213,7 @@ pub(crate) struct State {
proposal_id: Option<u64>,
voted_vks: bool,
executed_proposal: bool,
was_in_progress: bool,
}
impl State {
@@ -234,9 +237,23 @@ impl State {
proposal_id: persistent_state.proposal_id,
voted_vks: persistent_state.voted_vks,
executed_proposal: persistent_state.executed_proposal,
was_in_progress: persistent_state.was_in_progress,
}
}
pub async fn reset_persistent(&mut self) {
self.coconut_keypair.set(None).await;
self.node_index = Default::default();
self.dealers = Default::default();
self.receiver_index = Default::default();
self.threshold = Default::default();
self.recovered_vks = Default::default();
self.proposal_id = Default::default();
self.voted_vks = Default::default();
self.executed_proposal = Default::default();
self.was_in_progress = Default::default();
}
pub fn persistent_state_path(&self) -> PathBuf {
self.persistent_state_path.clone()
}
@@ -299,12 +316,16 @@ impl State {
self.executed_proposal
}
pub fn was_in_progress(&self) -> bool {
self.was_in_progress
}
pub fn set_recovered_vks(&mut self, recovered_vks: Vec<RecoveredVerificationKeys>) {
self.recovered_vks = recovered_vks;
}
pub async fn set_coconut_keypair(&mut self, coconut_keypair: coconut_interface::KeyPair) {
self.coconut_keypair.set(coconut_keypair).await
self.coconut_keypair.set(Some(coconut_keypair)).await
}
pub fn set_node_index(&mut self, node_index: Option<NodeIndex>) {
@@ -349,6 +370,10 @@ impl State {
self.executed_proposal = true;
}
pub fn set_was_in_progress(&mut self) {
self.was_in_progress = true;
}
#[cfg(test)]
pub fn all_dealers(&self) -> &BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>> {
&self.dealers
+2 -1
View File
@@ -177,7 +177,8 @@ pub(crate) async fn verification_key_validation(
return Ok(());
}
let vk_shares = dkg_client.get_verification_key_shares().await?;
let epoch_id = dkg_client.get_current_epoch().await?.epoch_id;
let vk_shares = dkg_client.get_verification_key_shares(epoch_id).await?;
let proposal_ids = BTreeMap::from_iter(
dkg_client
.list_proposals()
+2 -2
View File
@@ -20,8 +20,8 @@ impl KeyPair {
self.inner.read().await
}
pub async fn set(&self, keypair: coconut_interface::KeyPair) {
pub async fn set(&self, keypair: Option<coconut_interface::KeyPair>) {
let mut w_lock = self.inner.write().await;
*w_lock = Some(keypair);
*w_lock = keypair;
}
}
+8 -3
View File
@@ -9,6 +9,7 @@ use crate::support::storage::NymApiStorage;
use coconut_bandwidth_contract_common::spend_credential::{
funds_from_cosmos_msgs, SpendCredentialStatus,
};
use coconut_dkg_common::types::EpochId;
use coconut_interface::KeyPair as CoconutKeyPair;
use coconut_interface::{
Attribute, BlindSignRequest, BlindedSignature, Parameters, VerificationKey,
@@ -132,8 +133,10 @@ impl State {
}
}
pub async fn verification_key(&self) -> Result<VerificationKey> {
self.comm_channel.aggregated_verification_key().await
pub async fn verification_key(&self, epoch_id: EpochId) -> Result<VerificationKey> {
self.comm_channel
.aggregated_verification_key(epoch_id)
.await
}
}
@@ -284,7 +287,9 @@ pub async fn verify_bandwidth_credential(
status: format!("{:?}", credential_status),
});
}
let verification_key = state.verification_key().await?;
let verification_key = state
.verification_key(*verify_credential_body.credential().epoch_id())
.await?;
let mut vote_yes = verify_credential_body
.credential()
.verify(&verification_key);
+19 -9
View File
@@ -40,11 +40,12 @@ use coconut_dkg_common::dealer::{
ContractDealing, DealerDetails, DealerDetailsResponse, DealerType,
};
use coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, TOTAL_DEALINGS};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId, TOTAL_DEALINGS};
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use contracts_common::dealings::ContractSafeBytes;
use crypto::asymmetric::{encryption, identity};
use cw3::ProposalResponse;
use cw4::MemberResponse;
use dkg::Threshold;
use rand_07::rngs::OsRng;
use rand_07::Rng;
@@ -192,6 +193,10 @@ impl super::client::Client for DummyClient {
Ok(*self.epoch.read().unwrap())
}
async fn group_member(&self, _addr: String) -> Result<MemberResponse> {
todo!()
}
async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>> {
Ok(*self.threshold.read().unwrap())
}
@@ -231,7 +236,10 @@ impl super::client::Client for DummyClient {
.collect())
}
async fn get_verification_key_shares(&self) -> Result<Vec<ContractVKShare>> {
async fn get_verification_key_shares(
&self,
_epoch_id: EpochId,
) -> Result<Vec<ContractVKShare>> {
Ok(self
.verification_share
.read()
@@ -342,6 +350,7 @@ impl super::client::Client for DummyClient {
announce_address: dealer_details.announce_address.clone(),
node_index: dealer_details.assigned_index,
owner: Addr::unchecked(self.validator_address.to_string()),
epoch_id: 0,
verified: false,
},
);
@@ -398,7 +407,7 @@ impl DummyCommunicationChannel {
#[async_trait]
impl super::comm::APICommunicationChannel for DummyCommunicationChannel {
async fn aggregated_verification_key(&self) -> Result<VerificationKey> {
async fn aggregated_verification_key(&self, _epoch_id: EpochId) -> Result<VerificationKey> {
Ok(self.aggregated_verification_key.clone())
}
}
@@ -471,7 +480,7 @@ async fn signed_before() {
.with_tx_db(&tx_db);
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
staged_key_pair.set(Some(key_pair)).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nyxd_client,
@@ -539,7 +548,7 @@ async fn state_functions() {
let storage = NymApiStorage::init(db_dir).await.unwrap();
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
staged_key_pair.set(Some(key_pair)).await;
let state = State::new(
nyxd_client,
TEST_COIN_DENOM.to_string(),
@@ -708,7 +717,7 @@ async fn blind_sign_correct() {
.with_tx_db(&tx_db);
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
staged_key_pair.set(Some(key_pair)).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nyxd_client,
@@ -785,7 +794,7 @@ async fn signature_test() {
DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap());
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
staged_key_pair.set(Some(key_pair)).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nyxd_client,
@@ -872,7 +881,7 @@ async fn verification_of_bandwidth_credential() {
let storage1 = NymApiStorage::init(db_dir).await.unwrap();
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
staged_key_pair.set(Some(key_pair)).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nyxd_client.clone(),
TEST_COIN_DENOM.to_string(),
@@ -885,7 +894,7 @@ async fn verification_of_bandwidth_credential() {
.await
.expect("valid rocket instance");
let credential = Credential::new(4, theta.clone(), voucher_value, voucher_info.to_string());
let credential = Credential::new(4, theta.clone(), voucher_value, voucher_info.to_string(), 0);
let proposal_id = 42;
// The address is not used, so we can use a duplicate
let gateway_cosmos_addr = validator_address.clone();
@@ -1036,6 +1045,7 @@ async fn verification_of_bandwidth_credential() {
theta.clone(),
voucher_value,
String::from("bad voucher info"),
0,
);
let bad_req =
VerifyCredentialBody::new(bad_credential, proposal_id, gateway_cosmos_addr.clone());
+3 -13
View File
@@ -22,6 +22,7 @@ use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use std::sync::Arc;
use task::TaskManager;
use validator_client::nyxd::SigningNyxdClient;
pub(crate) mod chunker;
pub(crate) mod gateways_reader;
@@ -93,25 +94,14 @@ impl<'a> NetworkMonitorBuilder<'a> {
*encryption_keypair.public_key(),
);
#[cfg(feature = "coconut")]
let bandwidth_controller = {
let client = self._nyxd_client.0.read().await;
let coconut_api_clients =
validator_client::CoconutApiClient::all_coconut_api_clients(&client)
.await
.expect("Could not query api clients");
BandwidthController::new(
credential_storage::initialise_storage(self.config.get_credentials_database_path())
.await,
coconut_api_clients,
client.clone(),
)
};
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_credentials_database_path())
.await,
)
.expect("Could not create bandwidth controller");
let packet_sender = new_packet_sender(
self.config,
@@ -188,7 +178,7 @@ fn new_packet_sender(
gateways_status_updater: GatewayClientUpdateSender,
local_identity: Arc<identity::KeyPair>,
max_sending_rate: usize,
bandwidth_controller: BandwidthController<PersistentStorage>,
bandwidth_controller: BandwidthController<SigningNyxdClient, PersistentStorage>,
disabled_credentials_mode: bool,
) -> PacketSender {
PacketSender::new(
@@ -6,18 +6,21 @@ use gateway_client::GatewayClient;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{Mutex, MutexGuard, TryLockError};
use validator_client::nyxd::SigningNyxdClient;
pub(crate) struct GatewayClientHandle(Arc<GatewayClientHandleInner>);
struct GatewayClientHandleInner {
client: Mutex<Option<GatewayClient>>,
client: Mutex<Option<GatewayClient<SigningNyxdClient>>>,
raw_identity: [u8; PUBLIC_KEY_LENGTH],
}
pub(crate) struct UnlockedGatewayClientHandle<'a>(MutexGuard<'a, Option<GatewayClient>>);
pub(crate) struct UnlockedGatewayClientHandle<'a>(
MutexGuard<'a, Option<GatewayClient<SigningNyxdClient>>>,
);
impl GatewayClientHandle {
pub(crate) fn new(gateway_client: GatewayClient) -> Self {
pub(crate) fn new(gateway_client: GatewayClient<SigningNyxdClient>) -> Self {
GatewayClientHandle(Arc::new(GatewayClientHandleInner {
raw_identity: gateway_client.gateway_identity().to_bytes(),
client: Mutex::new(Some(gateway_client)),
@@ -56,11 +59,11 @@ impl GatewayClientHandle {
}
impl<'a> UnlockedGatewayClientHandle<'a> {
pub(crate) fn get_mut_unchecked(&mut self) -> &mut GatewayClient {
pub(crate) fn get_mut_unchecked(&mut self) -> &mut GatewayClient<SigningNyxdClient> {
self.0.as_mut().unwrap()
}
pub(crate) fn inner_mut(&mut self) -> Option<&mut GatewayClient> {
pub(crate) fn inner_mut(&mut self) -> Option<&mut GatewayClient<SigningNyxdClient>> {
self.0.as_mut()
}
@@ -27,6 +27,7 @@ use std::time::Duration;
use task::TaskClient;
use gateway_client::bandwidth::BandwidthController;
use validator_client::nyxd::SigningNyxdClient;
const TIME_CHUNK_SIZE: Duration = Duration::from_millis(50);
@@ -90,7 +91,7 @@ struct FreshGatewayClientData {
gateways_status_updater: GatewayClientUpdateSender,
local_identity: Arc<identity::KeyPair>,
gateway_response_timeout: Duration,
bandwidth_controller: BandwidthController<PersistentStorage>,
bandwidth_controller: BandwidthController<SigningNyxdClient, PersistentStorage>,
disabled_credentials_mode: bool,
}
@@ -148,7 +149,7 @@ impl PacketSender {
gateway_connection_timeout: Duration,
max_concurrent_clients: usize,
max_sending_rate: usize,
bandwidth_controller: BandwidthController<PersistentStorage>,
bandwidth_controller: BandwidthController<SigningNyxdClient, PersistentStorage>,
disabled_credentials_mode: bool,
) -> Self {
PacketSender {
@@ -218,7 +219,7 @@ impl PacketSender {
}
async fn attempt_to_send_packets(
client: &mut GatewayClient,
client: &mut GatewayClient<SigningNyxdClient>,
mut mix_packets: Vec<MixPacket>,
max_sending_rate: usize,
) -> Result<(), GatewayClientError> {
@@ -327,7 +328,7 @@ impl PacketSender {
}
async fn check_remaining_bandwidth(
client: &mut GatewayClient,
client: &mut GatewayClient<SigningNyxdClient>,
) -> Result<(), GatewayClientError> {
if client.remaining_bandwidth() < REMAINING_BANDWIDTH_THRESHOLD {
Err(GatewayClientError::NotEnoughBandwidth(
+1 -1
View File
@@ -99,7 +99,7 @@ pub(crate) struct CliArgs {
/// Flag to indicate whether coconut signer authority is enabled on this API
#[cfg(feature = "coconut")]
#[clap(long, requires = "mnemonic", requires = "announce-address")]
#[clap(long, requires = "mnemonic", requires = "announce_address")]
pub(crate) enable_coconut: Option<bool>,
}
+11 -4
View File
@@ -32,7 +32,7 @@ use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse
#[cfg(feature = "coconut")]
use coconut_dkg_common::{
dealer::{ContractDealing, DealerDetails, DealerDetailsResponse},
types::{EncodedBTEPublicKeyWithProof, Epoch},
types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId},
verification_key::{ContractVKShare, VerificationKeyShare},
};
#[cfg(feature = "coconut")]
@@ -40,11 +40,13 @@ use contracts_common::dealings::ContractSafeBytes;
#[cfg(feature = "coconut")]
use cw3::ProposalResponse;
#[cfg(feature = "coconut")]
use cw4::MemberResponse;
#[cfg(feature = "coconut")]
use validator_client::nyxd::{
cosmwasm_client::types::ExecuteResult,
traits::{
CoconutBandwidthQueryClient, DkgQueryClient, DkgSigningClient, MultisigQueryClient,
MultisigSigningClient,
CoconutBandwidthQueryClient, DkgQueryClient, DkgSigningClient, GroupQueryClient,
MultisigQueryClient, MultisigSigningClient,
},
Fee,
};
@@ -327,6 +329,10 @@ impl crate::coconut::client::Client for Client {
Ok(self.0.read().await.nyxd.get_current_epoch().await?)
}
async fn group_member(&self, addr: String) -> crate::coconut::error::Result<MemberResponse> {
Ok(self.0.read().await.nyxd.member(addr).await?)
}
async fn get_current_epoch_threshold(
&self,
) -> crate::coconut::error::Result<Option<dkg::Threshold>> {
@@ -365,12 +371,13 @@ impl crate::coconut::client::Client for Client {
async fn get_verification_key_shares(
&self,
epoch_id: EpochId,
) -> crate::coconut::error::Result<Vec<ContractVKShare>> {
Ok(self
.0
.read()
.await
.get_all_nyxd_verification_key_shares()
.get_all_nyxd_verification_key_shares(epoch_id)
.await?)
}
+11
View File
@@ -1892,6 +1892,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "group-contract-common"
version = "0.1.0"
dependencies = [
"cw4",
"schemars",
"serde",
]
[[package]]
name = "gtk"
version = "0.15.4"
@@ -5403,9 +5412,11 @@ dependencies = [
"cosmrs",
"cosmwasm-std",
"cw3",
"cw4",
"execute",
"flate2",
"futures",
"group-contract-common",
"itertools",
"log",
"mixnet-contract-common",
@@ -109,6 +109,7 @@ mod sandbox {
"nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r";
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str =
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str =
@@ -145,6 +146,7 @@ mod sandbox {
coconut_bandwidth_contract_address: parse_optional_str(
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS),
},
@@ -170,6 +172,7 @@ mod qa {
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw";
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str =
"n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
@@ -204,6 +207,7 @@ mod qa {
coconut_bandwidth_contract_address: parse_optional_str(
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS),
},
+1
View File
@@ -14,6 +14,7 @@ gateway-requests = { path = "../../../gateway/gateway-requests" }
network-defaults = { path = "../../../common/network-defaults" }
nymsphinx = { path = "../../../common/nymsphinx" }
task = { path = "../../../common/task" }
validator-client = { path = "../../../common/client-libs/validator-client" }
futures = "0.3"
log = { workspace = true }
+2 -1
View File
@@ -21,6 +21,7 @@ use nymsphinx::{
use task::TaskManager;
use futures::StreamExt;
use validator_client::nyxd::SigningNyxdClient;
use super::{connection_state::BuilderState, Config, GatewayKeyMode, Keys, KeysArc, StoragePaths};
use crate::error::{Error, Result};
@@ -224,7 +225,7 @@ impl ClientBuilder {
let reply_storage_backend =
non_wasm_helpers::setup_empty_reply_surb_backend(&self.config.debug_config);
let base_builder = BaseClientBuilder::new(
let base_builder: BaseClientBuilder<_, SigningNyxdClient> = BaseClientBuilder::new(
&gateway_endpoint_config,
&self.config.debug_config,
self.key_manager.clone(),