Compare commits

...

12 Commits

Author SHA1 Message Date
Tommy Verrall f95e9a7d37 Implement base line for tests on the validator ts client
- This will need to be separated and configured accordingly 
- This was a quick spin up, using jest as a library to implement some coverage
- Further things to be refined - mocks, more coverage, better configuration, clean up methods, improve env vars
2022-01-06 10:01:08 +00:00
Bogdan-Ștefan Neacșu 2041b03046 Missed takes_value() call 2022-01-04 17:12:41 +01:00
Bogdan-Ștefan Neacşu 0b6adf59ce Feature/fix gateway sign (#1004)
* Include version check in sign command

* Ask for wallet address the same way as mixnode

The reason for this is that the cosmos mnemonic that is asked at
init is for the address that gets rewarded for gateway usage.
Since that address is not necessarly set now and it can take a
default value, we won't be using that to derive the address in the
signing process.
2022-01-04 13:49:24 +01:00
Jędrzej Stuczyński d95df4b286 Feature/implicit runtime (#973)
* Made mixnode runtime implicit

* Ibid for native and socks5 clients
2022-01-04 11:00:48 +00:00
Bogdan-Ștefan Neacşu 4f109169af Fix clippy (#1003) 2022-01-03 14:32:37 +01:00
Bogdan-Ștefan Neacșu 0cef1abbb2 Update mixnode version 2021-12-23 15:05:11 +02:00
Tommy Verrall 1871c6b2e3 Merge pull request #998 from nymtech/feature/wallet-update-version
Update wallet version
2021-12-23 10:01:13 +00:00
Tommy Verrall 75ad2a113f Update wallet version
- keep all the binaries aligned
2021-12-23 10:00:13 +00:00
Bogdan-Ștefan Neacşu 1d1496aa49 Make the separation between testnet-mode and erc20 bandwidth mode clearer (#994)
* Make the separation between testnet-mode and erc20 bandwidth mode more clear

* Update Cargo.toml

* Remove eth bw from native client under a feature flag

* Remove eth bw from socks5 client under a feature flag

* Remove eth bw from gateway under a feature flag

* Update gateway version

* Fix coconut build warnings
2021-12-23 11:55:01 +02:00
Tommy Verrall a48e06fe51 Fix wallet build instructions (#997)
* Fix wallet build instructions

- Supplying the .env file requirement

* re-added the admin - address
2021-12-23 11:54:34 +02:00
Drazen Urch 614b99a36e Differentiate staking and ownership (#961)
* Differentiate staking and ownership

* Ownership transfer, tests

* Consistent random keys

* Improve account tests

* Update Cargo.lock

* Make everybody happy

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-22 13:22:46 +01:00
dependabot[bot] d8cb6199e0 Bump @openzeppelin/contracts in /contracts/basic-bandwidth-generation (#983)
Bumps [@openzeppelin/contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) from 3.4.0 to 4.4.1.
- [Release notes](https://github.com/OpenZeppelin/openzeppelin-contracts/releases)
- [Changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md)
- [Commits](https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.4.0...v4.4.1)

---
updated-dependencies:
- dependency-name: "@openzeppelin/contracts"
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-22 11:42:37 +01:00
73 changed files with 4939 additions and 3664 deletions
Generated
+12 -10
View File
@@ -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",
+23 -9
View File
@@ -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;
+2 -1
View File
@@ -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
+49 -67
View File
@@ -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(
+38 -33
View File
@@ -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());
+21 -3
View File
@@ -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)
}
+20 -15
View File
@@ -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;
}
+6 -5
View File
@@ -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()),
}
+2 -3
View File
@@ -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 })
}
}
+2 -1
View File
@@ -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"] }
+49 -67
View File
@@ -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(
+38 -33
View File
@@ -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());
+21 -3
View File
@@ -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)
}
+20 -15
View File
@@ -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;
}
+6 -5
View File
@@ -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()),
}
+6
View File
@@ -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
+6 -12
View File
@@ -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
------------------------
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFiles: ["dotenv/config"]
};
+7 -14
View File
@@ -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"
}
}
+6 -1
View File
@@ -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);
});
});
+10 -2
View File
@@ -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
View File
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
+1 -1
View File
@@ -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"
+53 -18
View File
@@ -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",
+2 -6
View File
@@ -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
View File
@@ -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"
+3 -7
View File
@@ -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"
+3 -6
View File
@@ -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"]}
+95 -37
View File
@@ -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,
+4
View File
@@ -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),
}
+8 -1
View File
@@ -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)]
+11 -1
View File
@@ -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(
+2 -1
View File
@@ -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(())
}
}
+81 -46
View File
@@ -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(())
}
}
+110 -13
View File
@@ -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
View File
@@ -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"] }
+6 -5
View File
@@ -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")
+34 -1
View File
@@ -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
}
}
-20
View File
@@ -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();
+10 -75
View File
@@ -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
View File
@@ -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"] }
+42 -46
View File
@@ -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()
}
+19
View File
@@ -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
}
}
+3 -24
View File
@@ -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
}
+6
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
MAJOR_CURRENCY=nymt
MINOR_CURRENCY=unymt
ADMIN_ADDRESS=nymt1a586lvexy7ahva8h27fp6h8ds8taea6p95w3vx
NETWORK_NAME=nym-sandbox
NETWORK_NAME=sandbox
+2
View File
@@ -5188,7 +5188,9 @@ dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"getrandom 0.2.3",
"mixnet-contract",
"rand 0.8.4",
"schemars",
"serde",
"thiserror",
+11
View File
@@ -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
```
+1 -1
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-wallet",
"version": "0.12.0"
"version": "0.12.1"
},
"build": {
"distDir": "../dist",