Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f95e9a7d37 | |||
| 2041b03046 | |||
| 0b6adf59ce | |||
| d95df4b286 | |||
| 4f109169af | |||
| 0cef1abbb2 | |||
| 1871c6b2e3 | |||
| 75ad2a113f | |||
| 1d1496aa49 | |||
| a48e06fe51 | |||
| 614b99a36e | |||
| d8cb6199e0 |
Generated
+12
-10
@@ -907,9 +907,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
|
||||
checksum = "a380b87642204557629c9b72988c47b55fbfe6d474960adba56b22331504956a"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
@@ -920,18 +920,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
|
||||
checksum = "866713b2fe13f23038c7d8824c3059d1f28dd94685fb406d1533c4eeeefeefae"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
|
||||
checksum = "8dbb9939b31441dfa9af3ec9740c8a24d585688401eff1b6b386abb7ad0d10a8"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -3573,7 +3573,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"client-core",
|
||||
@@ -3631,7 +3631,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
dependencies = [
|
||||
"bandwidth-claim-contract",
|
||||
"bip39",
|
||||
@@ -3674,7 +3674,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -3729,7 +3729,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"client-core",
|
||||
@@ -7275,7 +7275,9 @@ dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"getrandom 0.2.3",
|
||||
"mixnet-contract",
|
||||
"rand 0.8.4",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
all: clippy test fmt
|
||||
clippy: clippy-main clippy-contracts clippy-wallet
|
||||
all: clippy-all test fmt
|
||||
happy: clippy-happy test fmt
|
||||
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet
|
||||
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet
|
||||
test: test-main test-contracts test-wallet
|
||||
fmt: fmt-main fmt-contracts fmt-wallet
|
||||
|
||||
clippy-main:
|
||||
clippy-happy-main:
|
||||
cargo clippy
|
||||
|
||||
clippy-contracts:
|
||||
cargo clippy --manifest-path contracts/Cargo.toml
|
||||
clippy-happy-contracts:
|
||||
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
clippy-wallet:
|
||||
clippy-happy-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml
|
||||
|
||||
clippy-all-main:
|
||||
cargo clippy --all-features -- -D warnings
|
||||
|
||||
clippy-all-contracts:
|
||||
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
clippy-all-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
|
||||
test-main:
|
||||
cargo test
|
||||
cargo test --all-features
|
||||
|
||||
test-contracts:
|
||||
cargo test --manifest-path contracts/Cargo.toml
|
||||
cargo test --manifest-path contracts/Cargo.toml --all-features
|
||||
|
||||
test-wallet:
|
||||
cargo test --manifest-path nym-wallet/Cargo.toml
|
||||
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
|
||||
|
||||
fmt-main:
|
||||
cargo fmt --all
|
||||
@@ -29,3 +40,6 @@ fmt-contracts:
|
||||
|
||||
fmt-wallet:
|
||||
cargo fmt --manifest-path nym-wallet/Cargo.toml --all
|
||||
|
||||
wasm:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
|
||||
|
||||
@@ -13,7 +13,6 @@ use nymsphinx::utils::sample_poisson_duration;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time;
|
||||
|
||||
@@ -165,8 +164,8 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
pub fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
self.run().await;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use futures::StreamExt;
|
||||
use gateway_client::GatewayClient;
|
||||
use log::*;
|
||||
use nymsphinx::forwarding::packet::MixPacket;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
|
||||
@@ -72,8 +71,8 @@ impl MixTrafficController {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
pub fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
self.run().await;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
mod acknowledgement_control;
|
||||
@@ -170,10 +169,8 @@ impl RealMessagesController<OsRng> {
|
||||
self.ack_control = Some(ack_control_fut.await.unwrap());
|
||||
}
|
||||
|
||||
// &Handle is only passed for consistency sake with other client modules, but I think
|
||||
// when we get to refactoring, we should apply gateway approach and make it implicit
|
||||
pub fn start(mut self, handle: &Handle) -> JoinHandle<Self> {
|
||||
handle.spawn(async move {
|
||||
pub fn start(mut self) -> JoinHandle<Self> {
|
||||
tokio::spawn(async move {
|
||||
self.run().await;
|
||||
self
|
||||
})
|
||||
|
||||
@@ -15,7 +15,6 @@ use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorith
|
||||
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
|
||||
@@ -291,8 +290,8 @@ impl RequestReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
while let Some(request) = self.query_receiver.next().await {
|
||||
match request {
|
||||
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
|
||||
@@ -322,8 +321,8 @@ impl FragmentedMessageReceiver {
|
||||
mixnet_packet_receiver,
|
||||
}
|
||||
}
|
||||
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
|
||||
self.received_buffer.handle_new_received(new_messages).await;
|
||||
}
|
||||
@@ -355,9 +354,9 @@ impl ReceivedMessagesBufferController {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self, handle: &Handle) {
|
||||
pub fn start(self) {
|
||||
// TODO: should we do anything with JoinHandle(s) returned by start methods?
|
||||
self.fragmented_message_receiver.start(handle);
|
||||
self.request_receiver.start(handle);
|
||||
self.fragmented_message_receiver.start();
|
||||
self.request_receiver.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tokio::task::JoinHandle;
|
||||
use topology::{nym_topology_from_bonds, NymTopology};
|
||||
@@ -304,8 +303,8 @@ impl TopologyRefresher {
|
||||
self.topology_accessor.is_routable().await
|
||||
}
|
||||
|
||||
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
pub fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(self.refresh_rate).await;
|
||||
self.refresh().await;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
@@ -48,6 +48,7 @@ network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
eth = []
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0" # for the "textsend" example
|
||||
|
||||
@@ -32,7 +32,6 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::websocket;
|
||||
@@ -44,11 +43,6 @@ pub struct NymClient {
|
||||
/// key filepaths, etc.
|
||||
config: Config,
|
||||
|
||||
/// Tokio runtime used for futures execution.
|
||||
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
|
||||
// gateway.
|
||||
runtime: Runtime,
|
||||
|
||||
/// KeyManager object containing smart pointers to all relevant keys used by the client.
|
||||
key_manager: KeyManager,
|
||||
|
||||
@@ -68,7 +62,6 @@ impl NymClient {
|
||||
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
|
||||
|
||||
NymClient {
|
||||
runtime: Runtime::new().unwrap(),
|
||||
config,
|
||||
key_manager,
|
||||
input_tx: None,
|
||||
@@ -94,9 +87,6 @@ impl NymClient {
|
||||
mix_tx: BatchMixMessageSender,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
|
||||
// set in the constructor which HAS TO be called within context of a tokio runtime
|
||||
let _guard = self.runtime.enter();
|
||||
|
||||
LoopCoverTrafficStream::new(
|
||||
self.key_manager.ack_key(),
|
||||
@@ -109,7 +99,7 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
)
|
||||
.start(self.runtime.handle());
|
||||
.start();
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
@@ -131,10 +121,6 @@ impl NymClient {
|
||||
);
|
||||
|
||||
info!("Starting real traffic stream...");
|
||||
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
|
||||
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
|
||||
// When refactoring this restriction should definitely be removed.
|
||||
let _guard = self.runtime.enter();
|
||||
|
||||
RealMessagesController::new(
|
||||
controller_config,
|
||||
@@ -144,7 +130,7 @@ impl NymClient {
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
)
|
||||
.start(self.runtime.handle());
|
||||
.start();
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -162,10 +148,10 @@ impl NymClient {
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
)
|
||||
.start(self.runtime.handle())
|
||||
.start()
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
async fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
@@ -182,47 +168,44 @@ impl NymClient {
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
self.runtime.block_on(async {
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_eth_endpoint(),
|
||||
self.config.get_base().get_eth_private_key(),
|
||||
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
|
||||
)
|
||||
.expect("Could not create bandwidth controller");
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_eth_endpoint(),
|
||||
self.config.get_base().get_eth_private_key(),
|
||||
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
|
||||
)
|
||||
.expect("Could not create bandwidth controller");
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
|
||||
if self.config.get_base().get_testnet_mode() {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
}
|
||||
if self.config.get_base().get_testnet_mode() {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
}
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
gateway_client
|
||||
})
|
||||
gateway_client
|
||||
}
|
||||
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// the current global view of topology
|
||||
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
|
||||
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
@@ -233,13 +216,10 @@ impl NymClient {
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
info!("Obtaining initial network topology");
|
||||
self.runtime.block_on(topology_refresher.refresh());
|
||||
topology_refresher.refresh().await;
|
||||
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !self
|
||||
.runtime
|
||||
.block_on(topology_refresher.is_topology_routable())
|
||||
{
|
||||
if !topology_refresher.is_topology_routable().await {
|
||||
panic!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
- check if enough nodes and a gateway are online"
|
||||
@@ -247,7 +227,7 @@ impl NymClient {
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start(self.runtime.handle());
|
||||
topology_refresher.start();
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
@@ -260,7 +240,7 @@ impl NymClient {
|
||||
gateway_client: GatewayClient,
|
||||
) {
|
||||
info!("Starting mix traffic controller...");
|
||||
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
|
||||
MixTrafficController::new(mix_rx, gateway_client).start();
|
||||
}
|
||||
|
||||
fn start_websocket_listener(
|
||||
@@ -273,8 +253,7 @@ impl NymClient {
|
||||
let websocket_handler =
|
||||
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient());
|
||||
|
||||
websocket::Listener::new(self.config.get_listening_port())
|
||||
.start(self.runtime.handle(), websocket_handler);
|
||||
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
@@ -321,9 +300,9 @@ impl NymClient {
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub fn run_forever(&mut self) {
|
||||
self.start();
|
||||
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
|
||||
pub async fn run_forever(&mut self) {
|
||||
self.start().await;
|
||||
if let Err(e) = tokio::signal::ctrl_c().await {
|
||||
error!(
|
||||
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
|
||||
e
|
||||
@@ -335,7 +314,7 @@ impl NymClient {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
pub async fn start(&mut self) {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -367,14 +346,17 @@ impl NymClient {
|
||||
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone());
|
||||
self.start_topology_refresher(shared_topology_accessor.clone())
|
||||
.await;
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
reply_key_storage.clone(),
|
||||
);
|
||||
|
||||
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
|
||||
let gateway_client = self
|
||||
.start_gateway_client(mixnet_messages_sender, ack_sender)
|
||||
.await;
|
||||
|
||||
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
|
||||
self.start_real_traffic_controller(
|
||||
|
||||
@@ -30,7 +30,13 @@ use topology::{filter::VersionFilterable, gateway};
|
||||
use url::Url;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::commands::{override_config, TESTNET_MODE_ARG_NAME};
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{
|
||||
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
TESTNET_MODE_ARG_NAME,
|
||||
};
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
let app = App::new("init")
|
||||
@@ -65,24 +71,29 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.long("fastmode")
|
||||
.hidden(true) // this will prevent this flag from being displayed in `--help`
|
||||
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
|
||||
)
|
||||
);
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement")
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(Arg::with_name("eth_endpoint")
|
||||
.long("eth_endpoint")
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
|
||||
.required(true))
|
||||
.arg(Arg::with_name("eth_private_key")
|
||||
.long("eth_private_key")
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.required(true));
|
||||
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
|
||||
.required(true)
|
||||
);
|
||||
|
||||
app
|
||||
}
|
||||
@@ -208,7 +219,7 @@ fn show_address(config: &Config) {
|
||||
println!("\nThe address of this client is: {}", client_recipient);
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = matches.value_of("id").unwrap(); // required for now
|
||||
@@ -226,7 +237,7 @@ pub fn execute(matches: &ArgMatches) {
|
||||
|
||||
// TODO: ideally that should be the last thing that's being done to config.
|
||||
// However, we are later further overriding it with gateway id
|
||||
config = override_config(config, matches);
|
||||
config = override_config(config, &matches);
|
||||
if matches.is_present("fastmode") {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
@@ -239,26 +250,20 @@ pub fn execute(matches: &ArgMatches) {
|
||||
|
||||
let chosen_gateway_id = matches.value_of("gateway");
|
||||
|
||||
let registration_fut = async {
|
||||
let gate_details = gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gate_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
|
||||
(shared_keys, gate_details.clients_address())
|
||||
};
|
||||
|
||||
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
|
||||
let gateway_details = gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_listener(gateway_listener);
|
||||
.with_gateway_id(gateway_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_listener(gateway_details.clients_address());
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
|
||||
@@ -6,6 +6,16 @@ use clap::ArgMatches;
|
||||
use url::Url;
|
||||
|
||||
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
|
||||
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
|
||||
"0000000000000000000000000000000000000000000000000000000000000001";
|
||||
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
@@ -46,15 +56,23 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
|
||||
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
} else {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
|
||||
}
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
|
||||
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_private_key(eth_private_key);
|
||||
} else {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
}
|
||||
|
||||
if matches.is_present(TESTNET_MODE_ARG_NAME) {
|
||||
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
|
||||
config.get_base_mut().with_testnet_mode(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::client::NymClient;
|
||||
use crate::commands::{override_config, TESTNET_MODE_ARG_NAME};
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use log::*;
|
||||
@@ -38,21 +41,23 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.long("port")
|
||||
.help("Port for the socket (if applicable) to listen on")
|
||||
.takes_value(true)
|
||||
)
|
||||
);
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement")
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(Arg::with_name("eth_endpoint")
|
||||
.long("eth_endpoint")
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("eth_private_key")
|
||||
.long("eth_private_key")
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true));
|
||||
|
||||
app
|
||||
@@ -77,7 +82,7 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let id = matches.value_of("id").unwrap();
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
@@ -88,12 +93,12 @@ pub fn execute(matches: &ArgMatches) {
|
||||
}
|
||||
};
|
||||
|
||||
config = override_config(config, matches);
|
||||
config = override_config(config, &matches);
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever();
|
||||
NymClient::new(config).run_forever().await;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ pub mod client;
|
||||
pub mod commands;
|
||||
pub mod websocket;
|
||||
|
||||
fn main() {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
@@ -22,13 +23,13 @@ fn main() {
|
||||
.subcommand(commands::upgrade::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches);
|
||||
execute(arg_matches).await;
|
||||
}
|
||||
|
||||
fn execute(matches: ArgMatches) {
|
||||
async fn execute(matches: ArgMatches<'static>) {
|
||||
match matches.subcommand() {
|
||||
("init", Some(m)) => commands::init::execute(m),
|
||||
("run", Some(m)) => commands::run::execute(m),
|
||||
("init", Some(m)) => commands::init::execute(m.clone()).await,
|
||||
("run", Some(m)) => commands::run::execute(m.clone()).await,
|
||||
("upgrade", Some(m)) => commands::upgrade::execute(m),
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use super::handler::Handler;
|
||||
use log::*;
|
||||
use std::{net::SocketAddr, process, sync::Arc};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::runtime;
|
||||
use tokio::{sync::Notify, task::JoinHandle};
|
||||
|
||||
enum State {
|
||||
@@ -87,9 +86,9 @@ impl Listener {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, rt_handle: &runtime::Handle, handler: Handler) -> JoinHandle<()> {
|
||||
pub(crate) fn start(mut self, handler: Handler) -> JoinHandle<()> {
|
||||
info!("Running websocket on {:?}", self.address.to_string());
|
||||
|
||||
rt_handle.spawn(async move { self.run(handler).await })
|
||||
tokio::spawn(async move { self.run(handler).await })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
@@ -43,6 +43,7 @@ network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
eth = []
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
@@ -28,7 +28,6 @@ use gateway_client::{
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::socks::{
|
||||
@@ -43,11 +42,6 @@ pub struct NymClient {
|
||||
/// key filepaths, etc.
|
||||
config: Config,
|
||||
|
||||
/// Tokio runtime used for futures execution.
|
||||
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
|
||||
// gateway.
|
||||
runtime: Runtime,
|
||||
|
||||
/// KeyManager object containing smart pointers to all relevant keys used by the client.
|
||||
key_manager: KeyManager,
|
||||
}
|
||||
@@ -58,7 +52,6 @@ impl NymClient {
|
||||
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
|
||||
|
||||
NymClient {
|
||||
runtime: Runtime::new().unwrap(),
|
||||
config,
|
||||
key_manager,
|
||||
}
|
||||
@@ -82,9 +75,6 @@ impl NymClient {
|
||||
mix_tx: BatchMixMessageSender,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
|
||||
// set in the constructor which HAS TO be called within context of a tokio runtime
|
||||
let _guard = self.runtime.enter();
|
||||
|
||||
LoopCoverTrafficStream::new(
|
||||
self.key_manager.ack_key(),
|
||||
@@ -97,7 +87,7 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
)
|
||||
.start(self.runtime.handle());
|
||||
.start();
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
@@ -119,10 +109,6 @@ impl NymClient {
|
||||
);
|
||||
|
||||
info!("Starting real traffic stream...");
|
||||
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
|
||||
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
|
||||
// When refactoring this restriction should definitely be removed.
|
||||
let _guard = self.runtime.enter();
|
||||
|
||||
RealMessagesController::new(
|
||||
controller_config,
|
||||
@@ -132,7 +118,7 @@ impl NymClient {
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
)
|
||||
.start(self.runtime.handle());
|
||||
.start();
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -150,10 +136,10 @@ impl NymClient {
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
)
|
||||
.start(self.runtime.handle())
|
||||
.start()
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
async fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
@@ -170,47 +156,44 @@ impl NymClient {
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
self.runtime.block_on(async {
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_eth_endpoint(),
|
||||
self.config.get_base().get_eth_private_key(),
|
||||
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
|
||||
)
|
||||
.expect("Could not create bandwidth controller");
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_eth_endpoint(),
|
||||
self.config.get_base().get_eth_private_key(),
|
||||
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
|
||||
)
|
||||
.expect("Could not create bandwidth controller");
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
|
||||
if self.config.get_base().get_testnet_mode() {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
}
|
||||
if self.config.get_base().get_testnet_mode() {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
}
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
gateway_client
|
||||
})
|
||||
gateway_client
|
||||
}
|
||||
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// the current global view of topology
|
||||
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
|
||||
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
@@ -221,13 +204,10 @@ impl NymClient {
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
info!("Obtaining initial network topology");
|
||||
self.runtime.block_on(topology_refresher.refresh());
|
||||
topology_refresher.refresh().await;
|
||||
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !self
|
||||
.runtime
|
||||
.block_on(topology_refresher.is_topology_routable())
|
||||
{
|
||||
if !topology_refresher.is_topology_routable().await {
|
||||
panic!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
- check if enough nodes and a gateway are online"
|
||||
@@ -235,7 +215,7 @@ impl NymClient {
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start(self.runtime.handle());
|
||||
topology_refresher.start();
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
@@ -248,7 +228,7 @@ impl NymClient {
|
||||
gateway_client: GatewayClient,
|
||||
) {
|
||||
info!("Starting mix traffic controller...");
|
||||
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
|
||||
MixTrafficController::new(mix_rx, gateway_client).start();
|
||||
}
|
||||
|
||||
fn start_socks5_listener(
|
||||
@@ -267,14 +247,13 @@ impl NymClient {
|
||||
self.config.get_provider_mix_address(),
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
self.runtime
|
||||
.spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
|
||||
tokio::spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub fn run_forever(&mut self) {
|
||||
self.start();
|
||||
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
|
||||
pub async fn run_forever(&mut self) {
|
||||
self.start().await;
|
||||
if let Err(e) = tokio::signal::ctrl_c().await {
|
||||
error!(
|
||||
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
|
||||
e
|
||||
@@ -286,7 +265,7 @@ impl NymClient {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
pub async fn start(&mut self) {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -318,14 +297,17 @@ impl NymClient {
|
||||
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone());
|
||||
self.start_topology_refresher(shared_topology_accessor.clone())
|
||||
.await;
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
reply_key_storage.clone(),
|
||||
);
|
||||
|
||||
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
|
||||
let gateway_client = self
|
||||
.start_gateway_client(mixnet_messages_sender, ack_sender)
|
||||
.await;
|
||||
|
||||
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
|
||||
self.start_real_traffic_controller(
|
||||
|
||||
@@ -28,7 +28,13 @@ use topology::{filter::VersionFilterable, gateway};
|
||||
use url::Url;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::commands::{override_config, TESTNET_MODE_ARG_NAME};
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{
|
||||
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
TESTNET_MODE_ARG_NAME,
|
||||
};
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
let app = App::new("init")
|
||||
@@ -65,24 +71,29 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.long("fastmode")
|
||||
.hidden(true) // this will prevent this flag from being displayed in `--help`
|
||||
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
|
||||
)
|
||||
);
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement")
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(Arg::with_name("eth_endpoint")
|
||||
.long("eth_endpoint")
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
|
||||
.required(true))
|
||||
.arg(Arg::with_name("eth_private_key")
|
||||
.long("eth_private_key")
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.required(true));
|
||||
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
|
||||
.required(true)
|
||||
);
|
||||
|
||||
app
|
||||
}
|
||||
@@ -208,7 +219,7 @@ fn show_address(config: &Config) {
|
||||
println!("\nThe address of this client is: {}", client_recipient);
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = matches.value_of("id").unwrap(); // required for now
|
||||
@@ -227,7 +238,7 @@ pub fn execute(matches: &ArgMatches) {
|
||||
|
||||
// TODO: ideally that should be the last thing that's being done to config.
|
||||
// However, we are later further overriding it with gateway id
|
||||
config = override_config(config, matches);
|
||||
config = override_config(config, &matches);
|
||||
if matches.is_present("fastmode") {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
@@ -240,26 +251,20 @@ pub fn execute(matches: &ArgMatches) {
|
||||
|
||||
let chosen_gateway_id = matches.value_of("gateway");
|
||||
|
||||
let registration_fut = async {
|
||||
let gate_details = gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gate_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
|
||||
(shared_keys, gate_details.clients_address())
|
||||
};
|
||||
|
||||
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
|
||||
let gateway_details = gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_listener(gateway_listener);
|
||||
.with_gateway_id(gateway_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_listener(gateway_details.clients_address());
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
|
||||
@@ -10,6 +10,16 @@ pub(crate) mod run;
|
||||
pub(crate) mod upgrade;
|
||||
|
||||
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
|
||||
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
|
||||
"0000000000000000000000000000000000000000000000000000000000000001";
|
||||
|
||||
fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
raw.split(',')
|
||||
@@ -42,15 +52,23 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
|
||||
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
} else {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
|
||||
}
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
|
||||
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_private_key(eth_private_key);
|
||||
} else {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
}
|
||||
|
||||
if matches.is_present(TESTNET_MODE_ARG_NAME) {
|
||||
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
|
||||
config.get_base_mut().with_testnet_mode(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::client::NymClient;
|
||||
use crate::commands::{override_config, TESTNET_MODE_ARG_NAME};
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use log::*;
|
||||
@@ -44,21 +47,23 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.long("port")
|
||||
.help("Port for the socket to listen on")
|
||||
.takes_value(true)
|
||||
)
|
||||
);
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement")
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(Arg::with_name("eth_endpoint")
|
||||
.long("eth_endpoint")
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("eth_private_key")
|
||||
.long("eth_private_key")
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true));
|
||||
|
||||
app
|
||||
@@ -83,7 +88,7 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let id = matches.value_of("id").unwrap();
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
@@ -94,12 +99,12 @@ pub fn execute(matches: &ArgMatches) {
|
||||
}
|
||||
};
|
||||
|
||||
config = override_config(config, matches);
|
||||
config = override_config(config, &matches);
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever();
|
||||
NymClient::new(config).run_forever().await;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ pub mod client;
|
||||
mod commands;
|
||||
pub mod socks;
|
||||
|
||||
fn main() {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
@@ -22,13 +23,13 @@ fn main() {
|
||||
.subcommand(commands::upgrade::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches);
|
||||
execute(arg_matches).await;
|
||||
}
|
||||
|
||||
fn execute(matches: ArgMatches) {
|
||||
async fn execute(matches: ArgMatches<'static>) {
|
||||
match matches.subcommand() {
|
||||
("init", Some(m)) => commands::init::execute(m),
|
||||
("run", Some(m)) => commands::run::execute(m),
|
||||
("init", Some(m)) => commands::init::execute(m.clone()).await,
|
||||
("run", Some(m)) => commands::run::execute(m.clone()).await,
|
||||
("upgrade", Some(m)) => commands::upgrade::execute(m),
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
NYMD_URL=https://sandbox-validator.nymtech.net
|
||||
VALIDATOR_API=https://sandbox-validator.nymtech.net/api
|
||||
MIXNET_CONTRACT=nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j
|
||||
VESTING_CONTRACT=nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s
|
||||
CURRENCY_PREFIX=nymt
|
||||
CHAIN_ID=nym-sandbox
|
||||
@@ -3,26 +3,20 @@ Nym Validator Client
|
||||
|
||||
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
|
||||
|
||||
Running examples
|
||||
-----------------
|
||||
|
||||
With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client.
|
||||
|
||||
Running tests
|
||||
-------------
|
||||
|
||||
The tests will be separated into three categories: unit, integration and mock.
|
||||
|
||||
Currently the command to run all tests:
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is:
|
||||
The tests require `.env.example` being renamed to `.env`. The variables and their values for these tests are currently pointing to the `nym-sandbox` environment.
|
||||
|
||||
|
||||
```
|
||||
cargo watch -s "cd clients/validator && npm test"
|
||||
```
|
||||
|
||||
It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here.
|
||||
`Tests are still in development` - the test libary is `jest` and the test script will execute currently with: `--coverage --verbosity false`
|
||||
|
||||
Generating Documentation
|
||||
------------------------
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
setupFiles: ["dotenv/config"]
|
||||
};
|
||||
@@ -6,9 +6,7 @@
|
||||
"main": "./dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "ts-mocha tests/**/*.test.ts",
|
||||
"coverage": "nyc npm test",
|
||||
"test": "jest --coverage --verbose false",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"docs": "typedoc --out docs src/index.ts"
|
||||
@@ -21,27 +19,21 @@
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/expect": "^24.3.0",
|
||||
"@types/mocha": "^8.2.1",
|
||||
"@types/jest": "27.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
||||
"@typescript-eslint/parser": "^5.7.0",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-config-airbnb": "^19.0.2",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-root-import": "^1.0.4",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-mocha": "^10.0.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"mocha": "^8.2.1",
|
||||
"moq.ts": "^7.2.0",
|
||||
"nyc": "^15.1.0",
|
||||
"jest": "^27.4.5",
|
||||
"prettier": "^2.5.1",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"ts-jest": "^27.1.2",
|
||||
"typedoc": "^0.20.27",
|
||||
"typescript": "^4.1.3"
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/cosmwasm-stargate": "^0.27.0-rc2",
|
||||
@@ -51,6 +43,7 @@
|
||||
"@cosmjs/stargate": "^0.27.0-rc2",
|
||||
"@cosmjs/tendermint-rpc": "^0.27.0-rc2",
|
||||
"axios": "^0.21.1",
|
||||
"cosmjs-types": "^0.4.0"
|
||||
"cosmjs-types": "^0.4.0",
|
||||
"dotenv": "^10.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ import axios from 'axios';
|
||||
import { GasPrice } from '@cosmjs/stargate';
|
||||
|
||||
export function nymGasPrice(prefix: string): GasPrice {
|
||||
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
|
||||
if (typeof prefix === 'string') {
|
||||
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
|
||||
}
|
||||
else {
|
||||
throw new Error(`${prefix} is not of type string`);
|
||||
}
|
||||
}
|
||||
|
||||
export const downloadWasm = async (url: string): Promise<Uint8Array> => {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import validatorClient from "../../src/index";
|
||||
import { config } from '../test-utils/config';
|
||||
|
||||
let client: validatorClient;
|
||||
let mnemonic: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
mnemonic = validatorClient.randomMnemonic();
|
||||
client = await validatorClient.connect(
|
||||
mnemonic,
|
||||
config.NYMD_URL as string,
|
||||
config.VALIDATOR_API as string,
|
||||
config.CURRENCY_PREFIX as string,
|
||||
config.MIXNET_CONTRACT as string,
|
||||
config.VESTING_CONTRACT as string
|
||||
);
|
||||
});
|
||||
|
||||
//todos
|
||||
//we want to mock the majority of these tests
|
||||
//and keep a few integration tests in place
|
||||
|
||||
describe("connect to the nym validator client and perform integration tests against the current testnet", () => {
|
||||
test("get cached mixnodes", async () => {
|
||||
try {
|
||||
const response = await client.getCachedMixnodes();
|
||||
//expect all mixnodes to have their owner address
|
||||
response.forEach(mixnodeDetails => {
|
||||
expect(mixnodeDetails.owner).toHaveLength(43)
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get balance of address and denom of the network", async () => {
|
||||
try {
|
||||
//provide a users address and get their balance
|
||||
//we expect their balance to be zero, as it's a new account
|
||||
const address = await validatorClient.mnemonicToAddress(mnemonic, config.CURRENCY_PREFIX as string);
|
||||
const response = await client.getBalance(address);
|
||||
expect(response.amount).toStrictEqual("0");
|
||||
expect(response.denom).toBe("unymt");
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get minimium pledge amount for a mixnode", async () => {
|
||||
try {
|
||||
const response = await client.minimumMixnodePledge();
|
||||
expect(response.amount).toBe("100000000");
|
||||
expect(response.denom).toBe(config.CURRENCY_PREFIX as string);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get minimium gateway pledge amount", async () => {
|
||||
try {
|
||||
const response = await client.minimumGatewayPledge();
|
||||
expect(response.amount).toBe("100000000");
|
||||
expect(response.denom).toBe(config.CURRENCY_PREFIX as string);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get current mixnet contract address", () => {
|
||||
try {
|
||||
const response = client.mixnetContract;
|
||||
expect(response).toStrictEqual(config.MIXNET_CONTRACT as string)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get current vesting contract address", () => {
|
||||
try {
|
||||
const response = client.vestingContract;
|
||||
expect(response).toStrictEqual(config.VESTING_CONTRACT as string)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import SigningClient from '../../src/signing-client';
|
||||
import validator from "../../src/index";
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
|
||||
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
|
||||
import { config } from '../test-utils/config';
|
||||
|
||||
let cosmWasmClient: CosmWasmClient;
|
||||
let signingClient: SigningClient;
|
||||
let mnemonic: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
cosmWasmClient = await SigningClient.connect(config.NYMD_URL as string);
|
||||
|
||||
mnemonic = validator.randomMnemonic();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic)
|
||||
signingClient = await SigningClient.connectWithNymSigner(
|
||||
wallet,
|
||||
config.NYMD_URL as string,
|
||||
config.VALIDATOR_API as string,
|
||||
config.CURRENCY_PREFIX as string);
|
||||
});
|
||||
|
||||
describe("alternate between the signing clients of nym and perform basic requests", () => {
|
||||
test("retrieve balance using the cosmwasm client", async () => {
|
||||
try {
|
||||
const randomAddress = await validator.mnemonicToAddress(mnemonic, config.CURRENCY_PREFIX as string);
|
||||
const balance = await cosmWasmClient.getBalance(randomAddress, config.CURRENCY_PREFIX as string);
|
||||
expect(balance.denom).toStrictEqual(config.CURRENCY_PREFIX as string);
|
||||
expect(balance.amount).toStrictEqual("0");
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
test("get the chain id of the network", async () => {
|
||||
try {
|
||||
//pass these values in environment variables in the future
|
||||
const chainId = await cosmWasmClient.getChainId();
|
||||
expect(chainId).toStrictEqual(config.CHAIN_ID as string);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
test("get height then search its block", async () => {
|
||||
try {
|
||||
const height = await cosmWasmClient.getHeight()
|
||||
const block = await cosmWasmClient.getBlock(height);
|
||||
expect(block.header.chainId).toStrictEqual(config.CHAIN_ID as string);
|
||||
expect(block.header.height).toStrictEqual(height);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
test("get current reward pool", async () => {
|
||||
try {
|
||||
//this is due to change due to when rewards get distributed
|
||||
const rewards = await signingClient.getRewardPool(config.MIXNET_CONTRACT as string);
|
||||
expect(rewards).toStrictEqual("250000000000000");
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import ValidatorApiQuerier from '../../src/validator-api-querier';
|
||||
import { config } from '../test-utils/config';
|
||||
import { now, elapsed} from '../test-utils/utils';
|
||||
|
||||
let client: ValidatorApiQuerier;
|
||||
|
||||
beforeEach(() => {
|
||||
client = new ValidatorApiQuerier(config.VALIDATOR_API as string);
|
||||
});
|
||||
|
||||
//todos
|
||||
//we want to mock the majority of these tests
|
||||
//and keep a few integration tests in place
|
||||
|
||||
describe("call out to the validator api and run queries", () => {
|
||||
test.skip("build client and get all mixnodes", async () => {
|
||||
try {
|
||||
//this test was currently ran against a set of prefix data
|
||||
//this will change
|
||||
const ownerAddress = "nymt1ydqkmz0ddpvkd3l0vyf8k5xjrqtcnxxvhlpdsr";
|
||||
let response = await client.getActiveMixnodes();
|
||||
expect(response[0].owner).toStrictEqual(ownerAddress);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get rewarded mixnodes", async () => {
|
||||
try {
|
||||
// we assume that all mixnodes will have their owners address
|
||||
// also active sets will determine rewarded mixnodes
|
||||
let response = await client.getRewardedMixnodes();
|
||||
|
||||
response.forEach(rNode => {
|
||||
expect(rNode.owner.length).toStrictEqual(43);
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get cached gateways and it should be six", async () => {
|
||||
try {
|
||||
//current gateways running in sandbox-testnet 6
|
||||
let response = await client.getCachedGateways();
|
||||
expect(response.length).toStrictEqual(6);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test("get cached mixnodes", async () => {
|
||||
try {
|
||||
const start = now();
|
||||
let response = await client.getCachedMixnodes();
|
||||
response.forEach(mixnode => {
|
||||
expect(mixnode.owner.length).toStrictEqual(43);
|
||||
})
|
||||
console.log(elapsed(start, true));
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export const config = {
|
||||
NYMD_URL: process.env.NYMD_URL,
|
||||
VALIDATOR_API: process.env.VALIDATOR_API,
|
||||
MIXNET_CONTRACT: process.env.MIXNET_CONTRACT,
|
||||
VESTING_CONTRACT: process.env.MIXNET_CONTRACT,
|
||||
USER_MNEMONIC: process.env.MNEMONIC,
|
||||
CURRENCY_PREFIX: process.env.CURRENCY_PREFIX,
|
||||
CHAIN_ID: process.env.CHAIN_ID
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// timer actions
|
||||
// Store current time as `start`
|
||||
export const now = (eventName = null) => {
|
||||
if (eventName) {
|
||||
console.log(`Started ${eventName}..`);
|
||||
}
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
//takes arg of start time
|
||||
export const elapsed = (beginning: number, log = false) => {
|
||||
const duration = new Date().getTime() - beginning;
|
||||
if (log) {
|
||||
console.log(`${duration / 1000}s`);
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
const currency = require('../../src/currency');
|
||||
|
||||
describe("provide unit tests around the the currency module", () => {
|
||||
test.skip("convert to native balance", () => {
|
||||
const decimal = "12.0346";
|
||||
const value = currency.printableBalanceToNative(decimal);
|
||||
expect(value).toStrictEqual("12034600");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
const stargate = require("../../src/stargate-helper");
|
||||
import { config } from '../test-utils/config';
|
||||
|
||||
describe("test the stargate functions within the client", () => {
|
||||
test("test that the gas price is returned correctly", () => {
|
||||
const nymCurrency = config.CURRENCY_PREFIX as string;
|
||||
const getGasPrice = stargate.nymGasPrice(nymCurrency);
|
||||
expect(getGasPrice.denom).toBe(`u${nymCurrency}`);
|
||||
});
|
||||
|
||||
test("provide invalid type returns an error message", () => {
|
||||
//pass invalid type
|
||||
expect(() => {
|
||||
const nymCurrency = 13;
|
||||
stargate.nymGasPrice(nymCurrency);
|
||||
}).toThrow("13 is not of type string");
|
||||
});
|
||||
|
||||
//provide test for downloading wasm
|
||||
//mock this test
|
||||
// test.skip("providing nothing returns", async () => {
|
||||
// //pass invalid type
|
||||
// const downloadWasm = stargate.downloadWasm("http://localhost");
|
||||
// console.log(downloadWasm);
|
||||
// })
|
||||
});
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import validator from "../../src/index";
|
||||
import { config } from '../test-utils/config';
|
||||
|
||||
const NETWORK_DENOM = config.CURRENCY_PREFIX as string;
|
||||
|
||||
describe("perform basic interactions with the validator", () => {
|
||||
test("build client and get all mixnodes", async () => {
|
||||
const mnemonic = validator.randomMnemonic();
|
||||
const mnemonicCount = mnemonic.split(" ").length;
|
||||
expect(mnemonicCount).toStrictEqual(24);
|
||||
});
|
||||
|
||||
test("build client and get all mixnodes", async () => {
|
||||
const buildMnemonic = validator.randomMnemonic();
|
||||
const mnemonic = await validator.mnemonicToAddress(buildMnemonic, NETWORK_DENOM);
|
||||
expect(mnemonic).toHaveLength(43);
|
||||
expect(mnemonic).toContain(NETWORK_DENOM);
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,11 @@
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist"
|
||||
"outDir": "./dist",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
},
|
||||
"typedocOptions": {
|
||||
"entryPoints": [
|
||||
@@ -16,7 +20,11 @@
|
||||
"exclude": [
|
||||
"dist",
|
||||
"examples",
|
||||
"tests",
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"tests",
|
||||
"./tests/*/*.tsx",
|
||||
"./tests/*/*.ts"
|
||||
]
|
||||
}
|
||||
+3643
-2960
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,7 @@ prost = { version = "0.9", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { version = "1.0.0-beta2", optional = true }
|
||||
cosmwasm-std = { version = "1.0.0-beta3", optional = true }
|
||||
ts-rs = {version = "5.1", optional = true}
|
||||
|
||||
[features]
|
||||
|
||||
@@ -64,7 +64,8 @@ pub trait VestingSigningClient {
|
||||
|
||||
async fn create_periodic_vesting_account(
|
||||
&self,
|
||||
address: &str,
|
||||
owner_address: &str,
|
||||
staking_address: Option<String>,
|
||||
start_time: Option<u64>,
|
||||
amount: Coin,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
@@ -271,13 +272,15 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
}
|
||||
async fn create_periodic_vesting_account(
|
||||
&self,
|
||||
address: &str,
|
||||
owner_address: &str,
|
||||
staking_address: Option<String>,
|
||||
start_time: Option<u64>,
|
||||
amount: Coin,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
let fee = self.operation_fee(Operation::CreatePeriodicVestingAccount);
|
||||
let req = VestingExecuteMsg::CreateAccount {
|
||||
address: address.to_string(),
|
||||
owner_address: owner_address.to_string(),
|
||||
staking_address,
|
||||
start_time,
|
||||
};
|
||||
self.client
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0-beta2"
|
||||
cosmwasm-std = "1.0.0-beta3"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
|
||||
Generated
+53
-18
@@ -238,9 +238,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
|
||||
checksum = "a380b87642204557629c9b72988c47b55fbfe6d474960adba56b22331504956a"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
@@ -251,18 +251,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
|
||||
checksum = "866713b2fe13f23038c7d8824c3059d1f28dd94685fb406d1533c4eeeefeefae"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-schema"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe52b19d45fe3f8359db6cc24df44dbe05e5ae32539afc0f5b7f790a21aa6fd0"
|
||||
checksum = "818b928263c09a3269c2bed22494a62107a43ef87900e273af8ad2cb9f7e4440"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde_json",
|
||||
@@ -270,9 +270,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
|
||||
checksum = "8dbb9939b31441dfa9af3ec9740c8a24d585688401eff1b6b386abb7ad0d10a8"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -286,9 +286,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f"
|
||||
checksum = "b4a4e55f0d64fed54cd2202301b8d466af8de044589247dabd77a4222f52f749"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
@@ -326,7 +326,7 @@ dependencies = [
|
||||
"log",
|
||||
"nymsphinx-types",
|
||||
"pemstore",
|
||||
"rand",
|
||||
"rand 0.7.3",
|
||||
"subtle-encoding",
|
||||
"x25519-dalek",
|
||||
]
|
||||
@@ -458,7 +458,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"rand",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"sha2",
|
||||
"zeroize",
|
||||
@@ -592,8 +592,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -856,8 +858,8 @@ dependencies = [
|
||||
"cw-storage-plus",
|
||||
"fixed",
|
||||
"mixnet-contract",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rand 0.7.3",
|
||||
"rand_chacha 0.2.2",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -1065,9 +1067,21 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc",
|
||||
"rand_hc 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.3",
|
||||
"rand_hc 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1080,6 +1094,16 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
@@ -1105,7 +1129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rand 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1117,6 +1141,15 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
||||
dependencies = [
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
@@ -1297,7 +1330,7 @@ dependencies = [
|
||||
"hmac",
|
||||
"lioness",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.7.3",
|
||||
"rand_distr",
|
||||
"sha2",
|
||||
"subtle 2.4.1",
|
||||
@@ -1525,7 +1558,9 @@ dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"getrandom 0.2.3",
|
||||
"mixnet-contract",
|
||||
"rand 0.8.4",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
|
||||
@@ -8,18 +8,14 @@ edition = "2018"
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
|
||||
[dev-dependencies]
|
||||
config = { path = "../../common/config"}
|
||||
|
||||
[dependencies]
|
||||
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
|
||||
|
||||
cosmwasm-std = "1.0.0-beta2"
|
||||
cosmwasm-storage = "1.0.0-beta2"
|
||||
cosmwasm-std = "1.0.0-beta3"
|
||||
cosmwasm-storage = "1.0.0-beta3"
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
+3
-3
@@ -1556,9 +1556,9 @@
|
||||
}
|
||||
},
|
||||
"@openzeppelin/contracts": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.4.0.tgz",
|
||||
"integrity": "sha512-qh+EiHWzfY/9CORr+eRUkeEUP1WiFUcq3974bLHwyYzLBUtK6HPaMkIUHi74S1rDTZ0sNz42DwPc5A4IJvN3rg=="
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.4.1.tgz",
|
||||
"integrity": "sha512-o+pHCf/yMLSlV5MkDQEzEQL402i6SoRnktru+0rdSxVEFZcTzzGhZCAtZjUFyKGazMSv1TilzMg+RbED1N8XHQ=="
|
||||
},
|
||||
"@openzeppelin/test-helpers": {
|
||||
"version": "0.5.15",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"dependencies": {
|
||||
"@nomiclabs/hardhat-truffle5": "^2.0.2",
|
||||
"@nomiclabs/hardhat-web3": "^2.0.0",
|
||||
"@openzeppelin/contracts": "^3.4.0",
|
||||
"@openzeppelin/contracts": "^4.4.1",
|
||||
"@openzeppelin/test-helpers": "^0.5.15",
|
||||
"dotenv": "^10.0.0",
|
||||
"find-config": "^1.0.0"
|
||||
|
||||
@@ -15,17 +15,13 @@ exclude = [
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract = { path = "../../common/mixnet-contract" }
|
||||
config = { path = "../../common/config"}
|
||||
vesting-contract = { path = "../vesting" }
|
||||
|
||||
cosmwasm-std = "1.0.0-beta2"
|
||||
cosmwasm-storage = "1.0.0-beta2"
|
||||
cosmwasm-std = "1.0.0-beta3"
|
||||
cosmwasm-storage = "1.0.0-beta3"
|
||||
cw-storage-plus = "0.10.3"
|
||||
|
||||
bs58 = "0.4.0"
|
||||
@@ -34,7 +30,7 @@ serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = "1.0.0-beta2"
|
||||
cosmwasm-schema = "1.0.0-beta3"
|
||||
fixed = "1.1"
|
||||
rand_chacha = "0.2"
|
||||
rand = "0.7"
|
||||
|
||||
@@ -13,18 +13,15 @@ exclude = [
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract = { path = "../../common/mixnet-contract" }
|
||||
config = { path = "../../common/config" }
|
||||
|
||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
||||
cosmwasm-std = { version = "1.0.0-beta2", features = ["iterator"]}
|
||||
cosmwasm-std = { version = "1.0.0-beta3"}
|
||||
cw-storage-plus = { version = "0.10.3", features = ["iterator"] }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
rand = {version = "0.8.4", features = ["std_rng"]}
|
||||
getrandom = { version = "0.2.3", features = ["js"]}
|
||||
@@ -45,9 +45,17 @@ pub fn execute(
|
||||
try_undelegate_from_mixnode(mix_identity, info, deps)
|
||||
}
|
||||
ExecuteMsg::CreateAccount {
|
||||
address,
|
||||
owner_address,
|
||||
staking_address,
|
||||
start_time,
|
||||
} => try_create_periodic_vesting_account(&address, start_time, info, env, deps),
|
||||
} => try_create_periodic_vesting_account(
|
||||
&owner_address,
|
||||
staking_address,
|
||||
start_time,
|
||||
info,
|
||||
env,
|
||||
deps,
|
||||
),
|
||||
ExecuteMsg::WithdrawVestedCoins { amount } => {
|
||||
try_withdraw_vested_coins(amount, env, info, deps)
|
||||
}
|
||||
@@ -72,9 +80,84 @@ pub fn execute(
|
||||
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
|
||||
try_track_unbond_gateway(&owner, amount, info, deps)
|
||||
}
|
||||
ExecuteMsg::TransferOwnership { to_address } => {
|
||||
try_transfer_ownership(to_address, info, deps)
|
||||
}
|
||||
ExecuteMsg::UpdateStakingAddress { to_address } => {
|
||||
try_update_staking_address(to_address, info, deps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only owner
|
||||
pub fn try_withdraw_vested_coins(
|
||||
amount: Coin,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let address = info.sender.clone();
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
if address != account.owner_address() {
|
||||
return Err(ContractError::NotOwner(account.owner_address().to_string()));
|
||||
}
|
||||
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
|
||||
if amount.amount <= spendable_coins.amount {
|
||||
let new_balance = account
|
||||
.load_balance(deps.storage)?
|
||||
.u128()
|
||||
.saturating_sub(amount.amount.u128());
|
||||
account.save_balance(Uint128::new(new_balance), deps.storage)?;
|
||||
|
||||
let send_tokens = BankMsg::Send {
|
||||
to_address: account.owner_address().as_str().to_string(),
|
||||
amount: vec![amount],
|
||||
};
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "whitdraw")
|
||||
.add_message(send_tokens))
|
||||
} else {
|
||||
Err(ContractError::InsufficientSpendable(
|
||||
account.owner_address().as_str().to_string(),
|
||||
spendable_coins.amount.u128(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_transfer_ownership(
|
||||
to_address: String,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let address = info.sender.clone();
|
||||
let to_address = deps.api.addr_validate(&to_address)?;
|
||||
let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
if address == account.owner_address() {
|
||||
account.transfer_ownership(&to_address, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
} else {
|
||||
Err(ContractError::NotOwner(account.owner_address().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_update_staking_address(
|
||||
to_address: Option<String>,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let address = info.sender.clone();
|
||||
let to_address = to_address.and_then(|x| deps.api.addr_validate(&x).ok());
|
||||
let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
if address == account.owner_address() {
|
||||
account.update_staking_address(to_address, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
} else {
|
||||
Err(ContractError::NotOwner(account.owner_address().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
// Owner or staking
|
||||
pub fn try_bond_gateway(
|
||||
gateway: Gateway,
|
||||
owner_signature: String,
|
||||
@@ -137,38 +220,6 @@ pub fn try_track_unbond_mixnode(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_withdraw_vested_coins(
|
||||
amount: Coin,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let address = info.sender.clone();
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
|
||||
if amount.amount <= spendable_coins.amount {
|
||||
let new_balance = account
|
||||
.load_balance(deps.storage)?
|
||||
.u128()
|
||||
.saturating_sub(amount.amount.u128());
|
||||
account.save_balance(Uint128::new(new_balance), deps.storage)?;
|
||||
|
||||
let send_tokens = BankMsg::Send {
|
||||
to_address: address.as_str().to_string(),
|
||||
amount: vec![amount],
|
||||
};
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "whitdraw")
|
||||
.add_message(send_tokens))
|
||||
} else {
|
||||
Err(ContractError::InsufficientSpendable(
|
||||
address.as_str().to_string(),
|
||||
spendable_coins.amount.u128(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_track_undelegation(
|
||||
address: &str,
|
||||
mix_identity: IdentityKey,
|
||||
@@ -205,7 +256,8 @@ fn try_undelegate_from_mixnode(
|
||||
}
|
||||
|
||||
fn try_create_periodic_vesting_account(
|
||||
address: &str,
|
||||
owner_address: &str,
|
||||
staking_address: Option<String>,
|
||||
start_time: Option<u64>,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
@@ -215,11 +267,17 @@ fn try_create_periodic_vesting_account(
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
let coin = validate_funds(&info.funds)?;
|
||||
let address = deps.api.addr_validate(address)?;
|
||||
let owner_address = deps.api.addr_validate(owner_address)?;
|
||||
let staking_address = if let Some(staking_address) = staking_address {
|
||||
Some(deps.api.addr_validate(&staking_address)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let start_time = start_time.unwrap_or_else(|| env.block.time.seconds());
|
||||
let periods = populate_vesting_periods(start_time, NUM_VESTING_PERIODS);
|
||||
Account::new(
|
||||
address,
|
||||
owner_address,
|
||||
staking_address,
|
||||
coin,
|
||||
Timestamp::from_seconds(start_time),
|
||||
periods,
|
||||
|
||||
@@ -38,4 +38,8 @@ pub enum ContractError {
|
||||
Underflow,
|
||||
#[error("No bond found for account {0}")]
|
||||
NoBondFound(String),
|
||||
#[error("Action can only be executed by account owner -> {0}")]
|
||||
NotOwner(String),
|
||||
#[error("Invalid address: {0}")]
|
||||
InvalidAddress(String),
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ pub enum ExecuteMsg {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
CreateAccount {
|
||||
address: String,
|
||||
owner_address: String,
|
||||
staking_address: Option<String>,
|
||||
start_time: Option<u64>,
|
||||
},
|
||||
WithdrawVestedCoins {
|
||||
@@ -47,6 +48,12 @@ pub enum ExecuteMsg {
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
},
|
||||
TransferOwnership {
|
||||
to_address: String,
|
||||
},
|
||||
UpdateStakingAddress {
|
||||
to_address: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -5,8 +5,18 @@ use cw_storage_plus::Map;
|
||||
|
||||
const ACCOUNTS: Map<Addr, Account> = Map::new("acc");
|
||||
|
||||
pub fn delete_account(address: &Addr, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
ACCOUNTS.remove(storage, address.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_account(account: &Account, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
Ok(ACCOUNTS.save(storage, account.address(), account)?)
|
||||
// This is a bit dirty, but its a simple way to allow for both staking account and owner to load it from storage
|
||||
if let Some(staking_address) = account.staking_address() {
|
||||
ACCOUNTS.save(storage, staking_address.to_owned(), account)?;
|
||||
}
|
||||
ACCOUNTS.save(storage, account.owner_address(), account)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_account(
|
||||
|
||||
@@ -21,7 +21,8 @@ pub mod helpers {
|
||||
let periods = populate_vesting_periods(start_time.seconds(), NUM_VESTING_PERIODS);
|
||||
|
||||
Account::new(
|
||||
Addr::unchecked("fixture"),
|
||||
Addr::unchecked("owner"),
|
||||
Some(Addr::unchecked("staking")),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_000),
|
||||
denom: DENOM.to_string(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::errors::ContractError;
|
||||
use cosmwasm_std::{Coin, Env, Storage, Timestamp};
|
||||
use cosmwasm_std::{Addr, Coin, Env, Storage, Timestamp};
|
||||
|
||||
pub trait VestingAccount {
|
||||
// locked_coins returns the set of coins that are not spendable (can still be delegated tough) (i.e. locked),
|
||||
@@ -62,4 +62,14 @@ pub trait VestingAccount {
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
fn transfer_ownership(
|
||||
&mut self,
|
||||
to_address: &Addr,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
fn update_staking_address(
|
||||
&mut self,
|
||||
to_address: Option<Addr>,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::errors::ContractError;
|
||||
use crate::traits::DelegatingAccount;
|
||||
use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM};
|
||||
use cosmwasm_std::{wasm_execute, Coin, Env, Order, Response, Storage, Timestamp, Uint128};
|
||||
use cw_storage_plus::Map;
|
||||
use mixnet_contract::ExecuteMsg as MixnetExecuteMsg;
|
||||
use mixnet_contract::IdentityKey;
|
||||
|
||||
@@ -19,14 +20,14 @@ impl DelegatingAccount for Account {
|
||||
|
||||
if current_balance < coin.amount {
|
||||
return Err(ContractError::InsufficientBalance(
|
||||
self.address.as_str().to_string(),
|
||||
self.owner_address().as_str().to_string(),
|
||||
current_balance.u128(),
|
||||
));
|
||||
}
|
||||
|
||||
let msg = MixnetExecuteMsg::DelegateToMixnodeOnBehalf {
|
||||
mix_identity: mix_identity.clone(),
|
||||
delegate: self.address().into_string(),
|
||||
delegate: self.owner_address().into_string(),
|
||||
};
|
||||
let delegate_to_mixnode =
|
||||
wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![coin.clone()])?;
|
||||
@@ -44,14 +45,14 @@ impl DelegatingAccount for Account {
|
||||
) -> Result<Response, ContractError> {
|
||||
if !self.any_delegation_for_mix(&mix_identity, storage) {
|
||||
return Err(ContractError::NoSuchDelegation(
|
||||
self.address(),
|
||||
self.owner_address(),
|
||||
mix_identity,
|
||||
));
|
||||
}
|
||||
|
||||
let msg = MixnetExecuteMsg::UndelegateFromMixnodeOnBehalf {
|
||||
mix_identity,
|
||||
delegate: self.address().into_string(),
|
||||
delegate: self.owner_address().into_string(),
|
||||
};
|
||||
let undelegate_from_mixnode = wasm_execute(
|
||||
DEFAULT_MIXNET_CONTRACT_ADDRESS,
|
||||
@@ -76,17 +77,17 @@ impl DelegatingAccount for Account {
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
let delegation_key = (mix_identity.as_bytes(), block_time.seconds());
|
||||
let delegations_storage_key = self.delegations_key();
|
||||
let delegations: Map<(&[u8], u64), Uint128> = Map::new(&delegations_storage_key);
|
||||
|
||||
let new_delegation = if let Some(existing_delegation) =
|
||||
self.delegations().may_load(storage, delegation_key)?
|
||||
{
|
||||
existing_delegation + delegation.amount
|
||||
} else {
|
||||
delegation.amount
|
||||
};
|
||||
let new_delegation =
|
||||
if let Some(existing_delegation) = delegations.may_load(storage, delegation_key)? {
|
||||
existing_delegation + delegation.amount
|
||||
} else {
|
||||
delegation.amount
|
||||
};
|
||||
|
||||
self.delegations()
|
||||
.save(storage, delegation_key, &new_delegation)?;
|
||||
delegations.save(storage, delegation_key, &new_delegation)?;
|
||||
|
||||
let new_balance = Uint128::new(current_balance.u128() - delegation.amount.u128());
|
||||
|
||||
@@ -102,10 +103,11 @@ impl DelegatingAccount for Account {
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
let mix_bytes = mix_identity.as_bytes();
|
||||
let delegations_key = self.delegations_key();
|
||||
let delegations: Map<(&[u8], u64), Uint128> = Map::new(&delegations_key);
|
||||
|
||||
// Iterate over keys matching the prefix and remove them from the map
|
||||
let block_times = self
|
||||
.delegations()
|
||||
let block_times = delegations
|
||||
.prefix_de(mix_bytes)
|
||||
.keys_de(storage, None, None, Order::Ascending)
|
||||
// Scan will blow up on first error
|
||||
@@ -113,7 +115,7 @@ impl DelegatingAccount for Account {
|
||||
.collect::<Vec<u64>>();
|
||||
|
||||
for t in block_times {
|
||||
self.delegations().remove(storage, (mix_bytes, t))
|
||||
delegations.remove(storage, (mix_bytes, t))
|
||||
}
|
||||
|
||||
let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128());
|
||||
|
||||
@@ -20,14 +20,14 @@ impl GatewayBondingAccount for Account {
|
||||
|
||||
if current_balance < pledge.amount {
|
||||
return Err(ContractError::InsufficientBalance(
|
||||
self.address.as_str().to_string(),
|
||||
self.owner_address().as_str().to_string(),
|
||||
current_balance.u128(),
|
||||
));
|
||||
}
|
||||
|
||||
let pledge_data = if self.load_gateway_pledge(storage)?.is_some() {
|
||||
return Err(ContractError::AlreadyBonded(
|
||||
self.address.as_str().to_string(),
|
||||
self.owner_address().as_str().to_string(),
|
||||
));
|
||||
} else {
|
||||
PledgeData {
|
||||
@@ -38,7 +38,7 @@ impl GatewayBondingAccount for Account {
|
||||
|
||||
let msg = MixnetExecuteMsg::BondGatewayOnBehalf {
|
||||
gateway,
|
||||
owner: self.address().into_string(),
|
||||
owner: self.owner_address().into_string(),
|
||||
owner_signature,
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ impl GatewayBondingAccount for Account {
|
||||
|
||||
fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::UnbondGatewayOnBehalf {
|
||||
owner: self.address().into_string(),
|
||||
owner: self.owner_address().into_string(),
|
||||
};
|
||||
|
||||
if let Some(_bond) = self.load_gateway_pledge(storage)? {
|
||||
@@ -67,7 +67,7 @@ impl GatewayBondingAccount for Account {
|
||||
.add_message(unbond_msg))
|
||||
} else {
|
||||
Err(ContractError::NoBondFound(
|
||||
self.address.as_str().to_string(),
|
||||
self.owner_address().as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ impl GatewayBondingAccount for Account {
|
||||
let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128());
|
||||
self.save_balance(new_balance, storage)?;
|
||||
|
||||
self.remove_gateway_bond(storage)?;
|
||||
self.remove_gateway_pledge(storage)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ impl MixnodeBondingAccount for Account {
|
||||
|
||||
if current_balance < pledge.amount {
|
||||
return Err(ContractError::InsufficientBalance(
|
||||
self.address.as_str().to_string(),
|
||||
self.owner_address().as_str().to_string(),
|
||||
current_balance.u128(),
|
||||
));
|
||||
}
|
||||
|
||||
let pledge_data = if self.load_mixnode_pledge(storage)?.is_some() {
|
||||
return Err(ContractError::AlreadyBonded(
|
||||
self.address.as_str().to_string(),
|
||||
self.owner_address().as_str().to_string(),
|
||||
));
|
||||
} else {
|
||||
PledgeData {
|
||||
@@ -38,7 +38,7 @@ impl MixnodeBondingAccount for Account {
|
||||
|
||||
let msg = MixnetExecuteMsg::BondMixnodeOnBehalf {
|
||||
mix_node,
|
||||
owner: self.address().into_string(),
|
||||
owner: self.owner_address().into_string(),
|
||||
owner_signature,
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ impl MixnodeBondingAccount for Account {
|
||||
|
||||
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::UnbondMixnodeOnBehalf {
|
||||
owner: self.address().into_string(),
|
||||
owner: self.owner_address().into_string(),
|
||||
};
|
||||
|
||||
if self.load_mixnode_pledge(storage)?.is_some() {
|
||||
@@ -67,7 +67,7 @@ impl MixnodeBondingAccount for Account {
|
||||
.add_message(unbond_msg))
|
||||
} else {
|
||||
Err(ContractError::NoBondFound(
|
||||
self.address.as_str().to_string(),
|
||||
self.owner_address().as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ impl MixnodeBondingAccount for Account {
|
||||
let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128());
|
||||
self.save_balance(new_balance, storage)?;
|
||||
|
||||
self.remove_mixnode_bond(storage)?;
|
||||
self.remove_mixnode_pledge(storage)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ use crate::storage::save_account;
|
||||
use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128};
|
||||
use cw_storage_plus::{Item, Map};
|
||||
use mixnet_contract::IdentityKey;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -18,44 +20,75 @@ const BALANCE_SUFFIX: &str = "ba";
|
||||
const PLEDGE_SUFFIX: &str = "bo";
|
||||
const GATEWAY_SUFFIX: &str = "ga";
|
||||
|
||||
fn generate_storage_key(b: &[u8], storage: &dyn Storage) -> Result<String, ContractError> {
|
||||
let mut rng = StdRng::seed_from_u64(b.iter().fold(0, |acc, x| acc + *x as u64));
|
||||
// Be paranoid and check for collisions
|
||||
loop {
|
||||
let key = rng.next_u64().to_string();
|
||||
let balance_key = format!("{}{}", key, BALANCE_SUFFIX);
|
||||
let balance: Item<Uint128> = Item::new(&balance_key);
|
||||
if balance.may_load(storage)?.is_none() {
|
||||
return Ok(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct Account {
|
||||
address: Addr,
|
||||
owner_address: Addr,
|
||||
staking_address: Option<Addr>,
|
||||
start_time: Timestamp,
|
||||
periods: Vec<VestingPeriod>,
|
||||
coin: Coin,
|
||||
delegations_key: String,
|
||||
balance_key: String,
|
||||
mixnode_pledge_key: String,
|
||||
gateway_pledge_key: String,
|
||||
storage_key: String,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(
|
||||
address: Addr,
|
||||
owner_address: Addr,
|
||||
staking_address: Option<Addr>,
|
||||
coin: Coin,
|
||||
start_time: Timestamp,
|
||||
periods: Vec<VestingPeriod>,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Self, ContractError> {
|
||||
let storage_key = generate_storage_key(owner_address.as_bytes(), storage)?;
|
||||
let amount = coin.amount;
|
||||
let account = Account {
|
||||
address: address.to_owned(),
|
||||
owner_address,
|
||||
staking_address,
|
||||
start_time,
|
||||
periods,
|
||||
coin,
|
||||
delegations_key: format!("{}_{}", address, DELEGATIONS_SUFFIX),
|
||||
balance_key: format!("{}_{}", address, BALANCE_SUFFIX),
|
||||
mixnode_pledge_key: format!("{}_{}", address, PLEDGE_SUFFIX),
|
||||
gateway_pledge_key: format!("{}_{}", address, GATEWAY_SUFFIX),
|
||||
storage_key,
|
||||
};
|
||||
save_account(&account, storage)?;
|
||||
account.save_balance(amount, storage)?;
|
||||
Ok(account)
|
||||
}
|
||||
|
||||
pub fn address(&self) -> Addr {
|
||||
self.address.clone()
|
||||
pub fn delegations_key(&self) -> String {
|
||||
format!("{}{}", self.storage_key, DELEGATIONS_SUFFIX)
|
||||
}
|
||||
|
||||
pub fn balance_key(&self) -> String {
|
||||
format!("{}{}", self.storage_key, BALANCE_SUFFIX)
|
||||
}
|
||||
|
||||
pub fn mixnode_pledge_key(&self) -> String {
|
||||
format!("{}{}", self.storage_key, PLEDGE_SUFFIX)
|
||||
}
|
||||
|
||||
pub fn gateway_pledge_key(&self) -> String {
|
||||
format!("{}{}", self.storage_key, GATEWAY_SUFFIX)
|
||||
}
|
||||
|
||||
pub fn owner_address(&self) -> Addr {
|
||||
self.owner_address.clone()
|
||||
}
|
||||
|
||||
pub fn staking_address(&self) -> Option<&Addr> {
|
||||
self.staking_address.as_ref()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -100,10 +133,9 @@ impl Account {
|
||||
}
|
||||
|
||||
pub fn load_balance(&self, storage: &dyn Storage) -> Result<Uint128, ContractError> {
|
||||
Ok(self
|
||||
.balance()
|
||||
.may_load(storage)?
|
||||
.unwrap_or_else(Uint128::zero))
|
||||
let key = self.balance_key();
|
||||
let balance = Item::new(&key);
|
||||
Ok(balance.may_load(storage)?.unwrap_or_else(Uint128::zero))
|
||||
}
|
||||
|
||||
pub fn save_balance(
|
||||
@@ -111,18 +143,18 @@ impl Account {
|
||||
amount: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
Ok(self.balance().save(storage, &amount)?)
|
||||
}
|
||||
|
||||
fn balance(&self) -> Item<Uint128> {
|
||||
Item::new(self.balance_key.as_ref())
|
||||
let key = self.balance_key();
|
||||
let balance = Item::new(&key);
|
||||
Ok(balance.save(storage, &amount)?)
|
||||
}
|
||||
|
||||
pub fn load_mixnode_pledge(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Option<PledgeData>, ContractError> {
|
||||
Ok(self.mixnode_pledge().may_load(storage)?)
|
||||
let key = self.mixnode_pledge_key();
|
||||
let mixnode_pledge = Item::new(&key);
|
||||
Ok(mixnode_pledge.may_load(storage)?)
|
||||
}
|
||||
|
||||
pub fn save_mixnode_pledge(
|
||||
@@ -130,23 +162,25 @@ impl Account {
|
||||
pledge: PledgeData,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
Ok(self.mixnode_pledge().save(storage, &pledge)?)
|
||||
let key = self.mixnode_pledge_key();
|
||||
let mixnode_pledge = Item::new(&key);
|
||||
Ok(mixnode_pledge.save(storage, &pledge)?)
|
||||
}
|
||||
|
||||
pub fn remove_mixnode_bond(&self, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
self.mixnode_pledge().remove(storage);
|
||||
pub fn remove_mixnode_pledge(&self, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
let key = self.mixnode_pledge_key();
|
||||
let mixnode_pledge: Item<PledgeData> = Item::new(&key);
|
||||
mixnode_pledge.remove(storage);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mixnode_pledge(&self) -> Item<PledgeData> {
|
||||
Item::new(self.mixnode_pledge_key.as_ref())
|
||||
}
|
||||
|
||||
pub fn load_gateway_pledge(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Option<PledgeData>, ContractError> {
|
||||
Ok(self.gateway_pledge().may_load(storage)?)
|
||||
let key = self.gateway_pledge_key();
|
||||
let gateway_pledge = Item::new(&key);
|
||||
Ok(gateway_pledge.may_load(storage)?)
|
||||
}
|
||||
|
||||
pub fn save_gateway_pledge(
|
||||
@@ -154,25 +188,23 @@ impl Account {
|
||||
pledge: PledgeData,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
Ok(self.gateway_pledge().save(storage, &pledge)?)
|
||||
let key = self.gateway_pledge_key();
|
||||
let gateway_pledge = Item::new(&key);
|
||||
Ok(gateway_pledge.save(storage, &pledge)?)
|
||||
}
|
||||
|
||||
pub fn remove_gateway_bond(&self, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
self.gateway_pledge().remove(storage);
|
||||
pub fn remove_gateway_pledge(&self, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
let key = self.gateway_pledge_key();
|
||||
let gateway_pledge: Item<PledgeData> = Item::new(&key);
|
||||
gateway_pledge.remove(storage);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gateway_pledge(&self) -> Item<PledgeData> {
|
||||
Item::new(self.gateway_pledge_key.as_ref())
|
||||
}
|
||||
|
||||
fn delegations(&self) -> Map<(&[u8], u64), Uint128> {
|
||||
Map::new(self.delegations_key.as_ref())
|
||||
}
|
||||
|
||||
// Returns block_time part of the delegation key
|
||||
pub fn delegation_keys_for_mix(&self, mix: &str, storage: &dyn Storage) -> Vec<u64> {
|
||||
self.delegations()
|
||||
let key = self.delegations_key();
|
||||
let delegations: Map<(&[u8], u64), Uint128> = Map::new(&key);
|
||||
delegations
|
||||
.prefix_de(mix.as_bytes())
|
||||
.keys_de(storage, None, None, Order::Ascending)
|
||||
// Scan will blow up on first error
|
||||
@@ -191,10 +223,12 @@ impl Account {
|
||||
) -> Result<Vec<Uint128>, ContractError> {
|
||||
let mix_bytes = mix.as_bytes();
|
||||
let keys = self.delegation_keys_for_mix(&mix, storage);
|
||||
let delegations_key = self.delegations_key();
|
||||
let delegations: Map<(&[u8], u64), Uint128> = Map::new(&delegations_key);
|
||||
|
||||
let mut delegation_amounts = Vec::new();
|
||||
for key in keys {
|
||||
delegation_amounts.push(self.delegations().load(storage, (mix_bytes, key))?)
|
||||
delegation_amounts.push(delegations.load(storage, (mix_bytes, key))?)
|
||||
}
|
||||
|
||||
Ok(delegation_amounts)
|
||||
@@ -213,8 +247,9 @@ impl Account {
|
||||
}
|
||||
|
||||
pub fn total_delegations(&self, storage: &dyn Storage) -> Result<Uint128, ContractError> {
|
||||
Ok(self
|
||||
.delegations()
|
||||
let delegations_key = self.delegations_key();
|
||||
let delegations: Map<(&[u8], u64), Uint128> = Map::new(&delegations_key);
|
||||
Ok(delegations
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.scan((), |_, x| x.ok())
|
||||
.fold(Uint128::zero(), |acc, (_, x)| acc + x))
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::contract::NUM_VESTING_PERIODS;
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::{delete_account, save_account};
|
||||
use crate::traits::VestingAccount;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{Coin, Env, Order, Storage, Timestamp, Uint128};
|
||||
use cosmwasm_std::{Addr, Coin, Env, Order, Storage, Timestamp, Uint128};
|
||||
use cw_storage_plus::Map;
|
||||
|
||||
use super::Account;
|
||||
|
||||
@@ -113,9 +115,10 @@ impl VestingAccount for Account {
|
||||
let period = self.get_current_vesting_period(block_time);
|
||||
let max_vested = self.tokens_per_period()? * period as u128;
|
||||
let start_time = self.periods[period].start_time;
|
||||
let delegations_key = self.delegations_key();
|
||||
let delegations: Map<(&[u8], u64), Uint128> = Map::new(&delegations_key);
|
||||
|
||||
let delegations_keys = self
|
||||
.delegations()
|
||||
let delegations_keys = delegations
|
||||
.keys_de(storage, None, None, Order::Ascending)
|
||||
.scan((), |_, x| x.ok())
|
||||
.filter(|(_mix, block_time)| *block_time < start_time)
|
||||
@@ -124,7 +127,7 @@ impl VestingAccount for Account {
|
||||
|
||||
let mut amount = Uint128::zero();
|
||||
for (mix, block_time) in delegations_keys {
|
||||
amount += self.delegations().load(storage, (&mix, block_time))?
|
||||
amount += delegations.load(storage, (&mix, block_time))?
|
||||
}
|
||||
amount = Uint128::new(amount.u128().min(max_vested));
|
||||
|
||||
@@ -209,4 +212,28 @@ impl VestingAccount for Account {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn transfer_ownership(
|
||||
&mut self,
|
||||
to_address: &Addr,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
delete_account(&self.owner_address(), storage)?;
|
||||
self.owner_address = to_address.to_owned();
|
||||
save_account(self, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_staking_address(
|
||||
&mut self,
|
||||
to_address: Option<Addr>,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
if let Some(staking_address) = self.staking_address() {
|
||||
delete_account(staking_address, storage)?;
|
||||
}
|
||||
self.staking_address = to_address;
|
||||
save_account(self, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,35 +36,132 @@ pub fn populate_vesting_periods(start_time: u64, n: usize) -> Vec<VestingPeriod>
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::contract::{NUM_VESTING_PERIODS, VESTING_PERIOD};
|
||||
use crate::contract::{execute, ADMIN_ADDRESS, NUM_VESTING_PERIODS, VESTING_PERIOD};
|
||||
use crate::messages::ExecuteMsg;
|
||||
use crate::storage::load_account;
|
||||
use crate::support::tests::helpers::{init_contract, vesting_account_fixture};
|
||||
use crate::traits::DelegatingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, Addr, Coin, Timestamp, Uint128};
|
||||
use mixnet_contract::{Gateway, MixNode};
|
||||
|
||||
#[test]
|
||||
fn test_account_creation() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
let created_account = load_account(&account.address(), &deps.storage).unwrap();
|
||||
let created_account_test =
|
||||
load_account(&Addr::unchecked("fixture"), &deps.storage).unwrap();
|
||||
assert_eq!(Some(&account), created_account.as_ref());
|
||||
assert_eq!(Some(&account), created_account_test.as_ref());
|
||||
let info = mock_info("not_admin", &coins(1_000_000_000_000, DENOM));
|
||||
let msg = ExecuteMsg::CreateAccount {
|
||||
owner_address: "owner".to_string(),
|
||||
staking_address: Some("staking".to_string()),
|
||||
start_time: None,
|
||||
};
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||
assert!(response.is_err());
|
||||
|
||||
let info = mock_info(ADMIN_ADDRESS, &coins(1_000_000_000_000, DENOM));
|
||||
let _response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||
let created_account = load_account(&Addr::unchecked("owner"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let created_account_test_by_staking =
|
||||
load_account(&Addr::unchecked("staking"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(created_account_test_by_staking, created_account);
|
||||
assert_eq!(
|
||||
account.load_balance(&deps.storage).unwrap(),
|
||||
created_account.load_balance(&deps.storage).unwrap(),
|
||||
Uint128::new(1_000_000_000_000)
|
||||
);
|
||||
// Test key collision avoidance
|
||||
|
||||
let account_again = vesting_account_fixture(&mut deps.storage, &env);
|
||||
assert_eq!(
|
||||
account.load_balance(&deps.storage).unwrap(),
|
||||
Uint128::new(1_000_000_000_000)
|
||||
)
|
||||
created_account.balance_key(),
|
||||
"5032709489228919411ba".to_string()
|
||||
);
|
||||
assert_ne!(created_account.balance_key(), account_again.balance_key());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ownership_transfer() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
let info = mock_info("owner", &[]);
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
let msg = ExecuteMsg::TransferOwnership {
|
||||
to_address: "new_owner".to_string(),
|
||||
};
|
||||
let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap();
|
||||
let new_owner_account = load_account(&Addr::unchecked("new_owner"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
new_owner_account.load_balance(&deps.storage),
|
||||
account.load_balance(&deps.storage)
|
||||
);
|
||||
|
||||
// Check old account is gone
|
||||
let old_owner_account = load_account(&Addr::unchecked("owner"), &deps.storage).unwrap();
|
||||
assert!(old_owner_account.is_none());
|
||||
|
||||
// Not the owner
|
||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg);
|
||||
assert!(response.is_err());
|
||||
|
||||
let info = mock_info("new_owner", &[]);
|
||||
let msg = ExecuteMsg::UpdateStakingAddress {
|
||||
to_address: Some("new_staking".to_string()),
|
||||
};
|
||||
let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
let new_staking_account = load_account(&Addr::unchecked("new_staking"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(new_staking_account.owner_address(), "new_owner".to_string());
|
||||
|
||||
let old_staking_account = load_account(&Addr::unchecked("staking"), &deps.storage).unwrap();
|
||||
assert!(old_staking_account.is_none());
|
||||
|
||||
let msg = ExecuteMsg::WithdrawVestedCoins {
|
||||
amount: Coin {
|
||||
amount: Uint128::new(1),
|
||||
denom: "nym".to_string(),
|
||||
},
|
||||
};
|
||||
let info = mock_info("new_owner", &[]);
|
||||
env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000);
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||
assert!(response.is_ok());
|
||||
|
||||
let info = mock_info("owner", &[]);
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||
assert!(response.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_staking_account() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
let info = mock_info("staking", &[]);
|
||||
let msg = ExecuteMsg::TransferOwnership {
|
||||
to_address: "new_owner".to_string(),
|
||||
};
|
||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
||||
// Only owner can transfer
|
||||
assert!(response.is_err());
|
||||
|
||||
let msg = ExecuteMsg::WithdrawVestedCoins {
|
||||
amount: Coin {
|
||||
amount: Uint128::new(1),
|
||||
denom: "nym".to_string(),
|
||||
},
|
||||
};
|
||||
env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000);
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||
// Only owner can withdraw
|
||||
assert!(response.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+2
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
@@ -52,6 +52,7 @@ version-checker = { path = "../common/version-checker" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
eth = []
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
@@ -56,11 +56,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.help("Comma separated list of endpoints of the validators APIs")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this gateway to work in a testnet mode that would allow clients to bypass bandwidth credential requirement")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(WALLET_ADDRESS)
|
||||
.long(WALLET_ADDRESS)
|
||||
@@ -69,8 +64,14 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.required(true)
|
||||
);
|
||||
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this gateway to work in a testnet mode that would allow clients to bypass bandwidth credential requirement")
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT)
|
||||
.long(ETH_ENDPOINT)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
|
||||
|
||||
@@ -31,6 +31,14 @@ pub(crate) const DATASTORE_PATH: &str = "datastore";
|
||||
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
|
||||
pub(crate) const WALLET_ADDRESS: &str = "wallet-address";
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
const DEFAULT_ETH_ENDPOINT: &str = "https://rinkeby.infura.io/v3/00000000000000000000000000000000";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
const DEFAULT_VALIDATOR_ENDPOINT: &str = "http://localhost:26657";
|
||||
// A dummy mnemonic
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
const DEFAULT_MNEMONIC: &str = "typical regret aware used tennis noise resource crisp defy join donate orient army item immense clean emerge globe gift chronic loan flat enter egg";
|
||||
|
||||
fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
raw.split(',')
|
||||
.map(|raw_validator| {
|
||||
@@ -85,11 +93,15 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(raw_validators) = matches.value_of(VALIDATORS_ARG_NAME) {
|
||||
config = config.with_custom_validator_nymd(parse_validators(raw_validators));
|
||||
} else {
|
||||
config = config.with_custom_validator_nymd(parse_validators(DEFAULT_VALIDATOR_ENDPOINT));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(cosmos_mnemonic) = matches.value_of(COSMOS_MNEMONIC) {
|
||||
config = config.with_cosmos_mnemonic(String::from(cosmos_mnemonic));
|
||||
} else {
|
||||
config = config.with_cosmos_mnemonic(String::from(DEFAULT_MNEMONIC));
|
||||
}
|
||||
|
||||
if let Some(datastore_path) = matches.value_of(DATASTORE_PATH) {
|
||||
@@ -99,6 +111,8 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT) {
|
||||
config = config.with_eth_endpoint(String::from(eth_endpoint));
|
||||
} else {
|
||||
config = config.with_eth_endpoint(String::from(DEFAULT_ETH_ENDPOINT));
|
||||
}
|
||||
|
||||
if let Some(wallet_address) = matches.value_of(WALLET_ADDRESS) {
|
||||
@@ -107,7 +121,7 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
|
||||
config = config.with_wallet_address(trimmed);
|
||||
}
|
||||
|
||||
if matches.is_present(TESTNET_MODE_ARG_NAME) {
|
||||
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
|
||||
config.with_testnet_mode(true)
|
||||
} else {
|
||||
config
|
||||
@@ -134,3 +148,22 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) {
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
pub(crate) fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = cfg.get_version();
|
||||
if binary_version != config_version {
|
||||
log::warn!("The gateway binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
|
||||
if version_checker::is_minor_version_compatible(binary_version, config_version) {
|
||||
log::info!("but they are still semver compatible. However, consider running the `upgrade` command");
|
||||
true
|
||||
} else {
|
||||
log::error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::node::Gateway;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use log::*;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
let app = App::new("run")
|
||||
@@ -95,25 +94,6 @@ fn special_addresses() -> Vec<&'static str> {
|
||||
vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = cfg.get_version();
|
||||
if binary_version != config_version {
|
||||
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
|
||||
if is_minor_version_compatible(binary_version, config_version) {
|
||||
info!("but they are still semver compatible. However, consider running the `upgrade` command");
|
||||
true
|
||||
} else {
|
||||
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let id = matches.value_of(ID_ARG_NAME).unwrap();
|
||||
|
||||
|
||||
@@ -5,16 +5,9 @@ use crate::commands::*;
|
||||
use crate::config::{persistence::pathfinder::GatewayPathfinder, Config};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use colored::Colorize;
|
||||
#[cfg(feature = "coconut")]
|
||||
use config::defaults::BECH32_PREFIX;
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::identity;
|
||||
use log::error;
|
||||
use std::process;
|
||||
#[cfg(feature = "coconut")]
|
||||
use subtle_encoding::bech32;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use validator_client::nymd::AccountId;
|
||||
|
||||
const SIGN_TEXT_ARG_NAME: &str = "text";
|
||||
const SIGN_ADDRESS_ARG_NAME: &str = "address";
|
||||
@@ -40,6 +33,7 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> {
|
||||
let mut address_sign_cmd = Arg::with_name(SIGN_ADDRESS_ARG_NAME)
|
||||
.long(SIGN_ADDRESS_ARG_NAME)
|
||||
.help("Signs your blockchain address with your identity key")
|
||||
.takes_value(true)
|
||||
.conflicts_with(SIGN_TEXT_ARG_NAME);
|
||||
|
||||
if cfg!(feature = "coconut") {
|
||||
@@ -60,57 +54,6 @@ pub fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair {
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
fn derive_address(raw_mnemonic: &str) -> AccountId {
|
||||
let mnemonic = match raw_mnemonic.parse() {
|
||||
Ok(mnemonic) => mnemonic,
|
||||
Err(err) => {
|
||||
let error_message = format!("failed to parse the provided mnemonic - {}", err).red();
|
||||
println!("{}", error_message);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let wallet =
|
||||
match validator_client::nymd::wallet::DirectSecp256k1HdWallet::from_mnemonic(mnemonic) {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
let error_message = format!(
|
||||
"failed to derive your account with the provided mnemonic - {}",
|
||||
err
|
||||
)
|
||||
.red();
|
||||
println!("{}", error_message);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let account_data = match wallet.try_derive_accounts() {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
let error_message = format!(
|
||||
"failed to derive your account with the provided mnemonic - {}",
|
||||
err
|
||||
)
|
||||
.red();
|
||||
println!("{}", error_message);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
account_data[0].address().clone()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
fn sign_derived_address(private_key: &identity::PrivateKey, address: &AccountId) {
|
||||
let signature_bytes = private_key.sign(&address.to_bytes()).to_bytes();
|
||||
let signature = bs58::encode(signature_bytes).into_string();
|
||||
|
||||
println!(
|
||||
"The base58-encoded signature on '{}' is: {}",
|
||||
address, signature
|
||||
)
|
||||
}
|
||||
|
||||
// we do tiny bit of sanity check validation
|
||||
#[cfg(feature = "coconut")]
|
||||
fn print_signed_address(private_key: &identity::PrivateKey, raw_address: &str) -> String {
|
||||
let trimmed = raw_address.trim();
|
||||
validate_bech32_address_or_exit(trimmed);
|
||||
@@ -147,28 +90,20 @@ pub fn execute(matches: &ArgMatches) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
}
|
||||
|
||||
let pathfinder = GatewayPathfinder::new_from_config(&config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder);
|
||||
|
||||
if let Some(text) = matches.value_of(SIGN_TEXT_ARG_NAME) {
|
||||
print_signed_text(identity_keypair.private_key(), text)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
{
|
||||
if matches.is_present(SIGN_ADDRESS_ARG_NAME) {
|
||||
let address = derive_address(&config.get_cosmos_mnemonic());
|
||||
sign_derived_address(identity_keypair.private_key(), &address);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "coconut")]
|
||||
{
|
||||
if let Some(address) = matches.value_of(SIGN_ADDRESS_ARG_NAME) {
|
||||
sign_provided_address(identity_keypair.private_key(), address)
|
||||
}
|
||||
}
|
||||
|
||||
if !matches.is_present(SIGN_TEXT_ARG_NAME) && !matches.is_present(SIGN_ADDRESS_ARG_NAME) {
|
||||
} else if let Some(address) = matches.value_of(SIGN_ADDRESS_ARG_NAME) {
|
||||
print_signed_address(identity_keypair.private_key(), address);
|
||||
} else {
|
||||
let error_message = format!(
|
||||
"You must specify either '--{}' or '--{}' argument!",
|
||||
SIGN_TEXT_ARG_NAME, SIGN_ADDRESS_ARG_NAME
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
@@ -48,4 +48,4 @@ version-checker = { path="../common/version-checker" }
|
||||
serial_test = "0.5"
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::node::MixNode;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
App::new("init")
|
||||
@@ -66,59 +65,56 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
// TODO: this should probably be made implicit by slapping `#[tokio::main]` on our main method
|
||||
// and then removing runtime from mixnode itself in `run`
|
||||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
let id = matches.value_of(ID_ARG_NAME).unwrap();
|
||||
println!("Initialising mixnode {}...", id);
|
||||
let id = matches.value_of(ID_ARG_NAME).unwrap();
|
||||
println!("Initialising mixnode {}...", id);
|
||||
|
||||
let already_init = if Config::default_config_file_path(Some(id)).exists() {
|
||||
println!("Mixnode \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let already_init = if Config::default_config_file_path(Some(id)).exists() {
|
||||
println!("Mixnode \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mut config = Config::new(id);
|
||||
config = override_config(config, matches);
|
||||
let mut config = Config::new(id);
|
||||
config = override_config(config, &matches);
|
||||
|
||||
// if node was already initialised, don't generate new keys
|
||||
if !already_init {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
// if node was already initialised, don't generate new keys
|
||||
if !already_init {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
let identity_keys = identity::KeyPair::new(&mut rng);
|
||||
let sphinx_keys = encryption::KeyPair::new(&mut rng);
|
||||
let pathfinder = MixNodePathfinder::new_from_config(&config);
|
||||
pemstore::store_keypair(
|
||||
&identity_keys,
|
||||
&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
),
|
||||
)
|
||||
.expect("Failed to save identity keys");
|
||||
let identity_keys = identity::KeyPair::new(&mut rng);
|
||||
let sphinx_keys = encryption::KeyPair::new(&mut rng);
|
||||
let pathfinder = MixNodePathfinder::new_from_config(&config);
|
||||
pemstore::store_keypair(
|
||||
&identity_keys,
|
||||
&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
),
|
||||
)
|
||||
.expect("Failed to save identity keys");
|
||||
|
||||
pemstore::store_keypair(
|
||||
&sphinx_keys,
|
||||
&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
),
|
||||
)
|
||||
.expect("Failed to save sphinx keys");
|
||||
pemstore::store_keypair(
|
||||
&sphinx_keys,
|
||||
&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
),
|
||||
)
|
||||
.expect("Failed to save sphinx keys");
|
||||
|
||||
println!("Saved mixnet identity and sphinx keypairs");
|
||||
}
|
||||
println!("Saved mixnet identity and sphinx keypairs");
|
||||
}
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
config
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
println!("Mixnode configuration completed.\n\n\n");
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
config
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
println!("Mixnode configuration completed.\n\n\n");
|
||||
|
||||
MixNode::new(config).print_node_details()
|
||||
})
|
||||
MixNode::new(config).print_node_details()
|
||||
}
|
||||
|
||||
@@ -116,3 +116,22 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) {
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
pub(crate) fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = cfg.get_version();
|
||||
if binary_version != config_version {
|
||||
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
|
||||
if version_checker::is_minor_version_compatible(binary_version, config_version) {
|
||||
info!("but they are still semver compatible. However, consider running the `upgrade` command");
|
||||
true
|
||||
} else {
|
||||
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ use crate::config::Config;
|
||||
use crate::node::MixNode;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use log::warn;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("run")
|
||||
@@ -73,26 +71,7 @@ fn special_addresses() -> Vec<&'static str> {
|
||||
vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = cfg.get_version();
|
||||
if binary_version != config_version {
|
||||
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
|
||||
if is_minor_version_compatible(binary_version, config_version) {
|
||||
info!("but they are still semver compatible. However, consider running the `upgrade` command");
|
||||
true
|
||||
} else {
|
||||
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let id = matches.value_of(ID_ARG_NAME).unwrap();
|
||||
|
||||
println!("Starting mixnode {}...", id);
|
||||
@@ -105,7 +84,7 @@ pub fn execute(matches: &ArgMatches) {
|
||||
}
|
||||
};
|
||||
|
||||
config = override_config(config, matches);
|
||||
config = override_config(config, &matches);
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
@@ -123,5 +102,5 @@ pub fn execute(matches: &ArgMatches) {
|
||||
Select the correct version and install it to your machine. You will need to provide the following: \n ");
|
||||
mixnode.print_node_details();
|
||||
|
||||
mixnode.run()
|
||||
mixnode.run().await
|
||||
}
|
||||
|
||||
@@ -83,6 +83,12 @@ pub fn execute(matches: &ArgMatches) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
}
|
||||
|
||||
let pathfinder = MixNodePathfinder::new_from_config(&config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder);
|
||||
|
||||
|
||||
+6
-5
@@ -10,7 +10,8 @@ mod commands;
|
||||
mod config;
|
||||
mod node;
|
||||
|
||||
fn main() {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
@@ -28,14 +29,14 @@ fn main() {
|
||||
.subcommand(commands::node_details::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches);
|
||||
execute(arg_matches).await;
|
||||
}
|
||||
|
||||
fn execute(matches: ArgMatches) {
|
||||
async fn execute(matches: ArgMatches<'static>) {
|
||||
match matches.subcommand() {
|
||||
("describe", Some(m)) => commands::describe::execute(m),
|
||||
("init", Some(m)) => commands::init::execute(m),
|
||||
("run", Some(m)) => commands::run::execute(m),
|
||||
("init", Some(m)) => commands::init::execute(m.clone()).await,
|
||||
("run", Some(m)) => commands::run::execute(m.clone()).await,
|
||||
("sign", Some(m)) => commands::sign::execute(m),
|
||||
("upgrade", Some(m)) => commands::upgrade::execute(m),
|
||||
("node-details", Some(m)) => commands::node_details::execute(m),
|
||||
|
||||
+19
-23
@@ -27,7 +27,6 @@ use rand::thread_rng;
|
||||
use std::net::SocketAddr;
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Runtime;
|
||||
use version_checker::parse_version;
|
||||
|
||||
pub(crate) mod http;
|
||||
@@ -279,33 +278,30 @@ impl MixNode {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
pub async fn run(&mut self) {
|
||||
info!("Starting nym mixnode");
|
||||
|
||||
let runtime = Runtime::new().unwrap();
|
||||
|
||||
runtime.block_on(async {
|
||||
if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists().await {
|
||||
if duplicate_node_key == self.identity_keypair.public_key().to_base58_string() {
|
||||
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
|
||||
} else {
|
||||
log::error!(
|
||||
"Our announce-host is identical to an existing node's announce-host! (its key is {:?})",
|
||||
duplicate_node_key
|
||||
);
|
||||
return;
|
||||
}
|
||||
if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists().await {
|
||||
if duplicate_node_key == self.identity_keypair.public_key().to_base58_string() {
|
||||
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
|
||||
} else {
|
||||
log::error!(
|
||||
"Our announce-host is identical to an existing node's announce-host! (its key is {:?})",
|
||||
duplicate_node_key
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let (node_stats_pointer, node_stats_update_sender) = self.start_node_stats_controller();
|
||||
let delay_forwarding_channel = self.start_packet_delay_forwarder(node_stats_update_sender.clone());
|
||||
self.start_socket_listener(node_stats_update_sender, delay_forwarding_channel);
|
||||
let (node_stats_pointer, node_stats_update_sender) = self.start_node_stats_controller();
|
||||
let delay_forwarding_channel =
|
||||
self.start_packet_delay_forwarder(node_stats_update_sender.clone());
|
||||
self.start_socket_listener(node_stats_update_sender, delay_forwarding_channel);
|
||||
|
||||
let atomic_verloc_results= self.start_verloc_measurements();
|
||||
self.start_http_api(atomic_verloc_results, node_stats_pointer);
|
||||
let atomic_verloc_results = self.start_verloc_measurements();
|
||||
self.start_http_api(atomic_verloc_results, node_stats_pointer);
|
||||
|
||||
info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!");
|
||||
self.wait_for_interrupt().await
|
||||
});
|
||||
info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!");
|
||||
self.wait_for_interrupt().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
MAJOR_CURRENCY=nymt
|
||||
MINOR_CURRENCY=unymt
|
||||
ADMIN_ADDRESS=nymt1a586lvexy7ahva8h27fp6h8ds8taea6p95w3vx
|
||||
NETWORK_NAME=nym-sandbox
|
||||
NETWORK_NAME=sandbox
|
||||
Generated
+2
@@ -5188,7 +5188,9 @@ dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"getrandom 0.2.3",
|
||||
"mixnet-contract",
|
||||
"rand 0.8.4",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
|
||||
@@ -29,9 +29,20 @@ Inside the `nym-wallet` directory, run the following command:
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Populate environment variables
|
||||
|
||||
The wallet requires you to supply a `.env` file, this populates values in the wallet once it's compiled.
|
||||
|
||||
In the project roots there's a `.env.sample` file, these values currently match what the `.env` file should be populated with. However, if you want to change these values you can do so accordingly.
|
||||
|
||||
- In the root directory, create a new file named `.env`
|
||||
- Input the values against the variables
|
||||
|
||||
## Development mode
|
||||
|
||||
You can compile the wallet in development mode by running the following command inside the `nym-wallet` directory:
|
||||
|
||||
```
|
||||
yarn dev
|
||||
```
|
||||
|
||||
@@ -30,7 +30,7 @@ eyre = "0.6.5"
|
||||
|
||||
|
||||
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev="e5a1872083abb3d88fa62dda966e7f5408deba58", features = ["rpc", "bip32", "cosmwasm"] }
|
||||
cosmwasm-std = "1.0.0-beta2"
|
||||
cosmwasm-std = "1.0.0-beta3"
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nymd-client",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-wallet",
|
||||
"version": "0.12.0"
|
||||
"version": "0.12.1"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
Reference in New Issue
Block a user