Feature/coconut feature (#805)
* 'Coconut' feature in gateway * Enabled coconut feature in gateway-requests * Native client coconut feature * Ibid for socks5 client * Ibid for wasm client * Coconut feature flag for validator-api * Added coconut feature flag to our CI * build.yml typo * Continue on windows errors * Missing quote * Another typo in build.yml * Reclaiming disk space when building for windows on CI
This commit is contained in:
committed by
GitHub
parent
51dc8c81ed
commit
4e0e081b3e
@@ -5,7 +5,7 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' }}
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.os == 'windows-latest' }}
|
||||
strategy:
|
||||
matrix:
|
||||
rust: [stable, beta, nightly]
|
||||
@@ -16,33 +16,121 @@ jobs:
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all
|
||||
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
# COCONUT stuff
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
# BUILD
|
||||
- name: Build gateway with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --bin nym-gateway --features=coconut
|
||||
|
||||
- name: Build native client with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --bin nym-client --features=coconut
|
||||
|
||||
- name: Build socks5 client with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --bin nym-socks5-client --features=coconut
|
||||
|
||||
- name: Build validator-api with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --bin nym-validator-api --features=coconut
|
||||
|
||||
# TEST
|
||||
- name: Test gateway with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --bin nym-gateway --features=coconut
|
||||
|
||||
- name: Test native client with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --bin nym-client --features=coconut
|
||||
|
||||
- name: Test socks5 client with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --bin nym-socks5-client --features=coconut
|
||||
|
||||
- name: Test validator-api with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --bin nym-validator-api --features=coconut
|
||||
|
||||
# CLIPPY
|
||||
|
||||
- name: Run clippy on gateway with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --bin nym-gateway --features=coconut -- -D warnings
|
||||
|
||||
- name: Run clippy on native client with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --bin nym-client --features=coconut -- -D warnings
|
||||
|
||||
- name: Run clippy on socks5 client with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --bin nym-socks5-client --features=coconut -- -D warnings
|
||||
|
||||
- name: Run clippy on validator-api with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --bin nym-validator-api --features=coconut -- -D warnings
|
||||
@@ -21,6 +21,11 @@ jobs:
|
||||
command: build
|
||||
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --features=coconut
|
||||
|
||||
# for some reason this does not seem to work correctly, leave it for later, building is good enough for now
|
||||
# - uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
|
||||
@@ -31,8 +31,8 @@ tokio-tungstenite = "0.14" # websocket
|
||||
|
||||
## internal
|
||||
client-core = { path = "../client-core" }
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
credentials = { path = "../../common/credentials" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
config = { path = "../../common/config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
@@ -44,5 +44,8 @@ websocket-requests = { path = "websocket-requests" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0" # for the "textsend" example
|
||||
|
||||
@@ -22,9 +22,6 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use coconut_interface::Credential;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::{
|
||||
@@ -38,6 +35,11 @@ use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
pub struct NymClient {
|
||||
@@ -166,7 +168,8 @@ impl NymClient {
|
||||
.start(self.runtime.handle())
|
||||
}
|
||||
|
||||
async fn prepare_credential(&self) -> Credential {
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn prepare_coconut_credential(&self) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(
|
||||
&self.config.get_base().get_validator_api_endpoints(),
|
||||
)
|
||||
@@ -208,7 +211,8 @@ impl NymClient {
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
self.runtime.block_on(async {
|
||||
let coconut_credential = self.prepare_credential().await;
|
||||
#[cfg(feature = "coconut")]
|
||||
let coconut_credential = self.prepare_coconut_credential().await;
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
@@ -218,11 +222,13 @@ impl NymClient {
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
coconut_credential,
|
||||
);
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(coconut_credential),
|
||||
)
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ use crate::commands::override_config;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use coconut_interface::Credential;
|
||||
use config::NymConfig;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
@@ -60,34 +57,15 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
)
|
||||
}
|
||||
|
||||
// this behaviour should definitely be changed, we shouldn't
|
||||
// need to get bandwidth credential for registration
|
||||
async fn prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
|
||||
let bandwidth_credential = credentials::bandwidth::obtain_signature(raw_identity, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(raw_identity, &bandwidth_credential, &verification_key)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
validator_urls: Vec<Url>,
|
||||
) -> Arc<SharedKeys> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let coconut_credential =
|
||||
prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await;
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
gateway.identity_key,
|
||||
our_identity.clone(),
|
||||
coconut_credential,
|
||||
timeout,
|
||||
);
|
||||
gateway_client
|
||||
@@ -210,13 +188,8 @@ pub fn execute(matches: &ArgMatches) {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gate_details.identity_key.to_base58_string());
|
||||
let validator_urls = config.get_base().get_validator_api_endpoints();
|
||||
let shared_keys = register_with_gateway(
|
||||
&gate_details,
|
||||
key_manager.identity_keypair(),
|
||||
validator_urls,
|
||||
)
|
||||
.await;
|
||||
let shared_keys =
|
||||
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
|
||||
(shared_keys, gate_details.clients_address())
|
||||
};
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ url = "2.2"
|
||||
|
||||
# internal
|
||||
client-core = { path = "../client-core" }
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
credentials = { path = "../../common/credentials" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
config = { path = "../../common/config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
@@ -38,3 +38,6 @@ pemstore = { path = "../../common/pemstore" }
|
||||
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
|
||||
@@ -23,9 +23,6 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use coconut_interface::Credential;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::{
|
||||
@@ -37,6 +34,11 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
pub struct NymClient {
|
||||
@@ -154,7 +156,8 @@ impl NymClient {
|
||||
.start(self.runtime.handle())
|
||||
}
|
||||
|
||||
async fn prepare_credential(&self) -> Credential {
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn prepare_coconut_credential(&self) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(
|
||||
&self.config.get_base().get_validator_api_endpoints(),
|
||||
)
|
||||
@@ -196,7 +199,8 @@ impl NymClient {
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
self.runtime.block_on(async {
|
||||
let coconut_credential = self.prepare_credential().await;
|
||||
#[cfg(feature = "coconut")]
|
||||
let coconut_credential = self.prepare_coconut_credential().await;
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
@@ -206,11 +210,13 @@ impl NymClient {
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
coconut_credential,
|
||||
);
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(coconut_credential),
|
||||
)
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ use crate::commands::override_config;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use coconut_interface::Credential;
|
||||
use config::NymConfig;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
@@ -60,34 +57,15 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
)
|
||||
}
|
||||
|
||||
// this behaviour should definitely be changed, we shouldn't
|
||||
// need to get bandwidth credential for registration
|
||||
async fn prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
|
||||
let bandwidth_credential = credentials::bandwidth::obtain_signature(raw_identity, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(raw_identity, &bandwidth_credential, &verification_key)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
validator_urls: Vec<Url>,
|
||||
) -> Arc<SharedKeys> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let coconut_credential =
|
||||
prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await;
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
gateway.identity_key,
|
||||
our_identity.clone(),
|
||||
coconut_credential,
|
||||
timeout,
|
||||
);
|
||||
gateway_client
|
||||
@@ -211,13 +189,8 @@ pub fn execute(matches: &ArgMatches) {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gate_details.identity_key.to_base58_string());
|
||||
let validator_urls = config.get_base().get_validator_api_endpoints();
|
||||
let shared_keys = register_with_gateway(
|
||||
&gate_details,
|
||||
key_manager.identity_keypair(),
|
||||
validator_urls,
|
||||
)
|
||||
.await;
|
||||
let shared_keys =
|
||||
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
|
||||
(shared_keys, gate_details.clients_address())
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
offline-test = []
|
||||
coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
@@ -25,8 +26,8 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
url = "2.2"
|
||||
|
||||
# internal
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
credentials = { path = "../../common/credentials" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
topology = { path = "../../common/topology" }
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_interface::Credential;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::GatewayClient;
|
||||
@@ -20,6 +17,11 @@ use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use wasm_utils::{console_log, console_warn};
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod received_processor;
|
||||
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
||||
@@ -101,7 +103,8 @@ impl NymClient {
|
||||
self.self_recipient().to_string()
|
||||
}
|
||||
|
||||
async fn prepare_credential(validators: &[Url], identity_bytes: &[u8]) -> Credential {
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn prepare_coconut_credential(validators: &[Url], identity_bytes: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
@@ -119,17 +122,23 @@ impl NymClient {
|
||||
|
||||
// Right now it's impossible to have async exported functions to take `&self` rather than self
|
||||
pub async fn initial_setup(self) -> Self {
|
||||
let validator_server = self.validator_server.clone();
|
||||
let identity_public_key = self.identity.public_key().clone();
|
||||
#[cfg(feature = "coconut")]
|
||||
let coconut_credential = {
|
||||
let validator_server = self.validator_server.clone();
|
||||
let identity_public_key = self.identity.public_key().clone();
|
||||
Self::prepare_coconut_credential(
|
||||
&vec![validator_server],
|
||||
&identity_public_key.to_bytes(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
let mut client = self.get_and_update_topology().await;
|
||||
let gateway = client.choose_gateway();
|
||||
|
||||
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
|
||||
let coconut_credential =
|
||||
Self::prepare_credential(&vec![validator_server], &identity_public_key.to_bytes())
|
||||
.await;
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway.clients_address(),
|
||||
Arc::clone(&client.identity),
|
||||
@@ -138,11 +147,13 @@ impl NymClient {
|
||||
mixnet_messages_sender,
|
||||
ack_sender,
|
||||
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
coconut_credential,
|
||||
);
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(coconut_credential),
|
||||
)
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
crypto = { path = "../../crypto" }
|
||||
gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||
nymsphinx = { path = "../../nymsphinx" }
|
||||
coconut-interface = { path = "../../coconut-interface" }
|
||||
coconut-interface = { path = "../../coconut-interface", optional = true }
|
||||
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13"
|
||||
@@ -57,3 +57,6 @@ features = ["js"]
|
||||
[dev-dependencies]
|
||||
# for tests
|
||||
#url = "2.1"
|
||||
|
||||
[features]
|
||||
coconut = ["gateway-requests/coconut", "coconut-interface"]
|
||||
@@ -8,7 +8,6 @@ pub use crate::packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
};
|
||||
use crate::socket_state::{PartiallyDelegated, SocketState};
|
||||
use coconut_interface::Credential;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
||||
@@ -31,6 +30,9 @@ use fluvio_wasm_timer as wasm_timer;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
|
||||
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
|
||||
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
||||
|
||||
@@ -53,7 +55,6 @@ pub struct GatewayClient {
|
||||
reconnection_attempts: usize,
|
||||
/// Delay between each subsequent reconnection attempt.
|
||||
reconnection_backoff: Duration,
|
||||
coconut_credential: Credential,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
@@ -67,7 +68,6 @@ impl GatewayClient {
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
response_timeout_duration: Duration,
|
||||
coconut_credential: Credential,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
@@ -82,7 +82,6 @@ impl GatewayClient {
|
||||
should_reconnect_on_failure: true,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
coconut_credential,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +102,6 @@ impl GatewayClient {
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
coconut_credential: Credential,
|
||||
response_timeout_duration: Duration,
|
||||
) -> Self {
|
||||
use futures::channel::mpsc;
|
||||
@@ -127,7 +125,6 @@ impl GatewayClient {
|
||||
should_reconnect_on_failure: false,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
coconut_credential,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +196,14 @@ impl GatewayClient {
|
||||
|
||||
for i in 1..self.reconnection_attempts {
|
||||
info!("attempt {}...", i);
|
||||
if self.authenticate_and_start().await.is_ok() {
|
||||
if self
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
info!("managed to reconnect!");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -218,7 +222,13 @@ impl GatewayClient {
|
||||
|
||||
// final attempt (done separately to be able to return a proper error)
|
||||
info!("attempt {}", self.reconnection_attempts);
|
||||
match self.authenticate_and_start().await {
|
||||
match self
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!("managed to reconnect!");
|
||||
Ok(())
|
||||
@@ -455,7 +465,11 @@ impl GatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> {
|
||||
#[cfg(feature = "coconut")]
|
||||
pub async fn claim_coconut_bandwidth(
|
||||
&mut self,
|
||||
coconut_credential: Credential,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
@@ -466,8 +480,8 @@ impl GatewayClient {
|
||||
let mut rng = OsRng;
|
||||
let iv = IV::new_random(&mut rng);
|
||||
|
||||
let msg = ClientControlRequest::new_enc_bandwidth_credential(
|
||||
&self.coconut_credential,
|
||||
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
|
||||
&coconut_credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
iv,
|
||||
)
|
||||
@@ -527,15 +541,10 @@ impl GatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_ping_message(&mut self) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
// as per RFC6455 section 5.5.2, `Ping frame MAY include "Application data".`
|
||||
// so we don't need to include any here.
|
||||
let msg = Message::Ping(Vec::new());
|
||||
|
||||
async fn send_with_reconnection_on_failure(
|
||||
&mut self,
|
||||
msg: Message,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if let Err(err) = self.send_websocket_message_without_response(msg).await {
|
||||
if err.is_closed_connection() && self.should_reconnect_on_failure {
|
||||
info!("Going to attempt a reconnection");
|
||||
@@ -548,6 +557,17 @@ impl GatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_ping_message(&mut self) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
// as per RFC6455 section 5.5.2, `Ping frame MAY include "Application data".`
|
||||
// so we don't need to include any here.
|
||||
let msg = Message::Ping(Vec::new());
|
||||
self.send_with_reconnection_on_failure(msg).await
|
||||
}
|
||||
|
||||
// TODO: possibly make responses optional
|
||||
pub async fn send_mix_packet(
|
||||
&mut self,
|
||||
@@ -569,17 +589,7 @@ impl GatewayClient {
|
||||
.as_ref()
|
||||
.expect("no shared key present even though we're authenticated!"),
|
||||
);
|
||||
|
||||
if let Err(err) = self.send_websocket_message_without_response(msg).await {
|
||||
if err.is_closed_connection() && self.should_reconnect_on_failure {
|
||||
info!("Going to attempt a reconnection");
|
||||
self.attempt_reconnection().await
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
self.send_with_reconnection_on_failure(msg).await
|
||||
}
|
||||
|
||||
async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
@@ -634,12 +644,21 @@ impl GatewayClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError> {
|
||||
pub async fn authenticate_and_start(
|
||||
&mut self,
|
||||
#[cfg(feature = "coconut")] coconut_credential: Option<Credential>,
|
||||
) -> Result<Arc<SharedKeys>, GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
self.establish_connection().await?;
|
||||
}
|
||||
let shared_key = self.perform_initial_authentication().await?;
|
||||
self.claim_bandwidth().await?;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
{
|
||||
if let Some(coconut_credential) = coconut_credential {
|
||||
self.claim_coconut_bandwidth(coconut_credential).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
|
||||
+5
-2
@@ -29,8 +29,8 @@ url = { version = "2.2", features = [ "serde" ] }
|
||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
|
||||
# internal
|
||||
coconut-interface = { path = "../common/coconut-interface" }
|
||||
credentials = { path = "../common/credentials" }
|
||||
coconut-interface = { path = "../common/coconut-interface" , optional = true}
|
||||
credentials = { path = "../common/credentials" , optional = true}
|
||||
config = { path = "../common/config" }
|
||||
crypto = { path = "../common/crypto" }
|
||||
gateway-requests = { path = "gateway-requests" }
|
||||
@@ -41,6 +41,9 @@ pemstore = { path = "../common/pemstore" }
|
||||
validator-client = { path = "../common/client-libs/validator-client" }
|
||||
version-checker = { path = "../common/version-checker" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut"]
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
|
||||
@@ -23,8 +23,11 @@ bincode = "1.3"
|
||||
crypto = { path = "../../common/crypto" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
credentials = { path = "../../common/credentials"}
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials"]
|
||||
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13.0"
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::authentication::encrypted_address::EncryptedAddressBytes;
|
||||
use crate::iv::IV;
|
||||
use crate::registration::handshake::SharedKeys;
|
||||
use crate::GatewayMacSize;
|
||||
use coconut_interface::Credential;
|
||||
use crypto::generic_array::typenum::Unsigned;
|
||||
use crypto::hmac::recompute_keyed_hmac_and_verify_tag;
|
||||
use crypto::symmetric::stream_cipher;
|
||||
@@ -21,6 +20,9 @@ use std::{
|
||||
};
|
||||
use tungstenite::protocol::Message;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum RegistrationHandshake {
|
||||
@@ -115,7 +117,8 @@ pub enum ClientControlRequest {
|
||||
},
|
||||
#[serde(alias = "handshakePayload")]
|
||||
RegisterHandshakeInitRequest { data: Vec<u8> },
|
||||
BandwidthCredential {
|
||||
#[cfg(feature = "coconut")]
|
||||
CoconutBandwidthCredential {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
@@ -134,7 +137,8 @@ impl ClientControlRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_enc_bandwidth_credential(
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn new_enc_coconut_bandwidth_credential(
|
||||
credential: &Credential,
|
||||
shared_key: &SharedKeys,
|
||||
iv: IV,
|
||||
@@ -144,7 +148,7 @@ impl ClientControlRequest {
|
||||
let enc_credential =
|
||||
shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner()));
|
||||
|
||||
Some(ClientControlRequest::BandwidthCredential {
|
||||
Some(ClientControlRequest::CoconutBandwidthCredential {
|
||||
enc_credential,
|
||||
iv: iv.to_bytes(),
|
||||
})
|
||||
@@ -153,7 +157,8 @@ impl ClientControlRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_enc_bandwidth_credential(
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn try_from_enc_coconut_bandwidth_credential(
|
||||
enc_credential: Vec<u8>,
|
||||
shared_key: &SharedKeys,
|
||||
iv: IV,
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod active_clients;
|
||||
mod bandwidth;
|
||||
pub(crate) mod websocket;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
mod bandwidth;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node::client_handling::bandwidth::Bandwidth;
|
||||
use crate::node::client_handling::websocket::connection_handler::{ClientDetails, FreshHandler};
|
||||
use crate::node::client_handling::websocket::message_receiver::MixMessageReceiver;
|
||||
use crate::node::storage::error::StorageError;
|
||||
use futures::StreamExt;
|
||||
use gateway_requests::iv::{IVConversionError, IV};
|
||||
use gateway_requests::iv::IVConversionError;
|
||||
use gateway_requests::types::{BinaryRequest, ServerResponse};
|
||||
use gateway_requests::{ClientControlRequest, GatewayRequestsError};
|
||||
use log::*;
|
||||
@@ -18,6 +17,11 @@ use thiserror::Error;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use crate::node::client_handling::bandwidth::Bandwidth;
|
||||
#[cfg(feature = "coconut")]
|
||||
use gateway_requests::iv::IV;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum RequestHandlingError {
|
||||
#[error("Internal gateway storage error")]
|
||||
@@ -32,17 +36,20 @@ enum RequestHandlingError {
|
||||
#[error("Provided binary request was malformed - {0}")]
|
||||
InvalidTextRequest(<ClientControlRequest as TryFrom<String>>::Error),
|
||||
|
||||
#[error("Provided bandwidth credential did not verify correctly")]
|
||||
InvalidBandwidthCredential,
|
||||
|
||||
#[error("Provided bandwidth credential did not have expected structure - {0}")]
|
||||
BandwidthCredentialError(#[from] credentials::error::Error),
|
||||
#[error("The received request is not valid in the current context")]
|
||||
IllegalRequest,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
#[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)]
|
||||
UnsupportedBandwidthValue(u64),
|
||||
|
||||
#[error("The received request is not valid in the current context")]
|
||||
IllegalRequest,
|
||||
#[cfg(feature = "coconut")]
|
||||
#[error("Provided bandwidth credential did not verify correctly")]
|
||||
InvalidCoconutBandwidthCredential,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
#[error("Provided coconut bandwidth credential did not have expected structure - {0}")]
|
||||
CoconutBandwidthCredentialError(#[from] credentials::error::Error),
|
||||
}
|
||||
|
||||
impl RequestHandlingError {
|
||||
@@ -110,6 +117,10 @@ where
|
||||
Ok(bandwidth)
|
||||
}
|
||||
|
||||
// note: this is not technically a "coconut" thing, but currently we have no non-coconut
|
||||
// bandwidth handling and hence clippy complains about dead and unreachable code
|
||||
// so whenever we introduce another form of bandwidth claim, this feature flag should get removed
|
||||
#[cfg(feature = "coconut")]
|
||||
/// Increases the amount of available bandwidth of the connected client by the specified value.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -148,6 +159,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
/// Tries to handle the received bandwidth request by checking correctness of the received data
|
||||
/// and if successful, increases client's bandwidth by an appropriate amount.
|
||||
///
|
||||
@@ -155,20 +167,20 @@ where
|
||||
///
|
||||
/// * `enc_credential`: raw encrypted bandwidth credential to verify.
|
||||
/// * `iv`: fresh iv used for the credential.
|
||||
async fn handle_bandwidth(
|
||||
async fn handle_coconut_bandwidth(
|
||||
&mut self,
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
let iv = IV::try_from_bytes(&iv)?;
|
||||
let credential = ClientControlRequest::try_from_enc_bandwidth_credential(
|
||||
let credential = ClientControlRequest::try_from_enc_coconut_bandwidth_credential(
|
||||
enc_credential,
|
||||
&self.client.shared_keys,
|
||||
iv,
|
||||
)?;
|
||||
|
||||
if !credential.verify(&self.inner.aggregated_verification_key) {
|
||||
return Err(RequestHandlingError::InvalidBandwidthCredential);
|
||||
return Err(RequestHandlingError::InvalidCoconutBandwidthCredential);
|
||||
}
|
||||
|
||||
let bandwidth = Bandwidth::try_from(credential)?;
|
||||
@@ -202,10 +214,17 @@ where
|
||||
&self,
|
||||
mix_packet: MixPacket,
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
// currently we have no way for increasing bandwidth hence we shouldn't be performing
|
||||
// any meaningful metering. Once we have another way of claiming bandwidth, this
|
||||
// feature lock should go away
|
||||
#[cfg(feature = "coconut")]
|
||||
// for now let's just use actual size of the sphinx packet. there's a tiny bit of overhead
|
||||
// we're not including (but it's literally like 2 bytes) when the packet is framed
|
||||
let consumed_bandwidth = mix_packet.sphinx_packet().len() as i64;
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let consumed_bandwidth = 0;
|
||||
|
||||
let available_bandwidth = self.get_available_bandwidth().await?;
|
||||
|
||||
if available_bandwidth < consumed_bandwidth {
|
||||
@@ -254,8 +273,9 @@ where
|
||||
match ClientControlRequest::try_from(raw_request) {
|
||||
Err(e) => RequestHandlingError::InvalidTextRequest(e).into_error_message(),
|
||||
Ok(request) => match request {
|
||||
ClientControlRequest::BandwidthCredential { enc_credential, iv } => {
|
||||
match self.handle_bandwidth(enc_credential, iv).await {
|
||||
#[cfg(feature = "coconut")]
|
||||
ClientControlRequest::CoconutBandwidthCredential { enc_credential, iv } => {
|
||||
match self.handle_coconut_bandwidth(enc_credential, iv).await {
|
||||
Ok(response) => response.into(),
|
||||
Err(err) => err.into_error_message(),
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::node::client_handling::websocket::connection_handler::{
|
||||
};
|
||||
use crate::node::storage::error::StorageError;
|
||||
use crate::node::storage::PersistentStorage;
|
||||
use coconut_interface::VerificationKey;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use gateway_requests::authentication::encrypted_address::{
|
||||
@@ -28,6 +27,9 @@ use thiserror::Error;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::VerificationKey;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum InitialAuthenticationError {
|
||||
#[error("Internal gateway storage error")]
|
||||
@@ -67,10 +69,12 @@ pub(crate) struct FreshHandler<R, S> {
|
||||
rng: R,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
pub(crate) active_clients_store: ActiveClientsStore,
|
||||
pub(crate) aggregated_verification_key: VerificationKey,
|
||||
pub(crate) outbound_mix_sender: MixForwardingSender,
|
||||
pub(crate) socket_connection: SocketStream<S>,
|
||||
pub(crate) storage: PersistentStorage,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub(crate) aggregated_verification_key: VerificationKey,
|
||||
}
|
||||
|
||||
impl<R, S> FreshHandler<R, S>
|
||||
@@ -84,9 +88,9 @@ where
|
||||
conn: S,
|
||||
outbound_mix_sender: MixForwardingSender,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
aggregated_verification_key: VerificationKey,
|
||||
storage: PersistentStorage,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
#[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey,
|
||||
) -> Self {
|
||||
FreshHandler {
|
||||
rng,
|
||||
@@ -94,8 +98,9 @@ where
|
||||
outbound_mix_sender,
|
||||
socket_connection: SocketStream::RawTcp(conn),
|
||||
local_identity,
|
||||
aggregated_verification_key,
|
||||
storage,
|
||||
#[cfg(feature = "coconut")]
|
||||
aggregated_verification_key,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,6 +497,11 @@ where
|
||||
ClientControlRequest::RegisterHandshakeInitRequest { data } => {
|
||||
self.handle_register(data).await
|
||||
}
|
||||
|
||||
// note: this is not technically a "coconut" thing, but currently we have no non-coconut
|
||||
// bandwidth handling and hence clippy complains about dead and unreachable code
|
||||
// so whenever we introduce another form of bandwidth claim, this feature flag should get removed
|
||||
#[cfg(feature = "coconut")]
|
||||
// won't accept anything else (like bandwidth) without prior authentication
|
||||
_ => Err(InitialAuthenticationError::InvalidRequest),
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
use crate::node::client_handling::active_clients::ActiveClientsStore;
|
||||
use crate::node::client_handling::websocket::connection_handler::FreshHandler;
|
||||
use crate::node::storage::PersistentStorage;
|
||||
use coconut_interface::VerificationKey;
|
||||
use crypto::asymmetric::identity;
|
||||
use log::*;
|
||||
use mixnet_client::forwarder::MixForwardingSender;
|
||||
@@ -14,9 +13,14 @@ use std::process;
|
||||
use std::sync::Arc;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::VerificationKey;
|
||||
|
||||
pub(crate) struct Listener {
|
||||
address: SocketAddr,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
aggregated_verification_key: VerificationKey,
|
||||
}
|
||||
|
||||
@@ -24,11 +28,12 @@ impl Listener {
|
||||
pub(crate) fn new(
|
||||
address: SocketAddr,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
aggregated_verification_key: VerificationKey,
|
||||
#[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey,
|
||||
) -> Self {
|
||||
Listener {
|
||||
address,
|
||||
local_identity,
|
||||
#[cfg(feature = "coconut")]
|
||||
aggregated_verification_key,
|
||||
}
|
||||
}
|
||||
@@ -61,9 +66,10 @@ impl Listener {
|
||||
socket,
|
||||
outbound_mix_sender.clone(),
|
||||
Arc::clone(&self.local_identity),
|
||||
self.aggregated_verification_key.clone(),
|
||||
storage.clone(),
|
||||
active_clients_store.clone(),
|
||||
#[cfg(feature = "coconut")]
|
||||
self.aggregated_verification_key.clone(),
|
||||
);
|
||||
tokio::spawn(async move { handle.start_handling().await });
|
||||
}
|
||||
|
||||
+11
-4
@@ -6,8 +6,6 @@ use crate::node::client_handling::active_clients::ActiveClientsStore;
|
||||
use crate::node::client_handling::websocket;
|
||||
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
|
||||
use crate::node::storage::PersistentStorage;
|
||||
use coconut_interface::VerificationKey;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use log::*;
|
||||
use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder};
|
||||
@@ -17,6 +15,11 @@ use std::net::SocketAddr;
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::VerificationKey;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
|
||||
pub(crate) mod client_handling;
|
||||
pub(crate) mod mixnet_handling;
|
||||
pub(crate) mod storage;
|
||||
@@ -83,8 +86,8 @@ impl Gateway {
|
||||
fn start_client_websocket_listener(
|
||||
&self,
|
||||
forwarding_channel: MixForwardingSender,
|
||||
verification_key: VerificationKey,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
#[cfg(feature = "coconut")] verification_key: VerificationKey,
|
||||
) {
|
||||
info!("Starting client [web]socket listener...");
|
||||
|
||||
@@ -96,6 +99,7 @@ impl Gateway {
|
||||
websocket::Listener::new(
|
||||
listening_address,
|
||||
Arc::clone(&self.identity),
|
||||
#[cfg(feature = "coconut")]
|
||||
verification_key,
|
||||
)
|
||||
.start(
|
||||
@@ -170,6 +174,7 @@ impl Gateway {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let validators_verification_key =
|
||||
obtain_aggregate_verification_key(&self.config.get_validator_api_endpoints())
|
||||
.await
|
||||
@@ -182,10 +187,12 @@ impl Gateway {
|
||||
mix_forwarding_channel.clone(),
|
||||
active_clients_store.clone(),
|
||||
);
|
||||
|
||||
self.start_client_websocket_listener(
|
||||
mix_forwarding_channel,
|
||||
validators_verification_key,
|
||||
active_clients_store,
|
||||
#[cfg(feature = "coconut")]
|
||||
validators_verification_key,
|
||||
);
|
||||
|
||||
info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!");
|
||||
|
||||
@@ -54,6 +54,10 @@ impl BandwidthManager {
|
||||
.await
|
||||
}
|
||||
|
||||
// note: this is not technically a "coconut" thing, but currently we have no non-coconut
|
||||
// bandwidth handling and hence clippy complains about dead and unreachable code
|
||||
// so whenever we introduce another form of bandwidth claim, this feature flag should get removed
|
||||
#[cfg(feature = "coconut")]
|
||||
/// Increases available bandwidth of the particular client by the specified amount.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@@ -211,6 +211,10 @@ impl PersistentStorage {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// note: this is not technically a "coconut" thing, but currently we have no non-coconut
|
||||
// bandwidth handling and hence clippy complains about dead and unreachable code
|
||||
// so whenever we introduce another form of bandwidth claim, this feature flag should get removed
|
||||
#[cfg(feature = "coconut")]
|
||||
/// Increases available bandwidth of the particular client by the specified amount.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@@ -48,9 +48,11 @@ nymsphinx = { path="../common/nymsphinx" }
|
||||
topology = { path="../common/topology" }
|
||||
validator-client = { path="../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
version-checker = { path="../common/version-checker" }
|
||||
coconut-interface = { path = "../common/coconut-interface" }
|
||||
credentials = { path = "../common/credentials" }
|
||||
coconut-interface = { path = "../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../common/credentials", optional = true }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::template::config_template;
|
||||
use coconut_interface::{Base58, KeyPair};
|
||||
use config::defaults::{
|
||||
default_api_endpoints, DEFAULT_EPOCH_LENGTH, DEFAULT_FIRST_EPOCH_START,
|
||||
DEFAULT_MIXNET_CONTRACT_ADDRESS,
|
||||
@@ -14,6 +13,9 @@ use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{Base58, KeyPair};
|
||||
|
||||
mod template;
|
||||
|
||||
const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657";
|
||||
@@ -30,7 +32,6 @@ const DEFAULT_CACHE_INTERVAL: Duration = Duration::from_secs(60);
|
||||
const DEFAULT_MONITOR_THRESHOLD: u8 = 60;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
base: Base,
|
||||
@@ -74,7 +75,7 @@ impl NymConfig for Config {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
#[serde(default)]
|
||||
pub struct Base {
|
||||
local_validator: Url,
|
||||
|
||||
@@ -82,6 +83,7 @@ pub struct Base {
|
||||
mixnet_contract_address: String,
|
||||
|
||||
// Avoid breaking derives for now
|
||||
#[cfg(feature = "coconut")]
|
||||
keypair_bs58: String,
|
||||
}
|
||||
|
||||
@@ -92,13 +94,14 @@ impl Default for Base {
|
||||
.parse()
|
||||
.expect("default local validator is malformed!"),
|
||||
mixnet_contract_address: DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(),
|
||||
#[cfg(feature = "coconut")]
|
||||
keypair_bs58: String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkMonitor {
|
||||
/// Specifies whether network monitoring service is enabled in this process.
|
||||
enabled: bool,
|
||||
@@ -179,7 +182,7 @@ impl Default for NetworkMonitor {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
#[serde(default)]
|
||||
pub struct NodeStatusAPI {
|
||||
/// Path to the database file containing uptime statuses for all mixnodes and gateways.
|
||||
database_path: PathBuf,
|
||||
@@ -200,7 +203,7 @@ impl Default for NodeStatusAPI {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
#[serde(default)]
|
||||
pub struct TopologyCacher {
|
||||
#[serde(with = "humantime_serde")]
|
||||
caching_interval: Duration,
|
||||
@@ -215,7 +218,7 @@ impl Default for TopologyCacher {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
#[serde(default)]
|
||||
pub struct Rewarding {
|
||||
/// Specifies whether rewarding service is enabled in this process.
|
||||
enabled: bool,
|
||||
@@ -254,6 +257,7 @@ impl Config {
|
||||
Config::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn keypair(&self) -> KeyPair {
|
||||
KeyPair::try_from_bs58(self.base.keypair_bs58.clone()).unwrap()
|
||||
}
|
||||
@@ -298,6 +302,7 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn with_keypair<S: Into<String>>(mut self, keypair_bs58: S) -> Self {
|
||||
self.base.keypair_bs58 = keypair_bs58.into();
|
||||
self
|
||||
@@ -391,6 +396,8 @@ impl Config {
|
||||
self.node_status_api.database_path.clone()
|
||||
}
|
||||
|
||||
// fix dead code warnings as this method is only ever used with coconut feature
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn get_all_validator_api_endpoints(&self) -> Vec<Url> {
|
||||
self.network_monitor.all_validator_apis.clone()
|
||||
}
|
||||
|
||||
+30
-18
@@ -16,7 +16,6 @@ use ::config::NymConfig;
|
||||
use anyhow::Result;
|
||||
use cache::ValidatorCache;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use coconut::InternalSignRequest;
|
||||
use log::{info, warn};
|
||||
use rocket::fairing::AdHoc;
|
||||
use rocket::http::Method;
|
||||
@@ -31,8 +30,10 @@ use tokio::sync::Notify;
|
||||
use url::Url;
|
||||
use validator_client::nymd::SigningNymdClient;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut::InternalSignRequest;
|
||||
|
||||
pub(crate) mod cache;
|
||||
mod coconut;
|
||||
pub(crate) mod config;
|
||||
mod network_monitor;
|
||||
mod node_status_api;
|
||||
@@ -40,17 +41,22 @@ pub(crate) mod nymd_client;
|
||||
mod rewarding;
|
||||
pub(crate) mod storage;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
mod coconut;
|
||||
|
||||
const MONITORING_ENABLED: &str = "enable-monitor";
|
||||
const REWARDING_ENABLED: &str = "enable-rewarding";
|
||||
const V4_TOPOLOGY_ARG: &str = "v4-topology-filepath";
|
||||
const V6_TOPOLOGY_ARG: &str = "v6-topology-filepath";
|
||||
const API_VALIDATORS_ARG: &str = "api-validators";
|
||||
const DETAILED_REPORT_ARG: &str = "detailed-report";
|
||||
const MIXNET_CONTRACT_ARG: &str = "mixnet-contract";
|
||||
const MNEMONIC_ARG: &str = "mnemonic";
|
||||
const WRITE_CONFIG_ARG: &str = "save-config";
|
||||
const KEYPAIR_ARG: &str = "keypair";
|
||||
const NYMD_VALIDATOR_ARG: &str = "nymd-validator";
|
||||
const API_VALIDATORS_ARG: &str = "api-validators";
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
const KEYPAIR_ARG: &str = "keypair";
|
||||
|
||||
const EPOCH_LENGTH_ARG: &str = "epoch-length";
|
||||
const FIRST_REWARDING_EPOCH_ARG: &str = "first-epoch";
|
||||
@@ -70,7 +76,7 @@ fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
}
|
||||
|
||||
fn parse_args<'a>() -> ArgMatches<'a> {
|
||||
App::new("Nym Validator API")
|
||||
let base_app = App::new("Nym Validator API")
|
||||
.author("Nymtech")
|
||||
.arg(
|
||||
Arg::with_name(MONITORING_ENABLED)
|
||||
@@ -104,12 +110,6 @@ fn parse_args<'a>() -> ArgMatches<'a> {
|
||||
.long(NYMD_VALIDATOR_ARG)
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(API_VALIDATORS_ARG)
|
||||
.help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered")
|
||||
.long(API_VALIDATORS_ARG)
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name(MIXNET_CONTRACT_ARG)
|
||||
.long(MIXNET_CONTRACT_ARG)
|
||||
.help("Address of the validator contract managing the network")
|
||||
@@ -134,10 +134,10 @@ fn parse_args<'a>() -> ArgMatches<'a> {
|
||||
.short("w")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(KEYPAIR_ARG)
|
||||
.help("Path to the secret key file")
|
||||
Arg::with_name(API_VALIDATORS_ARG)
|
||||
.help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered")
|
||||
.long(API_VALIDATORS_ARG)
|
||||
.takes_value(true)
|
||||
.long(KEYPAIR_ARG)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FIRST_REWARDING_EPOCH_ARG)
|
||||
@@ -159,9 +159,17 @@ fn parse_args<'a>() -> ArgMatches<'a> {
|
||||
.takes_value(true)
|
||||
.long(REWARDING_MONITOR_THRESHOLD_ARG)
|
||||
.requires(REWARDING_ENABLED)
|
||||
)
|
||||
);
|
||||
|
||||
.get_matches()
|
||||
#[cfg(feature = "coconut")]
|
||||
let base_app = base_app.arg(
|
||||
Arg::with_name(KEYPAIR_ARG)
|
||||
.help("Path to the secret key file")
|
||||
.takes_value(true)
|
||||
.long(KEYPAIR_ARG),
|
||||
);
|
||||
|
||||
base_app.get_matches()
|
||||
}
|
||||
|
||||
async fn wait_for_interrupt() {
|
||||
@@ -264,6 +272,8 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
|
||||
if matches.is_present(DETAILED_REPORT_ARG) {
|
||||
config = config.with_detailed_network_monitor_report(true)
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
if let Some(keypair_path) = matches.value_of(KEYPAIR_ARG) {
|
||||
let keypair_bs58 = std::fs::read_to_string(keypair_path)
|
||||
.unwrap()
|
||||
@@ -377,8 +387,10 @@ async fn setup_rocket(config: &Config, liftoff_notify: Arc<Notify>) -> Result<Ro
|
||||
let rocket = rocket::build()
|
||||
.attach(setup_cors()?)
|
||||
.attach(setup_liftoff_notify(liftoff_notify))
|
||||
.attach(ValidatorCache::stage())
|
||||
.attach(InternalSignRequest::stage(config.keypair()));
|
||||
.attach(ValidatorCache::stage());
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let rocket = rocket.attach(InternalSignRequest::stage(config.keypair()));
|
||||
|
||||
// see if we should start up network monitor and if so, attach the node status api
|
||||
if config.get_network_monitor_enabled() {
|
||||
|
||||
@@ -15,9 +15,6 @@ use crate::network_monitor::monitor::summary_producer::SummaryProducer;
|
||||
use crate::network_monitor::monitor::Monitor;
|
||||
use crate::network_monitor::tested_network::TestedNetwork;
|
||||
use crate::storage::NodeStatusStorage;
|
||||
use coconut_interface::Credential;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use futures::channel::mpsc;
|
||||
use log::info;
|
||||
@@ -25,6 +22,11 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use std::sync::Arc;
|
||||
use topology::NymTopology;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod chunker;
|
||||
pub(crate) mod gateways_reader;
|
||||
pub(crate) mod monitor;
|
||||
@@ -90,6 +92,7 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
*encryption_keypair.public_key(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_credential =
|
||||
TEMPORARY_obtain_bandwidth_credential(self.config, identity_keypair.public_key()).await;
|
||||
|
||||
@@ -97,8 +100,9 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
self.config,
|
||||
gateway_status_update_sender,
|
||||
Arc::clone(&identity_keypair),
|
||||
bandwidth_credential,
|
||||
self.config.get_gateway_sending_rate(),
|
||||
#[cfg(feature = "coconut")]
|
||||
bandwidth_credential,
|
||||
);
|
||||
|
||||
let received_processor = new_received_processor(
|
||||
@@ -163,6 +167,7 @@ fn new_packet_preparer(
|
||||
|
||||
// SECURITY:
|
||||
// this implies we are re-using the same credential for all gateways all the time (which unfortunately is true!)
|
||||
#[cfg(feature = "coconut")]
|
||||
#[allow(non_snake_case)]
|
||||
async fn TEMPORARY_obtain_bandwidth_credential(
|
||||
config: &Config,
|
||||
@@ -192,17 +197,18 @@ fn new_packet_sender(
|
||||
config: &Config,
|
||||
gateways_status_updater: GatewayClientUpdateSender,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
bandwidth_credential: Credential,
|
||||
max_sending_rate: usize,
|
||||
#[cfg(feature = "coconut")] bandwidth_credential: Credential,
|
||||
) -> PacketSender {
|
||||
PacketSender::new(
|
||||
gateways_status_updater,
|
||||
local_identity,
|
||||
bandwidth_credential,
|
||||
config.get_gateway_response_timeout(),
|
||||
config.get_gateway_connection_timeout(),
|
||||
config.get_max_concurrent_gateway_clients(),
|
||||
max_sending_rate,
|
||||
#[cfg(feature = "coconut")]
|
||||
bandwidth_credential,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::network_monitor::monitor::receiver::{GatewayClientUpdate, GatewayClientUpdateSender};
|
||||
use coconut_interface::Credential;
|
||||
use crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH};
|
||||
use futures::channel::mpsc;
|
||||
use futures::stream::{self, FuturesUnordered, StreamExt};
|
||||
@@ -22,6 +21,9 @@ use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
|
||||
const TIME_CHUNK_SIZE: Duration = Duration::from_millis(50);
|
||||
|
||||
pub(crate) struct GatewayPackets {
|
||||
@@ -71,7 +73,8 @@ struct FreshGatewayClientData {
|
||||
// SECURITY:
|
||||
// since currently we have no double spending protection, just to get things running
|
||||
// we're re-using the same credential for all gateways all the time. THIS IS VERY BAD!!
|
||||
bandwidth_credential: Credential,
|
||||
#[cfg(feature = "coconut")]
|
||||
coconut_bandwidth_credential: Credential,
|
||||
}
|
||||
|
||||
pub(crate) struct PacketSender {
|
||||
@@ -93,11 +96,11 @@ impl PacketSender {
|
||||
pub(crate) fn new(
|
||||
gateways_status_updater: GatewayClientUpdateSender,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
bandwidth_credential: Credential,
|
||||
gateway_response_timeout: Duration,
|
||||
gateway_connection_timeout: Duration,
|
||||
max_concurrent_clients: usize,
|
||||
max_sending_rate: usize,
|
||||
#[cfg(feature = "coconut")] coconut_bandwidth_credential: Credential,
|
||||
) -> Self {
|
||||
PacketSender {
|
||||
active_gateway_clients: HashMap::new(),
|
||||
@@ -105,7 +108,8 @@ impl PacketSender {
|
||||
gateways_status_updater,
|
||||
local_identity,
|
||||
gateway_response_timeout,
|
||||
bandwidth_credential,
|
||||
#[cfg(feature = "coconut")]
|
||||
coconut_bandwidth_credential,
|
||||
}),
|
||||
gateway_connection_timeout,
|
||||
max_concurrent_clients,
|
||||
@@ -137,7 +141,6 @@ impl PacketSender {
|
||||
message_sender,
|
||||
ack_sender,
|
||||
fresh_gateway_client_data.gateway_response_timeout,
|
||||
fresh_gateway_client_data.bandwidth_credential.clone(),
|
||||
),
|
||||
(message_receiver, ack_receiver),
|
||||
)
|
||||
@@ -230,7 +233,14 @@ impl PacketSender {
|
||||
// (an actual bug we experienced)
|
||||
match tokio::time::timeout(
|
||||
gateway_connection_timeout,
|
||||
new_client.authenticate_and_start(),
|
||||
new_client.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(
|
||||
fresh_gateway_client_data
|
||||
.coconut_bandwidth_credential
|
||||
.clone(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user