Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba00782486 | |||
| 047ca4f450 | |||
| f6410979bc | |||
| 8ed7774e93 | |||
| b43657f42d | |||
| 20624243c0 | |||
| fdff4bf1b7 | |||
| 47726d3561 | |||
| f3ed0bb11f | |||
| 351adb7f7b | |||
| e0567dddf2 | |||
| d109c53370 | |||
| baed6c89fc | |||
| 106491ef01 | |||
| 46135146ea | |||
| 04e5cfabb8 | |||
| 10be112279 | |||
| 634818a988 | |||
| 5cb80f7648 | |||
| 4d5565d8b6 | |||
| f172a23ef8 | |||
| 2363f3ad0a | |||
| 6d3e5f22d4 | |||
| c74ea43b94 | |||
| 49d8424e30 | |||
| 5a7f296328 | |||
| 8a6f8185db | |||
| 30dfa09e18 | |||
| 7e7072258d | |||
| 10221a1767 |
@@ -44,6 +44,12 @@ jobs:
|
|||||||
command: build
|
command: build
|
||||||
args: --workspace
|
args: --workspace
|
||||||
|
|
||||||
|
- name: Reclaim some disk space (because Windows is being annoying)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
if: ${{ matrix.os == 'windows-latest' }}
|
||||||
|
with:
|
||||||
|
command: clean
|
||||||
|
|
||||||
- name: Run all tests
|
- name: Run all tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -166,8 +166,8 @@ jobs:
|
|||||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMTECH_TEAM }}"
|
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||||
KEYBASE_NYM_CHANNEL: "test"
|
KEYBASE_NYM_CHANNEL: "ci-nightly"
|
||||||
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
|
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
|
||||||
uses: docker://keybaseio/client:stable-node
|
uses: docker://keybaseio/client:stable-node
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: Nightly builds on dispatch
|
||||||
|
|
||||||
|
on: workflow_dispatch
|
||||||
|
jobs:
|
||||||
|
notification:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Keybase - Node Install
|
||||||
|
run: npm install
|
||||||
|
working-directory: .github/workflows/support-files
|
||||||
|
- name: Keybase - Send Notification
|
||||||
|
env:
|
||||||
|
NYM_NOTIFICATION_KIND: nightly
|
||||||
|
NYM_PROJECT_NAME: "Notification test"
|
||||||
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||||
|
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||||
|
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||||
|
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||||
|
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||||
|
KEYBASE_NYM_CHANNEL: "ci-nightly"
|
||||||
|
uses: docker://keybaseio/client:stable-node
|
||||||
|
with:
|
||||||
|
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||||
+4
-1
@@ -6,15 +6,18 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
|
||||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
||||||
|
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
|
||||||
|
|
||||||
[#1541]: https://github.com/nymtech/nym/pull/1541
|
[#1541]: https://github.com/nymtech/nym/pull/1541
|
||||||
[#1558]: https://github.com/nymtech/nym/pull/1558
|
[#1558]: https://github.com/nymtech/nym/pull/1558
|
||||||
|
[#1577]: https://github.com/nymtech/nym/pull/1577
|
||||||
|
[#1591]: https://github.com/nymtech/nym/pull/1591
|
||||||
|
|
||||||
|
|
||||||
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
|
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
|
||||||
|
|||||||
Generated
+1076
-653
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@ members = [
|
|||||||
"common/client-libs/mixnet-client",
|
"common/client-libs/mixnet-client",
|
||||||
"common/client-libs/validator-client",
|
"common/client-libs/validator-client",
|
||||||
"common/coconut-interface",
|
"common/coconut-interface",
|
||||||
|
"common/commands",
|
||||||
"common/config",
|
"common/config",
|
||||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||||
"common/cosmwasm-smart-contracts/contracts-common",
|
"common/cosmwasm-smart-contracts/contracts-common",
|
||||||
@@ -69,6 +70,7 @@ members = [
|
|||||||
"service-providers/network-statistics",
|
"service-providers/network-statistics",
|
||||||
"validator-api",
|
"validator-api",
|
||||||
"validator-api/validator-api-requests",
|
"validator-api/validator-api-requests",
|
||||||
|
"tools/nym-cli",
|
||||||
"tools/ts-rs-cli"
|
"tools/ts-rs-cli"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ build-wallet:
|
|||||||
build-connect:
|
build-connect:
|
||||||
cargo build --manifest-path nym-connect/Cargo.toml --workspace
|
cargo build --manifest-path nym-connect/Cargo.toml --workspace
|
||||||
|
|
||||||
|
build-nym-cli:
|
||||||
|
cargo build --release --manifest-path tools/nym-cli/Cargo.toml
|
||||||
|
|
||||||
fmt-main:
|
fmt-main:
|
||||||
cargo fmt --all
|
cargo fmt --all
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ log = "0.4"
|
|||||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
sled = "0.34"
|
sled = "0.34"
|
||||||
|
thiserror = "1.0.34"
|
||||||
tokio = { version = "1.19.1", features = ["macros"] }
|
tokio = { version = "1.19.1", features = ["macros"] }
|
||||||
url = { version ="2.2", features = ["serde"] }
|
url = { version ="2.2", features = ["serde"] }
|
||||||
|
|
||||||
@@ -25,8 +26,10 @@ gateway-requests = { path = "../../gateway/gateway-requests" }
|
|||||||
nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" }
|
nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" }
|
||||||
nymsphinx = { path = "../../common/nymsphinx" }
|
nymsphinx = { path = "../../common/nymsphinx" }
|
||||||
pemstore = { path = "../../common/pemstore" }
|
pemstore = { path = "../../common/pemstore" }
|
||||||
|
task = { path = "../../common/task" }
|
||||||
topology = { path = "../../common/topology" }
|
topology = { path = "../../common/topology" }
|
||||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||||
|
tap = "1.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use nymsphinx::utils::sample_poisson_duration;
|
|||||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use task::ShutdownListener;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
@@ -48,6 +49,9 @@ where
|
|||||||
|
|
||||||
/// Accessor to the common instance of network topology.
|
/// Accessor to the common instance of network topology.
|
||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
|
|
||||||
|
/// Listen to shutdown signals.
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> Stream for LoopCoverTrafficStream<R>
|
impl<R> Stream for LoopCoverTrafficStream<R>
|
||||||
@@ -84,6 +88,7 @@ where
|
|||||||
// obviously when we finally make shared rng that is on 'higher' level, this should become
|
// obviously when we finally make shared rng that is on 'higher' level, this should become
|
||||||
// generic `R`
|
// generic `R`
|
||||||
impl LoopCoverTrafficStream<OsRng> {
|
impl LoopCoverTrafficStream<OsRng> {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ack_key: Arc<AckKey>,
|
ack_key: Arc<AckKey>,
|
||||||
average_ack_delay: time::Duration,
|
average_ack_delay: time::Duration,
|
||||||
@@ -92,6 +97,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
|||||||
mix_tx: BatchMixMessageSender,
|
mix_tx: BatchMixMessageSender,
|
||||||
our_full_destination: Recipient,
|
our_full_destination: Recipient,
|
||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rng = OsRng;
|
let rng = OsRng;
|
||||||
|
|
||||||
@@ -105,6 +111,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
|||||||
our_full_destination,
|
our_full_destination,
|
||||||
rng,
|
rng,
|
||||||
topology_access,
|
topology_access,
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,9 +166,25 @@ impl LoopCoverTrafficStream<OsRng> {
|
|||||||
self.average_cover_message_sending_delay,
|
self.average_cover_message_sending_delay,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
while self.next().await.is_some() {
|
let mut shutdown = self.shutdown.clone();
|
||||||
self.on_new_message().await;
|
while !shutdown.is_shutdown() {
|
||||||
|
tokio::select! {
|
||||||
|
biased;
|
||||||
|
_ = shutdown.recv() => {
|
||||||
|
log::trace!("LoopCoverTrafficStream: Received shutdown");
|
||||||
|
}
|
||||||
|
next = self.next() => {
|
||||||
|
if next.is_some() {
|
||||||
|
self.on_new_message().await;
|
||||||
|
} else {
|
||||||
|
log::trace!("LoopCoverTrafficStream: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
assert!(self.shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("LoopCoverTrafficStream: Exiting");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(mut self) -> JoinHandle<()> {
|
pub fn start(mut self) -> JoinHandle<()> {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use futures::StreamExt;
|
|||||||
use gateway_client::GatewayClient;
|
use gateway_client::GatewayClient;
|
||||||
use log::*;
|
use log::*;
|
||||||
use nymsphinx::forwarding::packet::MixPacket;
|
use nymsphinx::forwarding::packet::MixPacket;
|
||||||
|
use task::ShutdownListener;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
|
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
|
||||||
@@ -18,6 +19,7 @@ pub struct MixTrafficController {
|
|||||||
// later on gateway_client will need to be accessible by other entities
|
// later on gateway_client will need to be accessible by other entities
|
||||||
gateway_client: GatewayClient,
|
gateway_client: GatewayClient,
|
||||||
mix_rx: BatchMixMessageReceiver,
|
mix_rx: BatchMixMessageReceiver,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
|
|
||||||
// TODO: this is temporary work-around.
|
// TODO: this is temporary work-around.
|
||||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||||
@@ -28,10 +30,12 @@ impl MixTrafficController {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
mix_rx: BatchMixMessageReceiver,
|
mix_rx: BatchMixMessageReceiver,
|
||||||
gateway_client: GatewayClient,
|
gateway_client: GatewayClient,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> MixTrafficController {
|
) -> MixTrafficController {
|
||||||
MixTrafficController {
|
MixTrafficController {
|
||||||
gateway_client,
|
gateway_client,
|
||||||
mix_rx,
|
mix_rx,
|
||||||
|
shutdown,
|
||||||
consecutive_gateway_failure_count: 0,
|
consecutive_gateway_failure_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,9 +70,24 @@ impl MixTrafficController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self) {
|
pub async fn run(&mut self) {
|
||||||
while let Some(mix_packets) = self.mix_rx.next().await {
|
while !self.shutdown.is_shutdown() {
|
||||||
self.on_messages(mix_packets).await;
|
tokio::select! {
|
||||||
|
mix_packets = self.mix_rx.next() => match mix_packets {
|
||||||
|
Some(mix_packets) => {
|
||||||
|
self.on_messages(mix_packets).await;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log::trace!("MixTrafficController: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.shutdown.recv() => {
|
||||||
|
log::trace!("MixTrafficController: Received shutdown");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
assert!(self.shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("MixTrafficController: Exiting");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(mut self) -> JoinHandle<()> {
|
pub fn start(mut self) -> JoinHandle<()> {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
pub mod cover_traffic_stream;
|
pub mod cover_traffic_stream;
|
||||||
pub mod inbound_messages;
|
pub mod inbound_messages;
|
||||||
pub mod key_manager;
|
pub mod key_manager;
|
||||||
@@ -6,3 +8,10 @@ pub mod real_messages_control;
|
|||||||
pub mod received_buffer;
|
pub mod received_buffer;
|
||||||
pub mod reply_key_storage;
|
pub mod reply_key_storage;
|
||||||
pub mod topology_control;
|
pub mod topology_control;
|
||||||
|
|
||||||
|
// This is *NOT* used to signal shutdown.
|
||||||
|
// It's critical that we don't have any tasks finishing early, this is an additional safety check
|
||||||
|
// that tasks exiting are doing so because shutdown has been signalled, and no other reason.
|
||||||
|
// In particular for tasks that rely on their associated channel being closed to signal shutdown,
|
||||||
|
// and don't have access to a shutdown listener channel.
|
||||||
|
pub static SHUTDOWN_HAS_BEEN_SIGNALLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|||||||
+23
-5
@@ -10,6 +10,7 @@ use nymsphinx::{
|
|||||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use task::ShutdownListener;
|
||||||
|
|
||||||
/// Module responsible for listening for any data resembling acknowledgements from the network
|
/// Module responsible for listening for any data resembling acknowledgements from the network
|
||||||
/// and firing actions to remove them from the 'Pending' state.
|
/// and firing actions to remove them from the 'Pending' state.
|
||||||
@@ -17,6 +18,7 @@ pub(super) struct AcknowledgementListener {
|
|||||||
ack_key: Arc<AckKey>,
|
ack_key: Arc<AckKey>,
|
||||||
ack_receiver: AcknowledgementReceiver,
|
ack_receiver: AcknowledgementReceiver,
|
||||||
action_sender: ActionSender,
|
action_sender: ActionSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AcknowledgementListener {
|
impl AcknowledgementListener {
|
||||||
@@ -24,11 +26,13 @@ impl AcknowledgementListener {
|
|||||||
ack_key: Arc<AckKey>,
|
ack_key: Arc<AckKey>,
|
||||||
ack_receiver: AcknowledgementReceiver,
|
ack_receiver: AcknowledgementReceiver,
|
||||||
action_sender: ActionSender,
|
action_sender: ActionSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
AcknowledgementListener {
|
AcknowledgementListener {
|
||||||
ack_key,
|
ack_key,
|
||||||
ack_receiver,
|
ack_receiver,
|
||||||
action_sender,
|
action_sender,
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,12 +69,26 @@ impl AcknowledgementListener {
|
|||||||
|
|
||||||
pub(super) async fn run(&mut self) {
|
pub(super) async fn run(&mut self) {
|
||||||
debug!("Started AcknowledgementListener");
|
debug!("Started AcknowledgementListener");
|
||||||
while let Some(acks) = self.ack_receiver.next().await {
|
while !self.shutdown.is_shutdown() {
|
||||||
// realistically we would only be getting one ack at the time
|
tokio::select! {
|
||||||
for ack in acks {
|
acks = self.ack_receiver.next() => match acks {
|
||||||
self.on_ack(ack).await;
|
Some(acks) => {
|
||||||
|
// realistically we would only be getting one ack at the time
|
||||||
|
for ack in acks {
|
||||||
|
self.on_ack(ack).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log::trace!("AcknowledgementListener: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.shutdown.recv() => {
|
||||||
|
log::trace!("AcknowledgementListener: Received shutdown");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
error!("TODO: error msg. Or maybe panic?")
|
assert!(self.shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("AcknowledgementListener: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-6
@@ -12,6 +12,7 @@ use nymsphinx::Delay as SphinxDelay;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use task::ShutdownListener;
|
||||||
|
|
||||||
pub(crate) type ActionSender = UnboundedSender<Action>;
|
pub(crate) type ActionSender = UnboundedSender<Action>;
|
||||||
|
|
||||||
@@ -99,12 +100,16 @@ pub(super) struct ActionController {
|
|||||||
|
|
||||||
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
|
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
|
||||||
retransmission_sender: RetransmissionRequestSender,
|
retransmission_sender: RetransmissionRequestSender,
|
||||||
|
|
||||||
|
/// Listen for shutdown notifications
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActionController {
|
impl ActionController {
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
config: Config,
|
config: Config,
|
||||||
retransmission_sender: RetransmissionRequestSender,
|
retransmission_sender: RetransmissionRequestSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> (Self, ActionSender) {
|
) -> (Self, ActionSender) {
|
||||||
let (sender, receiver) = mpsc::unbounded();
|
let (sender, receiver) = mpsc::unbounded();
|
||||||
(
|
(
|
||||||
@@ -114,6 +119,7 @@ impl ActionController {
|
|||||||
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
||||||
incoming_actions: receiver,
|
incoming_actions: receiver,
|
||||||
retransmission_sender,
|
retransmission_sender,
|
||||||
|
shutdown,
|
||||||
},
|
},
|
||||||
sender,
|
sender,
|
||||||
)
|
)
|
||||||
@@ -246,14 +252,30 @@ impl ActionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn run(&mut self) {
|
pub(super) async fn run(&mut self) {
|
||||||
loop {
|
while !self.shutdown.is_shutdown() {
|
||||||
// at some point there will be a global shutdown signal here as the third option
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
// we NEVER expect for ANY sender to get dropped so unwrap here is fine
|
action = self.incoming_actions.next() => match action {
|
||||||
action = self.incoming_actions.next() => self.process_action(action.unwrap()),
|
Some(action) => self.process_action(action),
|
||||||
// pending ack queue Stream CANNOT return a `None` so unwrap here is fine
|
None => {
|
||||||
expired_ack = self.pending_acks_timers.next() => self.handle_expired_ack_timer(expired_ack.unwrap())
|
log::trace!(
|
||||||
|
"ActionController: Stopping since incoming actions channel closed"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expired_ack = self.pending_acks_timers.next() => match expired_ack {
|
||||||
|
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack),
|
||||||
|
None => {
|
||||||
|
log::trace!("ActionController: Stopping since ack channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.shutdown.recv() => {
|
||||||
|
log::trace!("ActionController: Received shutdown");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert!(self.shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("ActionController: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-3
@@ -16,6 +16,7 @@ use nymsphinx::preparer::MessagePreparer;
|
|||||||
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use task::ShutdownListener;
|
||||||
|
|
||||||
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
|
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
|
||||||
/// putting everything into sphinx packets, etc.
|
/// putting everything into sphinx packets, etc.
|
||||||
@@ -32,6 +33,7 @@ where
|
|||||||
real_message_sender: BatchRealMessageSender,
|
real_message_sender: BatchRealMessageSender,
|
||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
reply_key_storage: ReplyKeyStorage,
|
reply_key_storage: ReplyKeyStorage,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> InputMessageListener<R>
|
impl<R> InputMessageListener<R>
|
||||||
@@ -50,6 +52,7 @@ where
|
|||||||
real_message_sender: BatchRealMessageSender,
|
real_message_sender: BatchRealMessageSender,
|
||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
reply_key_storage: ReplyKeyStorage,
|
reply_key_storage: ReplyKeyStorage,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
InputMessageListener {
|
InputMessageListener {
|
||||||
ack_key,
|
ack_key,
|
||||||
@@ -60,6 +63,7 @@ where
|
|||||||
real_message_sender,
|
real_message_sender,
|
||||||
topology_access,
|
topology_access,
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +186,23 @@ where
|
|||||||
|
|
||||||
pub(super) async fn run(&mut self) {
|
pub(super) async fn run(&mut self) {
|
||||||
debug!("Started InputMessageListener");
|
debug!("Started InputMessageListener");
|
||||||
while let Some(input_msg) = self.input_receiver.next().await {
|
while !self.shutdown.is_shutdown() {
|
||||||
self.on_input_message(input_msg).await;
|
tokio::select! {
|
||||||
|
input_msg = self.input_receiver.next() => match input_msg {
|
||||||
|
Some(input_msg) => {
|
||||||
|
self.on_input_message(input_msg).await;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log::trace!("InputMessageListener: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.shutdown.recv() => {
|
||||||
|
log::trace!("InputMessageListener: Received shutdown");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
error!("TODO: error msg. Or maybe panic?")
|
assert!(self.shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("InputMessageListener: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-7
@@ -25,6 +25,7 @@ use std::{
|
|||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use task::ShutdownListener;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
mod acknowledgement_listener;
|
mod acknowledgement_listener;
|
||||||
@@ -152,6 +153,7 @@ impl<R> AcknowledgementController<R>
|
|||||||
where
|
where
|
||||||
R: 'static + CryptoRng + Rng + Clone + Send,
|
R: 'static + CryptoRng + Rng + Clone + Send,
|
||||||
{
|
{
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
config: Config,
|
config: Config,
|
||||||
rng: R,
|
rng: R,
|
||||||
@@ -160,13 +162,14 @@ where
|
|||||||
ack_recipient: Recipient,
|
ack_recipient: Recipient,
|
||||||
reply_key_storage: ReplyKeyStorage,
|
reply_key_storage: ReplyKeyStorage,
|
||||||
connectors: AcknowledgementControllerConnectors,
|
connectors: AcknowledgementControllerConnectors,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||||
|
|
||||||
let action_config =
|
let action_config =
|
||||||
action_controller::Config::new(config.ack_wait_addition, config.ack_wait_multiplier);
|
action_controller::Config::new(config.ack_wait_addition, config.ack_wait_multiplier);
|
||||||
let (action_controller, action_sender) =
|
let (action_controller, action_sender) =
|
||||||
ActionController::new(action_config, retransmission_tx);
|
ActionController::new(action_config, retransmission_tx, shutdown.clone());
|
||||||
|
|
||||||
let message_preparer = MessagePreparer::new(
|
let message_preparer = MessagePreparer::new(
|
||||||
rng,
|
rng,
|
||||||
@@ -180,6 +183,7 @@ where
|
|||||||
Arc::clone(&ack_key),
|
Arc::clone(&ack_key),
|
||||||
connectors.ack_receiver,
|
connectors.ack_receiver,
|
||||||
action_sender.clone(),
|
action_sender.clone(),
|
||||||
|
shutdown.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// will listen for any new messages from the client
|
// will listen for any new messages from the client
|
||||||
@@ -192,6 +196,7 @@ where
|
|||||||
connectors.real_message_sender.clone(),
|
connectors.real_message_sender.clone(),
|
||||||
topology_access.clone(),
|
topology_access.clone(),
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
|
shutdown.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// will listen for any ack timeouts and trigger retransmission
|
// will listen for any ack timeouts and trigger retransmission
|
||||||
@@ -203,12 +208,13 @@ where
|
|||||||
connectors.real_message_sender,
|
connectors.real_message_sender,
|
||||||
retransmission_rx,
|
retransmission_rx,
|
||||||
topology_access,
|
topology_access,
|
||||||
|
shutdown.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// will listen for events indicating the packet was sent through the network so that
|
// will listen for events indicating the packet was sent through the network so that
|
||||||
// the retransmission timer should be started.
|
// the retransmission timer should be started.
|
||||||
let sent_notification_listener =
|
let sent_notification_listener =
|
||||||
SentNotificationListener::new(connectors.sent_notifier, action_sender);
|
SentNotificationListener::new(connectors.sent_notifier, action_sender, shutdown);
|
||||||
|
|
||||||
AcknowledgementController {
|
AcknowledgementController {
|
||||||
acknowledgement_listener: Some(acknowledgement_listener),
|
acknowledgement_listener: Some(acknowledgement_listener),
|
||||||
@@ -232,27 +238,27 @@ where
|
|||||||
// graceful shutdowns.
|
// graceful shutdowns.
|
||||||
let ack_listener_fut = tokio::spawn(async move {
|
let ack_listener_fut = tokio::spawn(async move {
|
||||||
acknowledgement_listener.run().await;
|
acknowledgement_listener.run().await;
|
||||||
error!("The acknowledgement listener has finished execution!");
|
debug!("The acknowledgement listener has finished execution!");
|
||||||
acknowledgement_listener
|
acknowledgement_listener
|
||||||
});
|
});
|
||||||
let input_listener_fut = tokio::spawn(async move {
|
let input_listener_fut = tokio::spawn(async move {
|
||||||
input_message_listener.run().await;
|
input_message_listener.run().await;
|
||||||
error!("The input listener has finished execution!");
|
debug!("The input listener has finished execution!");
|
||||||
input_message_listener
|
input_message_listener
|
||||||
});
|
});
|
||||||
let retransmission_req_fut = tokio::spawn(async move {
|
let retransmission_req_fut = tokio::spawn(async move {
|
||||||
retransmission_request_listener.run().await;
|
retransmission_request_listener.run().await;
|
||||||
error!("The retransmission request listener has finished execution!");
|
debug!("The retransmission request listener has finished execution!");
|
||||||
retransmission_request_listener
|
retransmission_request_listener
|
||||||
});
|
});
|
||||||
let sent_notification_fut = tokio::spawn(async move {
|
let sent_notification_fut = tokio::spawn(async move {
|
||||||
sent_notification_listener.run().await;
|
sent_notification_listener.run().await;
|
||||||
error!("The sent notification listener has finished execution!");
|
debug!("The sent notification listener has finished execution!");
|
||||||
sent_notification_listener
|
sent_notification_listener
|
||||||
});
|
});
|
||||||
let action_controller_fut = tokio::spawn(async move {
|
let action_controller_fut = tokio::spawn(async move {
|
||||||
action_controller.run().await;
|
action_controller.run().await;
|
||||||
error!("The controller has finished execution!");
|
debug!("The controller has finished execution!");
|
||||||
action_controller
|
action_controller
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+21
-3
@@ -14,6 +14,7 @@ use nymsphinx::preparer::MessagePreparer;
|
|||||||
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
use task::ShutdownListener;
|
||||||
|
|
||||||
// responsible for packet retransmission upon fired timer
|
// responsible for packet retransmission upon fired timer
|
||||||
pub(super) struct RetransmissionRequestListener<R>
|
pub(super) struct RetransmissionRequestListener<R>
|
||||||
@@ -27,12 +28,14 @@ where
|
|||||||
real_message_sender: BatchRealMessageSender,
|
real_message_sender: BatchRealMessageSender,
|
||||||
request_receiver: RetransmissionRequestReceiver,
|
request_receiver: RetransmissionRequestReceiver,
|
||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> RetransmissionRequestListener<R>
|
impl<R> RetransmissionRequestListener<R>
|
||||||
where
|
where
|
||||||
R: CryptoRng + Rng,
|
R: CryptoRng + Rng,
|
||||||
{
|
{
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
ack_key: Arc<AckKey>,
|
ack_key: Arc<AckKey>,
|
||||||
ack_recipient: Recipient,
|
ack_recipient: Recipient,
|
||||||
@@ -41,6 +44,7 @@ where
|
|||||||
real_message_sender: BatchRealMessageSender,
|
real_message_sender: BatchRealMessageSender,
|
||||||
request_receiver: RetransmissionRequestReceiver,
|
request_receiver: RetransmissionRequestReceiver,
|
||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
RetransmissionRequestListener {
|
RetransmissionRequestListener {
|
||||||
ack_key,
|
ack_key,
|
||||||
@@ -50,6 +54,7 @@ where
|
|||||||
real_message_sender,
|
real_message_sender,
|
||||||
request_receiver,
|
request_receiver,
|
||||||
topology_access,
|
topology_access,
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,9 +126,22 @@ where
|
|||||||
|
|
||||||
pub(super) async fn run(&mut self) {
|
pub(super) async fn run(&mut self) {
|
||||||
debug!("Started RetransmissionRequestListener");
|
debug!("Started RetransmissionRequestListener");
|
||||||
while let Some(timed_out_ack) = self.request_receiver.next().await {
|
|
||||||
self.on_retransmission_request(timed_out_ack).await;
|
while !self.shutdown.is_shutdown() {
|
||||||
|
tokio::select! {
|
||||||
|
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
|
||||||
|
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack).await,
|
||||||
|
None => {
|
||||||
|
log::trace!("RetransmissionRequestListener: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.shutdown.recv() => {
|
||||||
|
log::trace!("RetransmissionRequestListener: Received shutdown");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
error!("TODO: error msg. Or maybe panic?")
|
assert!(self.shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("RetransmissionRequestListener: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-3
@@ -6,6 +6,7 @@ use super::SentPacketNotificationReceiver;
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use log::*;
|
use log::*;
|
||||||
use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||||
|
use task::ShutdownListener;
|
||||||
|
|
||||||
/// Module responsible for starting up retransmission timers.
|
/// Module responsible for starting up retransmission timers.
|
||||||
/// It is required because when we send our packet to the `real traffic stream` controlled
|
/// It is required because when we send our packet to the `real traffic stream` controlled
|
||||||
@@ -14,16 +15,19 @@ use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
|||||||
pub(super) struct SentNotificationListener {
|
pub(super) struct SentNotificationListener {
|
||||||
sent_notifier: SentPacketNotificationReceiver,
|
sent_notifier: SentPacketNotificationReceiver,
|
||||||
action_sender: ActionSender,
|
action_sender: ActionSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SentNotificationListener {
|
impl SentNotificationListener {
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
sent_notifier: SentPacketNotificationReceiver,
|
sent_notifier: SentPacketNotificationReceiver,
|
||||||
action_sender: ActionSender,
|
action_sender: ActionSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
SentNotificationListener {
|
SentNotificationListener {
|
||||||
sent_notifier,
|
sent_notifier,
|
||||||
action_sender,
|
action_sender,
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +48,23 @@ impl SentNotificationListener {
|
|||||||
|
|
||||||
pub(super) async fn run(&mut self) {
|
pub(super) async fn run(&mut self) {
|
||||||
debug!("Started SentNotificationListener");
|
debug!("Started SentNotificationListener");
|
||||||
while let Some(frag_id) = self.sent_notifier.next().await {
|
while !self.shutdown.is_shutdown() {
|
||||||
self.on_sent_message(frag_id).await;
|
tokio::select! {
|
||||||
|
frag_id = self.sent_notifier.next() => match frag_id {
|
||||||
|
Some(frag_id) => {
|
||||||
|
self.on_sent_message(frag_id).await;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::trace!("SentNotificationListener: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.shutdown.recv() => {
|
||||||
|
log::trace!("SentNotificationListener: Received shutdown");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
error!("TODO: error msg. Or maybe panic?")
|
assert!(self.shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("SentNotificationListener: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use nymsphinx::addressing::clients::Recipient;
|
|||||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use task::ShutdownListener;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
mod acknowledgement_control;
|
mod acknowledgement_control;
|
||||||
@@ -91,6 +92,7 @@ impl RealMessagesController<OsRng> {
|
|||||||
mix_sender: BatchMixMessageSender,
|
mix_sender: BatchMixMessageSender,
|
||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
reply_key_storage: ReplyKeyStorage,
|
reply_key_storage: ReplyKeyStorage,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rng = OsRng;
|
let rng = OsRng;
|
||||||
|
|
||||||
@@ -119,6 +121,7 @@ impl RealMessagesController<OsRng> {
|
|||||||
config.self_recipient,
|
config.self_recipient,
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
ack_controller_connectors,
|
ack_controller_connectors,
|
||||||
|
shutdown.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let out_queue_config = real_traffic_stream::Config::new(
|
let out_queue_config = real_traffic_stream::Config::new(
|
||||||
@@ -136,6 +139,7 @@ impl RealMessagesController<OsRng> {
|
|||||||
rng,
|
rng,
|
||||||
config.self_recipient,
|
config.self_recipient,
|
||||||
topology_access,
|
topology_access,
|
||||||
|
shutdown,
|
||||||
);
|
);
|
||||||
|
|
||||||
RealMessagesController {
|
RealMessagesController {
|
||||||
@@ -153,12 +157,12 @@ impl RealMessagesController<OsRng> {
|
|||||||
// graceful shutdowns.
|
// graceful shutdowns.
|
||||||
let out_queue_control_fut = tokio::spawn(async move {
|
let out_queue_control_fut = tokio::spawn(async move {
|
||||||
out_queue_control.run_out_queue_control().await;
|
out_queue_control.run_out_queue_control().await;
|
||||||
error!("The out queue controller has finished execution!");
|
debug!("The out queue controller has finished execution!");
|
||||||
out_queue_control
|
out_queue_control
|
||||||
});
|
});
|
||||||
let ack_control_fut = tokio::spawn(async move {
|
let ack_control_fut = tokio::spawn(async move {
|
||||||
ack_control.run().await;
|
ack_control.run().await;
|
||||||
error!("The acknowledgement controller has finished execution!");
|
debug!("The acknowledgement controller has finished execution!");
|
||||||
ack_control
|
ack_control
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use std::collections::VecDeque;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use task::ShutdownListener;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
/// Configurable parameters of the `OutQueueControl`
|
/// Configurable parameters of the `OutQueueControl`
|
||||||
@@ -83,6 +84,9 @@ where
|
|||||||
|
|
||||||
/// Buffer containing all real messages received. It is first exhausted before more are pulled.
|
/// Buffer containing all real messages received. It is first exhausted before more are pulled.
|
||||||
received_buffer: VecDeque<RealMessage>,
|
received_buffer: VecDeque<RealMessage>,
|
||||||
|
|
||||||
|
/// Listens for shutdown signals
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct RealMessage {
|
pub(crate) struct RealMessage {
|
||||||
@@ -174,6 +178,7 @@ where
|
|||||||
rng: R,
|
rng: R,
|
||||||
our_full_destination: Recipient,
|
our_full_destination: Recipient,
|
||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
OutQueueControl {
|
OutQueueControl {
|
||||||
config,
|
config,
|
||||||
@@ -186,6 +191,7 @@ where
|
|||||||
rng,
|
rng,
|
||||||
topology_access,
|
topology_access,
|
||||||
received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly
|
received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +245,15 @@ where
|
|||||||
// - we run out of memory
|
// - we run out of memory
|
||||||
// - the receiver channel is closed
|
// - the receiver channel is closed
|
||||||
// in either case there's no recovery and we can only panic
|
// in either case there's no recovery and we can only panic
|
||||||
self.mix_tx.unbounded_send(vec![next_message]).unwrap();
|
if let Err(err) = self.mix_tx.unbounded_send(vec![next_message]) {
|
||||||
|
if self.shutdown.is_shutdown_poll() {
|
||||||
|
log::info!("Failed to send (shutdown detected)");
|
||||||
|
} else {
|
||||||
|
// We don't try to limp along, panic to avoid continuing in a potentially
|
||||||
|
// inconsistent state
|
||||||
|
panic!("{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
|
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
|
||||||
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
|
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
|
||||||
@@ -257,9 +271,26 @@ where
|
|||||||
self.config.average_message_sending_delay,
|
self.config.average_message_sending_delay,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
while let Some(next_message) = self.next().await {
|
let mut shutdown = self.shutdown.clone();
|
||||||
self.on_message(next_message).await;
|
while !shutdown.is_shutdown() {
|
||||||
|
tokio::select! {
|
||||||
|
biased;
|
||||||
|
_ = shutdown.recv() => {
|
||||||
|
log::trace!("OutQueueControl: Received shutdown");
|
||||||
|
}
|
||||||
|
next_message = self.next() => match next_message {
|
||||||
|
Some(next_message) => {
|
||||||
|
self.on_message(next_message).await;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log::trace!("OutQueueControl: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
assert!(shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("OutQueueControl: Exiting");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_out_queue_control(&mut self) {
|
pub(crate) async fn run_out_queue_control(&mut self) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||||
|
use crate::client::SHUTDOWN_HAS_BEEN_SIGNALLED;
|
||||||
use crypto::asymmetric::encryption;
|
use crypto::asymmetric::encryption;
|
||||||
use crypto::symmetric::stream_cipher;
|
use crypto::symmetric::stream_cipher;
|
||||||
use crypto::Digest;
|
use crypto::Digest;
|
||||||
@@ -14,7 +15,9 @@ use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncr
|
|||||||
use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm};
|
use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm};
|
||||||
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use task::ShutdownListener;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
|
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
|
||||||
@@ -292,16 +295,27 @@ impl RequestReceiver {
|
|||||||
|
|
||||||
fn start(mut self) -> JoinHandle<()> {
|
fn start(mut self) -> JoinHandle<()> {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(request) = self.query_receiver.next().await {
|
loop {
|
||||||
match request {
|
tokio::select! {
|
||||||
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
|
request = self.query_receiver.next() => {
|
||||||
self.received_buffer.connect_sender(sender).await;
|
match request {
|
||||||
}
|
Some(ReceivedBufferMessage::ReceiverAnnounce(sender)) => {
|
||||||
ReceivedBufferMessage::ReceiverDisconnect => {
|
self.received_buffer.connect_sender(sender).await;
|
||||||
self.received_buffer.disconnect_sender().await
|
}
|
||||||
}
|
Some(ReceivedBufferMessage::ReceiverDisconnect) => {
|
||||||
}
|
self.received_buffer.disconnect_sender().await
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::trace!("RequestReceiver: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert!(SHUTDOWN_HAS_BEEN_SIGNALLED.load(Ordering::Relaxed));
|
||||||
|
log::debug!("RequestReceiver: Exiting");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,23 +323,41 @@ impl RequestReceiver {
|
|||||||
struct FragmentedMessageReceiver {
|
struct FragmentedMessageReceiver {
|
||||||
received_buffer: ReceivedMessagesBuffer,
|
received_buffer: ReceivedMessagesBuffer,
|
||||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FragmentedMessageReceiver {
|
impl FragmentedMessageReceiver {
|
||||||
fn new(
|
fn new(
|
||||||
received_buffer: ReceivedMessagesBuffer,
|
received_buffer: ReceivedMessagesBuffer,
|
||||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
FragmentedMessageReceiver {
|
FragmentedMessageReceiver {
|
||||||
received_buffer,
|
received_buffer,
|
||||||
mixnet_packet_receiver,
|
mixnet_packet_receiver,
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn start(mut self) -> JoinHandle<()> {
|
fn start(mut self) -> JoinHandle<()> {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
|
while !self.shutdown.is_shutdown() {
|
||||||
self.received_buffer.handle_new_received(new_messages).await;
|
tokio::select! {
|
||||||
|
new_messages = self.mixnet_packet_receiver.next() => match new_messages {
|
||||||
|
Some(new_messages) => {
|
||||||
|
self.received_buffer.handle_new_received(new_messages).await;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::trace!("FragmentedMessageReceiver: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.shutdown.recv() => {
|
||||||
|
log::trace!("FragmentedMessageReceiver: Received shutdown");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
assert!(self.shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("FragmentedMessageReceiver: Exiting");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,6 +373,7 @@ impl ReceivedMessagesBufferController {
|
|||||||
query_receiver: ReceivedBufferRequestReceiver,
|
query_receiver: ReceivedBufferRequestReceiver,
|
||||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||||
reply_key_storage: ReplyKeyStorage,
|
reply_key_storage: ReplyKeyStorage,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let received_buffer =
|
let received_buffer =
|
||||||
ReceivedMessagesBuffer::new(local_encryption_keypair, reply_key_storage);
|
ReceivedMessagesBuffer::new(local_encryption_keypair, reply_key_storage);
|
||||||
@@ -349,6 +382,7 @@ impl ReceivedMessagesBufferController {
|
|||||||
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
||||||
received_buffer.clone(),
|
received_buffer.clone(),
|
||||||
mixnet_packet_receiver,
|
mixnet_packet_receiver,
|
||||||
|
shutdown,
|
||||||
),
|
),
|
||||||
request_receiver: RequestReceiver::new(received_buffer, query_receiver),
|
request_receiver: RequestReceiver::new(received_buffer, query_receiver),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use std::ops::Deref;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time;
|
use std::time;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use task::ShutdownListener;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use topology::{nym_topology_from_bonds, NymTopology};
|
use topology::{nym_topology_from_bonds, NymTopology};
|
||||||
@@ -303,12 +304,20 @@ impl TopologyRefresher {
|
|||||||
self.topology_accessor.is_routable().await
|
self.topology_accessor.is_routable().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(mut self) -> JoinHandle<()> {
|
pub fn start(mut self, mut shutdown: ShutdownListener) -> JoinHandle<()> {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
while !shutdown.is_shutdown() {
|
||||||
tokio::time::sleep(self.refresh_rate).await;
|
tokio::select! {
|
||||||
self.refresh().await;
|
_ = tokio::time::sleep(self.refresh_rate) => {
|
||||||
|
self.refresh().await;
|
||||||
|
},
|
||||||
|
_ = shutdown.recv() => {
|
||||||
|
log::trace!("TopologyRefresher: Received shutdown");
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
assert!(shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("TopologyRefresher: Exiting");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||||
|
use gateway_client::error::GatewayClientError;
|
||||||
|
use validator_client::ValidatorClientError;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum ClientCoreError {
|
||||||
|
#[error("I/O error: {0}")]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
#[error("Gateway client error: {0}")]
|
||||||
|
GatewayClientError(#[from] GatewayClientError),
|
||||||
|
#[error("Ed25519 error: {0}")]
|
||||||
|
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
|
||||||
|
#[error("Validator client error: {0}")]
|
||||||
|
ValidatorClientError(#[from] ValidatorClientError),
|
||||||
|
|
||||||
|
#[error("No gateway with id: {0}")]
|
||||||
|
NoGatewayWithId(String),
|
||||||
|
#[error("No gateways on network")]
|
||||||
|
NoGatewaysOnNetwork,
|
||||||
|
#[error("List of validator apis is empty")]
|
||||||
|
ListOfValidatorApisIsEmpty,
|
||||||
|
#[error("Could not load existing gateway configuration: {0}")]
|
||||||
|
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
|
||||||
|
}
|
||||||
@@ -14,25 +14,27 @@ use nymsphinx::addressing::nodes::NodeIdentity;
|
|||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
use tap::TapFallible;
|
||||||
use topology::{filter::VersionFilterable, gateway};
|
use topology::{filter::VersionFilterable, gateway};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::key_manager::KeyManager,
|
client::key_manager::KeyManager,
|
||||||
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
|
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
|
||||||
|
error::ClientCoreError,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn query_gateway_details(
|
pub async fn query_gateway_details(
|
||||||
validator_servers: Vec<Url>,
|
validator_servers: Vec<Url>,
|
||||||
chosen_gateway_id: Option<&str>,
|
chosen_gateway_id: Option<&str>,
|
||||||
) -> gateway::Node {
|
) -> Result<gateway::Node, ClientCoreError> {
|
||||||
let validator_api = validator_servers
|
let validator_api = validator_servers
|
||||||
.choose(&mut thread_rng())
|
.choose(&mut thread_rng())
|
||||||
.expect("The list of validator apis is empty");
|
.ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?;
|
||||||
let validator_client = validator_client::ApiClient::new(validator_api.clone());
|
let validator_client = validator_client::ApiClient::new(validator_api.clone());
|
||||||
|
|
||||||
log::trace!("Fetching list of gateways from: {}", validator_api);
|
log::trace!("Fetching list of gateways from: {}", validator_api);
|
||||||
let gateways = validator_client.get_cached_gateways().await.unwrap();
|
let gateways = validator_client.get_cached_gateways().await?;
|
||||||
let valid_gateways = gateways
|
let valid_gateways = gateways
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|gateway| gateway.try_into().ok())
|
.filter_map(|gateway| gateway.try_into().ok())
|
||||||
@@ -47,38 +49,40 @@ pub async fn query_gateway_details(
|
|||||||
filtered_gateways
|
filtered_gateways
|
||||||
.iter()
|
.iter()
|
||||||
.find(|gateway| gateway.identity_key.to_base58_string() == gateway_id)
|
.find(|gateway| gateway.identity_key.to_base58_string() == gateway_id)
|
||||||
.expect(&*format!("no gateway with id {} exists!", gateway_id))
|
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string()))
|
||||||
.clone()
|
.cloned()
|
||||||
} else {
|
} else {
|
||||||
filtered_gateways
|
filtered_gateways
|
||||||
.choose(&mut rand::thread_rng())
|
.choose(&mut rand::thread_rng())
|
||||||
.expect("there are no gateways on the network!")
|
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
|
||||||
.clone()
|
.cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register_with_gateway_and_store_keys<T>(
|
pub async fn register_with_gateway_and_store_keys<T>(
|
||||||
gateway_details: gateway::Node,
|
gateway_details: gateway::Node,
|
||||||
config: &Config<T>,
|
config: &Config<T>,
|
||||||
) where
|
) -> Result<(), ClientCoreError>
|
||||||
|
where
|
||||||
T: NymConfig,
|
T: NymConfig,
|
||||||
{
|
{
|
||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
let mut key_manager = KeyManager::new(&mut rng);
|
let mut key_manager = KeyManager::new(&mut rng);
|
||||||
|
|
||||||
let shared_keys = register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
|
let shared_keys =
|
||||||
|
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await?;
|
||||||
key_manager.insert_gateway_shared_key(shared_keys);
|
key_manager.insert_gateway_shared_key(shared_keys);
|
||||||
|
|
||||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||||
key_manager
|
Ok(key_manager
|
||||||
.store_keys(&pathfinder)
|
.store_keys(&pathfinder)
|
||||||
.expect("Failed to generated keys");
|
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn register_with_gateway(
|
async fn register_with_gateway(
|
||||||
gateway: &gateway::Node,
|
gateway: &gateway::Node,
|
||||||
our_identity: Arc<identity::KeyPair>,
|
our_identity: Arc<identity::KeyPair>,
|
||||||
) -> Arc<SharedKeys> {
|
) -> Result<Arc<SharedKeys>, ClientCoreError> {
|
||||||
let timeout = Duration::from_millis(1500);
|
let timeout = Duration::from_millis(1500);
|
||||||
let mut gateway_client = GatewayClient::new_init(
|
let mut gateway_client = GatewayClient::new_init(
|
||||||
gateway.clients_address(),
|
gateway.clients_address(),
|
||||||
@@ -86,52 +90,59 @@ async fn register_with_gateway(
|
|||||||
gateway.owner.clone(),
|
gateway.owner.clone(),
|
||||||
our_identity.clone(),
|
our_identity.clone(),
|
||||||
timeout,
|
timeout,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
gateway_client
|
gateway_client
|
||||||
.establish_connection()
|
.establish_connection()
|
||||||
.await
|
.await
|
||||||
.expect("failed to establish connection with the gateway!");
|
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
|
||||||
gateway_client
|
let shared_keys = gateway_client
|
||||||
.perform_initial_authentication()
|
.perform_initial_authentication()
|
||||||
.await
|
.await
|
||||||
.expect("failed to register with the gateway!")
|
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
|
||||||
|
Ok(shared_keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_address<T>(config: &Config<T>)
|
pub fn show_address<T>(config: &Config<T>) -> Result<(), ClientCoreError>
|
||||||
where
|
where
|
||||||
T: config::NymConfig,
|
T: config::NymConfig,
|
||||||
{
|
{
|
||||||
fn load_identity_keys(pathfinder: &ClientKeyPathfinder) -> identity::KeyPair {
|
fn load_identity_keys(
|
||||||
|
pathfinder: &ClientKeyPathfinder,
|
||||||
|
) -> Result<identity::KeyPair, ClientCoreError> {
|
||||||
let identity_keypair: identity::KeyPair =
|
let identity_keypair: identity::KeyPair =
|
||||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||||
pathfinder.private_identity_key().to_owned(),
|
pathfinder.private_identity_key().to_owned(),
|
||||||
pathfinder.public_identity_key().to_owned(),
|
pathfinder.public_identity_key().to_owned(),
|
||||||
))
|
))
|
||||||
.expect("Failed to read stored identity key files");
|
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
|
||||||
identity_keypair
|
Ok(identity_keypair)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_sphinx_keys(pathfinder: &ClientKeyPathfinder) -> encryption::KeyPair {
|
fn load_sphinx_keys(
|
||||||
|
pathfinder: &ClientKeyPathfinder,
|
||||||
|
) -> Result<encryption::KeyPair, ClientCoreError> {
|
||||||
let sphinx_keypair: encryption::KeyPair =
|
let sphinx_keypair: encryption::KeyPair =
|
||||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||||
pathfinder.private_encryption_key().to_owned(),
|
pathfinder.private_encryption_key().to_owned(),
|
||||||
pathfinder.public_encryption_key().to_owned(),
|
pathfinder.public_encryption_key().to_owned(),
|
||||||
))
|
))
|
||||||
.expect("Failed to read stored sphinx key files");
|
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
|
||||||
sphinx_keypair
|
Ok(sphinx_keypair)
|
||||||
}
|
}
|
||||||
|
|
||||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||||
let identity_keypair = load_identity_keys(&pathfinder);
|
let identity_keypair = load_identity_keys(&pathfinder)?;
|
||||||
let sphinx_keypair = load_sphinx_keys(&pathfinder);
|
let sphinx_keypair = load_sphinx_keys(&pathfinder)?;
|
||||||
|
|
||||||
let client_recipient = Recipient::new(
|
let client_recipient = Recipient::new(
|
||||||
*identity_keypair.public_key(),
|
*identity_keypair.public_key(),
|
||||||
*sphinx_keypair.public_key(),
|
*sphinx_keypair.public_key(),
|
||||||
// TODO: below only works under assumption that gateway address == gateway id
|
// TODO: below only works under assumption that gateway address == gateway id
|
||||||
// (which currently is true)
|
// (which currently is true)
|
||||||
NodeIdentity::from_base58_string(config.get_gateway_id()).unwrap(),
|
NodeIdentity::from_base58_string(config.get_gateway_id())?,
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("\nThe address of this client is: {}", client_recipient);
|
println!("\nThe address of this client is: {}", client_recipient);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod error;
|
||||||
pub mod init;
|
pub mod init;
|
||||||
|
|||||||
@@ -33,19 +33,20 @@ tokio-tungstenite = "0.14" # websocket
|
|||||||
## internal
|
## internal
|
||||||
client-core = { path = "../client-core" }
|
client-core = { path = "../client-core" }
|
||||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||||
credentials = { path = "../../common/credentials", optional = true }
|
|
||||||
credential-storage = { path = "../../common/credential-storage" }
|
|
||||||
config = { path = "../../common/config" }
|
config = { path = "../../common/config" }
|
||||||
|
credential-storage = { path = "../../common/credential-storage" }
|
||||||
|
credentials = { path = "../../common/credentials", optional = true }
|
||||||
crypto = { path = "../../common/crypto" }
|
crypto = { path = "../../common/crypto" }
|
||||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||||
|
network-defaults = { path = "../../common/network-defaults" }
|
||||||
nymsphinx = { path = "../../common/nymsphinx" }
|
nymsphinx = { path = "../../common/nymsphinx" }
|
||||||
pemstore = { path = "../../common/pemstore" }
|
pemstore = { path = "../../common/pemstore" }
|
||||||
|
task = { path = "../../common/task" }
|
||||||
topology = { path = "../../common/topology" }
|
topology = { path = "../../common/topology" }
|
||||||
websocket-requests = { path = "websocket-requests" }
|
|
||||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||||
version-checker = { path = "../../common/version-checker" }
|
version-checker = { path = "../../common/version-checker" }
|
||||||
network-defaults = { path = "../../common/network-defaults" }
|
websocket-requests = { path = "websocket-requests" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
|
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ use nymsphinx::addressing::clients::Recipient;
|
|||||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||||
use nymsphinx::anonymous_replies::ReplySurb;
|
use nymsphinx::anonymous_replies::ReplySurb;
|
||||||
use nymsphinx::receiver::ReconstructedMessage;
|
use nymsphinx::receiver::ReconstructedMessage;
|
||||||
|
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
|
||||||
|
|
||||||
use crate::client::config::{Config, SocketType};
|
use crate::client::config::{Config, SocketType};
|
||||||
use crate::websocket;
|
use crate::websocket;
|
||||||
@@ -85,6 +86,7 @@ impl NymClient {
|
|||||||
&self,
|
&self,
|
||||||
topology_accessor: TopologyAccessor,
|
topology_accessor: TopologyAccessor,
|
||||||
mix_tx: BatchMixMessageSender,
|
mix_tx: BatchMixMessageSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) {
|
) {
|
||||||
info!("Starting loop cover traffic stream...");
|
info!("Starting loop cover traffic stream...");
|
||||||
|
|
||||||
@@ -98,6 +100,7 @@ impl NymClient {
|
|||||||
mix_tx,
|
mix_tx,
|
||||||
self.as_mix_recipient(),
|
self.as_mix_recipient(),
|
||||||
topology_accessor,
|
topology_accessor,
|
||||||
|
shutdown,
|
||||||
)
|
)
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
@@ -109,6 +112,7 @@ impl NymClient {
|
|||||||
ack_receiver: AcknowledgementReceiver,
|
ack_receiver: AcknowledgementReceiver,
|
||||||
input_receiver: InputMessageReceiver,
|
input_receiver: InputMessageReceiver,
|
||||||
mix_sender: BatchMixMessageSender,
|
mix_sender: BatchMixMessageSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) {
|
) {
|
||||||
let controller_config = real_messages_control::Config::new(
|
let controller_config = real_messages_control::Config::new(
|
||||||
self.key_manager.ack_key(),
|
self.key_manager.ack_key(),
|
||||||
@@ -129,6 +133,7 @@ impl NymClient {
|
|||||||
mix_sender,
|
mix_sender,
|
||||||
topology_accessor,
|
topology_accessor,
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
|
shutdown,
|
||||||
)
|
)
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
@@ -140,6 +145,7 @@ impl NymClient {
|
|||||||
query_receiver: ReceivedBufferRequestReceiver,
|
query_receiver: ReceivedBufferRequestReceiver,
|
||||||
mixnet_receiver: MixnetMessageReceiver,
|
mixnet_receiver: MixnetMessageReceiver,
|
||||||
reply_key_storage: ReplyKeyStorage,
|
reply_key_storage: ReplyKeyStorage,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) {
|
) {
|
||||||
info!("Starting received messages buffer controller...");
|
info!("Starting received messages buffer controller...");
|
||||||
ReceivedMessagesBufferController::new(
|
ReceivedMessagesBufferController::new(
|
||||||
@@ -147,6 +153,7 @@ impl NymClient {
|
|||||||
query_receiver,
|
query_receiver,
|
||||||
mixnet_receiver,
|
mixnet_receiver,
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
|
shutdown,
|
||||||
)
|
)
|
||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
@@ -155,6 +162,7 @@ impl NymClient {
|
|||||||
&mut self,
|
&mut self,
|
||||||
mixnet_message_sender: MixnetMessageSender,
|
mixnet_message_sender: MixnetMessageSender,
|
||||||
ack_sender: AcknowledgementSender,
|
ack_sender: AcknowledgementSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> GatewayClient {
|
) -> GatewayClient {
|
||||||
let gateway_id = self.config.get_base().get_gateway_id();
|
let gateway_id = self.config.get_base().get_gateway_id();
|
||||||
if gateway_id.is_empty() {
|
if gateway_id.is_empty() {
|
||||||
@@ -197,6 +205,7 @@ impl NymClient {
|
|||||||
ack_sender,
|
ack_sender,
|
||||||
self.config.get_base().get_gateway_response_timeout(),
|
self.config.get_base().get_gateway_response_timeout(),
|
||||||
Some(bandwidth_controller),
|
Some(bandwidth_controller),
|
||||||
|
Some(shutdown),
|
||||||
);
|
);
|
||||||
|
|
||||||
gateway_client
|
gateway_client
|
||||||
@@ -212,7 +221,11 @@ impl NymClient {
|
|||||||
|
|
||||||
// future responsible for periodically polling directory server and updating
|
// future responsible for periodically polling directory server and updating
|
||||||
// the current global view of topology
|
// the current global view of topology
|
||||||
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
|
async fn start_topology_refresher(
|
||||||
|
&mut self,
|
||||||
|
topology_accessor: TopologyAccessor,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
|
) {
|
||||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||||
self.config.get_base().get_validator_api_endpoints(),
|
self.config.get_base().get_validator_api_endpoints(),
|
||||||
self.config.get_base().get_topology_refresh_rate(),
|
self.config.get_base().get_topology_refresh_rate(),
|
||||||
@@ -234,7 +247,7 @@ impl NymClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("Starting topology refresher...");
|
info!("Starting topology refresher...");
|
||||||
topology_refresher.start();
|
topology_refresher.start(shutdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||||
@@ -245,9 +258,10 @@ impl NymClient {
|
|||||||
&mut self,
|
&mut self,
|
||||||
mix_rx: BatchMixMessageReceiver,
|
mix_rx: BatchMixMessageReceiver,
|
||||||
gateway_client: GatewayClient,
|
gateway_client: GatewayClient,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) {
|
) {
|
||||||
info!("Starting mix traffic controller...");
|
info!("Starting mix traffic controller...");
|
||||||
MixTrafficController::new(mix_rx, gateway_client).start();
|
MixTrafficController::new(mix_rx, gateway_client, shutdown).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_websocket_listener(
|
fn start_websocket_listener(
|
||||||
@@ -308,20 +322,26 @@ impl NymClient {
|
|||||||
|
|
||||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||||
pub async fn run_forever(&mut self) {
|
pub async fn run_forever(&mut self) {
|
||||||
self.start().await;
|
let shutdown = self.start().await;
|
||||||
if let Err(e) = tokio::signal::ctrl_c().await {
|
wait_for_signal().await;
|
||||||
error!(
|
|
||||||
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Received SIGINT - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
|
"Received signal - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
log::info!("Sending shutdown");
|
||||||
|
shutdown.signal_shutdown().ok();
|
||||||
|
|
||||||
|
// Some of these components have shutdown signalling implemented as part of socks5 work,
|
||||||
|
// but since it's not fully implemented (yet) for all the components of the native client,
|
||||||
|
// we don't try to wait and instead just stop immediately.
|
||||||
|
//log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
|
||||||
|
//shutdown.wait_for_shutdown().await;
|
||||||
|
|
||||||
|
log::info!("Stopping nym-client");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(&mut self) {
|
pub async fn start(&mut self) -> ShutdownNotifier {
|
||||||
info!("Starting nym client");
|
info!("Starting nym client");
|
||||||
// channels for inter-component communication
|
// channels for inter-component communication
|
||||||
// TODO: make the channels be internally created by the relevant components
|
// TODO: make the channels be internally created by the relevant components
|
||||||
@@ -351,30 +371,43 @@ impl NymClient {
|
|||||||
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
|
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
|
||||||
.expect("Failed to load reply key storage!");
|
.expect("Failed to load reply key storage!");
|
||||||
|
|
||||||
|
// Shutdown notifier for signalling tasks to stop
|
||||||
|
let shutdown = ShutdownNotifier::default();
|
||||||
|
|
||||||
// the components are started in very specific order. Unless you know what you are doing,
|
// the components are started in very specific order. Unless you know what you are doing,
|
||||||
// do not change that.
|
// do not change that.
|
||||||
self.start_topology_refresher(shared_topology_accessor.clone())
|
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
|
||||||
.await;
|
.await;
|
||||||
self.start_received_messages_buffer_controller(
|
self.start_received_messages_buffer_controller(
|
||||||
received_buffer_request_receiver,
|
received_buffer_request_receiver,
|
||||||
mixnet_messages_receiver,
|
mixnet_messages_receiver,
|
||||||
reply_key_storage.clone(),
|
reply_key_storage.clone(),
|
||||||
|
shutdown.subscribe(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let gateway_client = self
|
let gateway_client = self
|
||||||
.start_gateway_client(mixnet_messages_sender, ack_sender)
|
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
|
self.start_mix_traffic_controller(
|
||||||
|
sphinx_message_receiver,
|
||||||
|
gateway_client,
|
||||||
|
shutdown.subscribe(),
|
||||||
|
);
|
||||||
self.start_real_traffic_controller(
|
self.start_real_traffic_controller(
|
||||||
shared_topology_accessor.clone(),
|
shared_topology_accessor.clone(),
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
ack_receiver,
|
ack_receiver,
|
||||||
input_receiver,
|
input_receiver,
|
||||||
sphinx_message_sender.clone(),
|
sphinx_message_sender.clone(),
|
||||||
|
shutdown.subscribe(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
|
self.start_cover_traffic_stream(
|
||||||
|
shared_topology_accessor,
|
||||||
|
sphinx_message_sender,
|
||||||
|
shutdown.subscribe(),
|
||||||
|
);
|
||||||
|
|
||||||
match self.config.get_socket_type() {
|
match self.config.get_socket_type() {
|
||||||
SocketType::WebSocket => {
|
SocketType::WebSocket => {
|
||||||
@@ -399,5 +432,7 @@ impl NymClient {
|
|||||||
|
|
||||||
info!("Client startup finished!");
|
info!("Client startup finished!");
|
||||||
info!("The address of this client is: {}", self.as_mix_recipient());
|
info!("The address of this client is: {}", self.as_mix_recipient());
|
||||||
|
|
||||||
|
shutdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use client_core::config::GatewayEndpoint;
|
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
|
||||||
use config::NymConfig;
|
use config::NymConfig;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -120,7 +120,12 @@ pub(crate) async fn execute(args: &Init) {
|
|||||||
let override_config_fields = OverrideConfig::from(args.clone());
|
let override_config_fields = OverrideConfig::from(args.clone());
|
||||||
config = override_config(config, override_config_fields);
|
config = override_config(config, override_config_fields);
|
||||||
|
|
||||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
|
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
eprintln!("Failed to setup gateway\nError: {err}");
|
||||||
|
std::process::exit(1)
|
||||||
|
});
|
||||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||||
|
|
||||||
let config_save_location = config.get_config_file_save_location();
|
let config_save_location = config.get_config_file_save_location();
|
||||||
@@ -138,7 +143,10 @@ pub(crate) async fn execute(args: &Init) {
|
|||||||
);
|
);
|
||||||
println!("Client configuration completed.");
|
println!("Client configuration completed.");
|
||||||
|
|
||||||
client_core::init::show_address(config.get_base());
|
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
|
||||||
|
eprintln!("Failed to show address\nError: {err}");
|
||||||
|
std::process::exit(1)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_gateway(
|
async fn setup_gateway(
|
||||||
@@ -146,7 +154,7 @@ async fn setup_gateway(
|
|||||||
register: bool,
|
register: bool,
|
||||||
user_chosen_gateway_id: Option<&str>,
|
user_chosen_gateway_id: Option<&str>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> GatewayEndpoint {
|
) -> Result<GatewayEndpoint, ClientCoreError> {
|
||||||
if register {
|
if register {
|
||||||
// Get the gateway details by querying the validator-api. Either pick one at random or use
|
// Get the gateway details by querying the validator-api. Either pick one at random or use
|
||||||
// the chosen one if it's among the available ones.
|
// the chosen one if it's among the available ones.
|
||||||
@@ -155,16 +163,16 @@ async fn setup_gateway(
|
|||||||
config.get_base().get_validator_api_endpoints(),
|
config.get_base().get_validator_api_endpoints(),
|
||||||
user_chosen_gateway_id,
|
user_chosen_gateway_id,
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
log::debug!("Querying gateway gives: {}", gateway);
|
log::debug!("Querying gateway gives: {}", gateway);
|
||||||
|
|
||||||
// Registering with gateway by setting up and writing shared keys to disk
|
// Registering with gateway by setting up and writing shared keys to disk
|
||||||
log::trace!("Registering gateway");
|
log::trace!("Registering gateway");
|
||||||
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
||||||
.await;
|
.await?;
|
||||||
println!("Saved all generated keys");
|
println!("Saved all generated keys");
|
||||||
|
|
||||||
gateway.into()
|
Ok(gateway.into())
|
||||||
} else if user_chosen_gateway_id.is_some() {
|
} else if user_chosen_gateway_id.is_some() {
|
||||||
// Just set the config, don't register or create any keys
|
// Just set the config, don't register or create any keys
|
||||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||||
@@ -174,22 +182,22 @@ async fn setup_gateway(
|
|||||||
config.get_base().get_validator_api_endpoints(),
|
config.get_base().get_validator_api_endpoints(),
|
||||||
user_chosen_gateway_id,
|
user_chosen_gateway_id,
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
log::debug!("Querying gateway gives: {}", gateway);
|
log::debug!("Querying gateway gives: {}", gateway);
|
||||||
gateway.into()
|
Ok(gateway.into())
|
||||||
} else {
|
} else {
|
||||||
println!("Not registering gateway, will reuse existing config and keys");
|
println!("Not registering gateway, will reuse existing config and keys");
|
||||||
match Config::load_from_file(Some(id)) {
|
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
|
||||||
Ok(existing_config) => existing_config.get_base().get_gateway_endpoint().clone(),
|
log::error!(
|
||||||
Err(err) => {
|
"Unable to configure gateway: {err}. \n
|
||||||
panic!(
|
Seems like the client was already initialized but it was not possible to read \
|
||||||
"Unable to configure gateway: {err}. \n
|
the existing configuration file. \n
|
||||||
Seems like the client was already initialized but it was not possible to read \
|
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||||
the existing configuration file. \n
|
removing the existing configuration and starting over."
|
||||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
);
|
||||||
removing the existing configuration and starting over."
|
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||||
)
|
})?;
|
||||||
}
|
|
||||||
}
|
Ok(existing_config.get_base().get_gateway_endpoint().clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,21 +26,22 @@ url = "2.2"
|
|||||||
# internal
|
# internal
|
||||||
client-core = { path = "../client-core" }
|
client-core = { path = "../client-core" }
|
||||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||||
credentials = { path = "../../common/credentials", optional = true }
|
|
||||||
credential-storage = { path = "../../common/credential-storage" }
|
|
||||||
config = { path = "../../common/config" }
|
config = { path = "../../common/config" }
|
||||||
|
credential-storage = { path = "../../common/credential-storage" }
|
||||||
|
credentials = { path = "../../common/credentials", optional = true }
|
||||||
crypto = { path = "../../common/crypto" }
|
crypto = { path = "../../common/crypto" }
|
||||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||||
|
network-defaults = { path = "../../common/network-defaults" }
|
||||||
nymsphinx = { path = "../../common/nymsphinx" }
|
nymsphinx = { path = "../../common/nymsphinx" }
|
||||||
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
|
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
|
||||||
socks5-requests = { path = "../../common/socks5/requests" }
|
|
||||||
topology = { path = "../../common/topology" }
|
|
||||||
pemstore = { path = "../../common/pemstore" }
|
pemstore = { path = "../../common/pemstore" }
|
||||||
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
|
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
|
||||||
|
socks5-requests = { path = "../../common/socks5/requests" }
|
||||||
|
task = { path = "../../common/task" }
|
||||||
|
topology = { path = "../../common/topology" }
|
||||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||||
version-checker = { path = "../../common/version-checker" }
|
version-checker = { path = "../../common/version-checker" }
|
||||||
network-defaults = { path = "../../common/network-defaults" }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"]
|
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"]
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||||
use client_core::client::inbound_messages::{
|
use client_core::client::inbound_messages::{
|
||||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||||
@@ -29,6 +31,7 @@ use gateway_client::{
|
|||||||
use log::*;
|
use log::*;
|
||||||
use nymsphinx::addressing::clients::Recipient;
|
use nymsphinx::addressing::clients::Recipient;
|
||||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||||
|
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
|
||||||
|
|
||||||
use crate::client::config::Config;
|
use crate::client::config::Config;
|
||||||
use crate::socks::{
|
use crate::socks::{
|
||||||
@@ -84,6 +87,7 @@ impl NymClient {
|
|||||||
&self,
|
&self,
|
||||||
topology_accessor: TopologyAccessor,
|
topology_accessor: TopologyAccessor,
|
||||||
mix_tx: BatchMixMessageSender,
|
mix_tx: BatchMixMessageSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) {
|
) {
|
||||||
info!("Starting loop cover traffic stream...");
|
info!("Starting loop cover traffic stream...");
|
||||||
|
|
||||||
@@ -97,6 +101,7 @@ impl NymClient {
|
|||||||
mix_tx,
|
mix_tx,
|
||||||
self.as_mix_recipient(),
|
self.as_mix_recipient(),
|
||||||
topology_accessor,
|
topology_accessor,
|
||||||
|
shutdown,
|
||||||
)
|
)
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
@@ -108,6 +113,7 @@ impl NymClient {
|
|||||||
ack_receiver: AcknowledgementReceiver,
|
ack_receiver: AcknowledgementReceiver,
|
||||||
input_receiver: InputMessageReceiver,
|
input_receiver: InputMessageReceiver,
|
||||||
mix_sender: BatchMixMessageSender,
|
mix_sender: BatchMixMessageSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) {
|
) {
|
||||||
let controller_config = client_core::client::real_messages_control::Config::new(
|
let controller_config = client_core::client::real_messages_control::Config::new(
|
||||||
self.key_manager.ack_key(),
|
self.key_manager.ack_key(),
|
||||||
@@ -128,6 +134,7 @@ impl NymClient {
|
|||||||
mix_sender,
|
mix_sender,
|
||||||
topology_accessor,
|
topology_accessor,
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
|
shutdown,
|
||||||
)
|
)
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
@@ -139,6 +146,7 @@ impl NymClient {
|
|||||||
query_receiver: ReceivedBufferRequestReceiver,
|
query_receiver: ReceivedBufferRequestReceiver,
|
||||||
mixnet_receiver: MixnetMessageReceiver,
|
mixnet_receiver: MixnetMessageReceiver,
|
||||||
reply_key_storage: ReplyKeyStorage,
|
reply_key_storage: ReplyKeyStorage,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) {
|
) {
|
||||||
info!("Starting received messages buffer controller...");
|
info!("Starting received messages buffer controller...");
|
||||||
ReceivedMessagesBufferController::new(
|
ReceivedMessagesBufferController::new(
|
||||||
@@ -146,6 +154,7 @@ impl NymClient {
|
|||||||
query_receiver,
|
query_receiver,
|
||||||
mixnet_receiver,
|
mixnet_receiver,
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
|
shutdown,
|
||||||
)
|
)
|
||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
@@ -154,6 +163,7 @@ impl NymClient {
|
|||||||
&mut self,
|
&mut self,
|
||||||
mixnet_message_sender: MixnetMessageSender,
|
mixnet_message_sender: MixnetMessageSender,
|
||||||
ack_sender: AcknowledgementSender,
|
ack_sender: AcknowledgementSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> GatewayClient {
|
) -> GatewayClient {
|
||||||
let gateway_id = self.config.get_base().get_gateway_id();
|
let gateway_id = self.config.get_base().get_gateway_id();
|
||||||
if gateway_id.is_empty() {
|
if gateway_id.is_empty() {
|
||||||
@@ -196,6 +206,7 @@ impl NymClient {
|
|||||||
ack_sender,
|
ack_sender,
|
||||||
self.config.get_base().get_gateway_response_timeout(),
|
self.config.get_base().get_gateway_response_timeout(),
|
||||||
Some(bandwidth_controller),
|
Some(bandwidth_controller),
|
||||||
|
Some(shutdown),
|
||||||
);
|
);
|
||||||
|
|
||||||
gateway_client
|
gateway_client
|
||||||
@@ -211,7 +222,11 @@ impl NymClient {
|
|||||||
|
|
||||||
// future responsible for periodically polling directory server and updating
|
// future responsible for periodically polling directory server and updating
|
||||||
// the current global view of topology
|
// the current global view of topology
|
||||||
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
|
async fn start_topology_refresher(
|
||||||
|
&mut self,
|
||||||
|
topology_accessor: TopologyAccessor,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
|
) {
|
||||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||||
self.config.get_base().get_validator_api_endpoints(),
|
self.config.get_base().get_validator_api_endpoints(),
|
||||||
self.config.get_base().get_topology_refresh_rate(),
|
self.config.get_base().get_topology_refresh_rate(),
|
||||||
@@ -233,7 +248,7 @@ impl NymClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("Starting topology refresher...");
|
info!("Starting topology refresher...");
|
||||||
topology_refresher.start();
|
topology_refresher.start(shutdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||||
@@ -244,15 +259,17 @@ impl NymClient {
|
|||||||
&mut self,
|
&mut self,
|
||||||
mix_rx: BatchMixMessageReceiver,
|
mix_rx: BatchMixMessageReceiver,
|
||||||
gateway_client: GatewayClient,
|
gateway_client: GatewayClient,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) {
|
) {
|
||||||
info!("Starting mix traffic controller...");
|
info!("Starting mix traffic controller...");
|
||||||
MixTrafficController::new(mix_rx, gateway_client).start();
|
MixTrafficController::new(mix_rx, gateway_client, shutdown).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_socks5_listener(
|
fn start_socks5_listener(
|
||||||
&self,
|
&self,
|
||||||
buffer_requester: ReceivedBufferRequestSender,
|
buffer_requester: ReceivedBufferRequestSender,
|
||||||
msg_input: InputMessageSender,
|
msg_input: InputMessageSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) {
|
) {
|
||||||
info!("Starting socks5 listener...");
|
info!("Starting socks5 listener...");
|
||||||
let auth_methods = vec![AuthenticationMethods::NoAuth as u8];
|
let auth_methods = vec![AuthenticationMethods::NoAuth as u8];
|
||||||
@@ -264,43 +281,57 @@ impl NymClient {
|
|||||||
authenticator,
|
authenticator,
|
||||||
self.config.get_provider_mix_address(),
|
self.config.get_provider_mix_address(),
|
||||||
self.as_mix_recipient(),
|
self.as_mix_recipient(),
|
||||||
|
shutdown,
|
||||||
);
|
);
|
||||||
tokio::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)
|
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||||
pub async fn run_forever(&mut self) {
|
pub async fn run_forever(&mut self) {
|
||||||
self.start().await;
|
let mut shutdown = self.start().await;
|
||||||
if let Err(e) = tokio::signal::ctrl_c().await {
|
wait_for_signal().await;
|
||||||
error!(
|
|
||||||
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
log::info!("Sending shutdown");
|
||||||
"Received SIGINT - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
|
client_core::client::SHUTDOWN_HAS_BEEN_SIGNALLED.store(true, Ordering::Relaxed);
|
||||||
);
|
shutdown.signal_shutdown().ok();
|
||||||
|
|
||||||
|
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
|
||||||
|
shutdown.wait_for_shutdown().await;
|
||||||
|
|
||||||
|
log::info!("Stopping nym-socks5-client");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variant of `run_forever` that listends for remote control messages
|
// Variant of `run_forever` that listends for remote control messages
|
||||||
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
|
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
|
||||||
self.start().await;
|
let mut shutdown = self.start().await;
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
message = receiver.next() => {
|
message = receiver.next() => {
|
||||||
log::debug!("Received message: {:?}", message);
|
log::debug!("Received message: {:?}", message);
|
||||||
match message {
|
match message {
|
||||||
Some(Socks5ControlMessage::Stop) => {
|
Some(Socks5ControlMessage::Stop) => {
|
||||||
log::info!("Shutting down");
|
log::info!("Received stop message");
|
||||||
log::info!("Graceful shutdown of tasks not yet implemented, you might see (harmless) panics until then");
|
}
|
||||||
|
None => {
|
||||||
|
log::info!("Channel closed, stopping");
|
||||||
}
|
}
|
||||||
None => log::debug!("None"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ = tokio::signal::ctrl_c() => {
|
||||||
|
log::info!("Received SIGINT");
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::info!("Sending shutdown");
|
||||||
|
client_core::client::SHUTDOWN_HAS_BEEN_SIGNALLED.store(true, Ordering::Relaxed);
|
||||||
|
shutdown.signal_shutdown().ok();
|
||||||
|
|
||||||
|
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
|
||||||
|
shutdown.wait_for_shutdown().await;
|
||||||
|
|
||||||
|
log::info!("Stopping nym-socks5-client");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(&mut self) {
|
pub async fn start(&mut self) -> ShutdownNotifier {
|
||||||
info!("Starting nym client");
|
info!("Starting nym client");
|
||||||
// channels for inter-component communication
|
// channels for inter-component communication
|
||||||
// TODO: make the channels be internally created by the relevant components
|
// TODO: make the channels be internally created by the relevant components
|
||||||
@@ -330,33 +361,52 @@ impl NymClient {
|
|||||||
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
|
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
|
||||||
.expect("Failed to load reply key storage!");
|
.expect("Failed to load reply key storage!");
|
||||||
|
|
||||||
|
// Shutdown notifier for signalling tasks to stop
|
||||||
|
let shutdown = ShutdownNotifier::default();
|
||||||
|
|
||||||
// the components are started in very specific order. Unless you know what you are doing,
|
// the components are started in very specific order. Unless you know what you are doing,
|
||||||
// do not change that.
|
// do not change that.
|
||||||
self.start_topology_refresher(shared_topology_accessor.clone())
|
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
|
||||||
.await;
|
.await;
|
||||||
self.start_received_messages_buffer_controller(
|
self.start_received_messages_buffer_controller(
|
||||||
received_buffer_request_receiver,
|
received_buffer_request_receiver,
|
||||||
mixnet_messages_receiver,
|
mixnet_messages_receiver,
|
||||||
reply_key_storage.clone(),
|
reply_key_storage.clone(),
|
||||||
|
shutdown.subscribe(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let gateway_client = self
|
let gateway_client = self
|
||||||
.start_gateway_client(mixnet_messages_sender, ack_sender)
|
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
|
self.start_mix_traffic_controller(
|
||||||
|
sphinx_message_receiver,
|
||||||
|
gateway_client,
|
||||||
|
shutdown.subscribe(),
|
||||||
|
);
|
||||||
self.start_real_traffic_controller(
|
self.start_real_traffic_controller(
|
||||||
shared_topology_accessor.clone(),
|
shared_topology_accessor.clone(),
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
ack_receiver,
|
ack_receiver,
|
||||||
input_receiver,
|
input_receiver,
|
||||||
sphinx_message_sender.clone(),
|
sphinx_message_sender.clone(),
|
||||||
|
shutdown.subscribe(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
|
self.start_cover_traffic_stream(
|
||||||
self.start_socks5_listener(received_buffer_request_sender, input_sender);
|
shared_topology_accessor,
|
||||||
|
sphinx_message_sender,
|
||||||
|
shutdown.subscribe(),
|
||||||
|
);
|
||||||
|
self.start_socks5_listener(
|
||||||
|
received_buffer_request_sender,
|
||||||
|
input_sender,
|
||||||
|
shutdown.subscribe(),
|
||||||
|
);
|
||||||
|
|
||||||
info!("Client startup finished!");
|
info!("Client startup finished!");
|
||||||
info!("The address of this client is: {}", self.as_mix_recipient());
|
info!("The address of this client is: {}", self.as_mix_recipient());
|
||||||
|
|
||||||
|
shutdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use client_core::config::GatewayEndpoint;
|
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
|
||||||
use config::NymConfig;
|
use config::NymConfig;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -120,7 +120,12 @@ pub(crate) async fn execute(args: &Init) {
|
|||||||
let override_config_fields = OverrideConfig::from(args.clone());
|
let override_config_fields = OverrideConfig::from(args.clone());
|
||||||
config = override_config(config, override_config_fields);
|
config = override_config(config, override_config_fields);
|
||||||
|
|
||||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
|
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
eprintln!("Failed to setup gateway\nError: {err}");
|
||||||
|
std::process::exit(1)
|
||||||
|
});
|
||||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||||
|
|
||||||
let config_save_location = config.get_config_file_save_location();
|
let config_save_location = config.get_config_file_save_location();
|
||||||
@@ -138,7 +143,10 @@ pub(crate) async fn execute(args: &Init) {
|
|||||||
);
|
);
|
||||||
println!("Client configuration completed.");
|
println!("Client configuration completed.");
|
||||||
|
|
||||||
client_core::init::show_address(config.get_base());
|
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
|
||||||
|
eprintln!("Failed to show address\nError: {err}");
|
||||||
|
std::process::exit(1)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_gateway(
|
async fn setup_gateway(
|
||||||
@@ -146,7 +154,7 @@ async fn setup_gateway(
|
|||||||
register: bool,
|
register: bool,
|
||||||
user_chosen_gateway_id: Option<&str>,
|
user_chosen_gateway_id: Option<&str>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> GatewayEndpoint {
|
) -> Result<GatewayEndpoint, ClientCoreError> {
|
||||||
if register {
|
if register {
|
||||||
// Get the gateway details by querying the validator-api. Either pick one at random or use
|
// Get the gateway details by querying the validator-api. Either pick one at random or use
|
||||||
// the chosen one if it's among the available ones.
|
// the chosen one if it's among the available ones.
|
||||||
@@ -155,16 +163,16 @@ async fn setup_gateway(
|
|||||||
config.get_base().get_validator_api_endpoints(),
|
config.get_base().get_validator_api_endpoints(),
|
||||||
user_chosen_gateway_id,
|
user_chosen_gateway_id,
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
log::debug!("Querying gateway gives: {}", gateway);
|
log::debug!("Querying gateway gives: {}", gateway);
|
||||||
|
|
||||||
// Registering with gateway by setting up and writing shared keys to disk
|
// Registering with gateway by setting up and writing shared keys to disk
|
||||||
log::trace!("Registering gateway");
|
log::trace!("Registering gateway");
|
||||||
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
||||||
.await;
|
.await?;
|
||||||
println!("Saved all generated keys");
|
println!("Saved all generated keys");
|
||||||
|
|
||||||
gateway.into()
|
Ok(gateway.into())
|
||||||
} else if user_chosen_gateway_id.is_some() {
|
} else if user_chosen_gateway_id.is_some() {
|
||||||
// Just set the config, don't register or create any keys
|
// Just set the config, don't register or create any keys
|
||||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||||
@@ -174,22 +182,21 @@ async fn setup_gateway(
|
|||||||
config.get_base().get_validator_api_endpoints(),
|
config.get_base().get_validator_api_endpoints(),
|
||||||
user_chosen_gateway_id,
|
user_chosen_gateway_id,
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
log::debug!("Querying gateway gives: {}", gateway);
|
log::debug!("Querying gateway gives: {}", gateway);
|
||||||
gateway.into()
|
Ok(gateway.into())
|
||||||
} else {
|
} else {
|
||||||
println!("Not registering gateway, will reuse existing config and keys");
|
println!("Not registering gateway, will reuse existing config and keys");
|
||||||
match Config::load_from_file(Some(id)) {
|
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
|
||||||
Ok(existing_config) => existing_config.get_base().get_gateway_endpoint().clone(),
|
log::error!(
|
||||||
Err(err) => {
|
"Unable to configure gateway: {err}. \n
|
||||||
panic!(
|
Seems like the client was already initialized but it was not possible to read \
|
||||||
"Unable to configure gateway: {err}. \n
|
the existing configuration file. \n
|
||||||
Seems like the client was already initialized but it was not possible to read \
|
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||||
the existing configuration file. \n
|
removing the existing configuration and starting over."
|
||||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
);
|
||||||
removing the existing configuration and starting over."
|
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||||
)
|
})?;
|
||||||
}
|
Ok(existing_config.get_base().get_gateway_endpoint().clone())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use socks5_requests::{ConnectionId, Message, RemoteAddress, Request};
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use task::ShutdownListener;
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
|
||||||
use tokio::{self, net::TcpStream};
|
use tokio::{self, net::TcpStream};
|
||||||
|
|
||||||
@@ -140,6 +141,7 @@ pub(crate) struct SocksClient {
|
|||||||
service_provider: Recipient,
|
service_provider: Recipient,
|
||||||
self_address: Recipient,
|
self_address: Recipient,
|
||||||
started_proxy: bool,
|
started_proxy: bool,
|
||||||
|
shutdown_listener: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for SocksClient {
|
impl Drop for SocksClient {
|
||||||
@@ -163,6 +165,7 @@ impl SocksClient {
|
|||||||
service_provider: Recipient,
|
service_provider: Recipient,
|
||||||
controller_sender: ControllerSender,
|
controller_sender: ControllerSender,
|
||||||
self_address: Recipient,
|
self_address: Recipient,
|
||||||
|
shutdown_listener: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let connection_id = Self::generate_random();
|
let connection_id = Self::generate_random();
|
||||||
SocksClient {
|
SocksClient {
|
||||||
@@ -176,6 +179,7 @@ impl SocksClient {
|
|||||||
service_provider,
|
service_provider,
|
||||||
self_address,
|
self_address,
|
||||||
started_proxy: false,
|
started_proxy: false,
|
||||||
|
shutdown_listener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,6 +254,7 @@ impl SocksClient {
|
|||||||
conn_receiver,
|
conn_receiver,
|
||||||
input_sender,
|
input_sender,
|
||||||
connection_id,
|
connection_id,
|
||||||
|
self.shutdown_listener.clone(),
|
||||||
)
|
)
|
||||||
.run(move |conn_id, read_data, socket_closed| {
|
.run(move |conn_id, read_data, socket_closed| {
|
||||||
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
|
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
use client_core::client::received_buffer::ReconstructedMessagesReceiver;
|
|
||||||
use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
|
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
|
use client_core::client::received_buffer::ReconstructedMessagesReceiver;
|
||||||
|
use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
|
||||||
use nymsphinx::receiver::ReconstructedMessage;
|
use nymsphinx::receiver::ReconstructedMessage;
|
||||||
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
|
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
|
||||||
use socks5_requests::Message;
|
use socks5_requests::Message;
|
||||||
|
use task::ShutdownListener;
|
||||||
|
|
||||||
pub(crate) struct MixnetResponseListener {
|
pub(crate) struct MixnetResponseListener {
|
||||||
buffer_requester: ReceivedBufferRequestSender,
|
buffer_requester: ReceivedBufferRequestSender,
|
||||||
mix_response_receiver: ReconstructedMessagesReceiver,
|
mix_response_receiver: ReconstructedMessagesReceiver,
|
||||||
controller_sender: ControllerSender,
|
controller_sender: ControllerSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for MixnetResponseListener {
|
impl Drop for MixnetResponseListener {
|
||||||
@@ -25,6 +28,7 @@ impl MixnetResponseListener {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
buffer_requester: ReceivedBufferRequestSender,
|
buffer_requester: ReceivedBufferRequestSender,
|
||||||
controller_sender: ControllerSender,
|
controller_sender: ControllerSender,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (mix_response_sender, mix_response_receiver) = mpsc::unbounded();
|
let (mix_response_sender, mix_response_receiver) = mpsc::unbounded();
|
||||||
buffer_requester
|
buffer_requester
|
||||||
@@ -35,6 +39,7 @@ impl MixnetResponseListener {
|
|||||||
buffer_requester,
|
buffer_requester,
|
||||||
mix_response_receiver,
|
mix_response_receiver,
|
||||||
controller_sender,
|
controller_sender,
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,11 +78,25 @@ impl MixnetResponseListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run(&mut self) {
|
pub(crate) async fn run(&mut self) {
|
||||||
while let Some(received_responses) = self.mix_response_receiver.next().await {
|
while !self.shutdown.is_shutdown() {
|
||||||
for reconstructed_message in received_responses {
|
tokio::select! {
|
||||||
self.on_message(reconstructed_message).await;
|
received_responses = self.mix_response_receiver.next() => match received_responses {
|
||||||
|
Some(received_responses) => {
|
||||||
|
for reconstructed_message in received_responses {
|
||||||
|
self.on_message(reconstructed_message).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log::trace!("MixnetResponseListener: Stopping since channel closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.shutdown.recv() => {
|
||||||
|
log::trace!("MixnetResponseListener: Received shutdown");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
error!("We should never see this message");
|
assert!(self.shutdown.is_shutdown_poll());
|
||||||
|
log::debug!("MixnetResponseListener: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use log::*;
|
|||||||
use nymsphinx::addressing::clients::Recipient;
|
use nymsphinx::addressing::clients::Recipient;
|
||||||
use proxy_helpers::connection_controller::Controller;
|
use proxy_helpers::connection_controller::Controller;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use task::ShutdownListener;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
/// A Socks5 server that listens for connections.
|
/// A Socks5 server that listens for connections.
|
||||||
@@ -19,6 +20,7 @@ pub struct SphinxSocksServer {
|
|||||||
listening_address: SocketAddr,
|
listening_address: SocketAddr,
|
||||||
service_provider: Recipient,
|
service_provider: Recipient,
|
||||||
self_address: Recipient,
|
self_address: Recipient,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SphinxSocksServer {
|
impl SphinxSocksServer {
|
||||||
@@ -28,6 +30,7 @@ impl SphinxSocksServer {
|
|||||||
authenticator: Authenticator,
|
authenticator: Authenticator,
|
||||||
service_provider: Recipient,
|
service_provider: Recipient,
|
||||||
self_address: Recipient,
|
self_address: Recipient,
|
||||||
|
shutdown: ShutdownListener,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can
|
// hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can
|
||||||
// just modify the config
|
// just modify the config
|
||||||
@@ -38,6 +41,7 @@ impl SphinxSocksServer {
|
|||||||
listening_address: format!("{}:{}", ip, port).parse().unwrap(),
|
listening_address: format!("{}:{}", ip, port).parse().unwrap(),
|
||||||
service_provider,
|
service_provider,
|
||||||
self_address,
|
self_address,
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,62 +56,73 @@ impl SphinxSocksServer {
|
|||||||
info!("Serving Connections...");
|
info!("Serving Connections...");
|
||||||
|
|
||||||
// controller for managing all active connections
|
// controller for managing all active connections
|
||||||
let (mut active_streams_controller, controller_sender) = Controller::new();
|
let (mut active_streams_controller, controller_sender) =
|
||||||
|
Controller::new(self.shutdown.clone());
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
active_streams_controller.run().await;
|
active_streams_controller.run().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
// listener for mix messages
|
// listener for mix messages
|
||||||
let mut mixnet_response_listener =
|
let mut mixnet_response_listener = MixnetResponseListener::new(
|
||||||
MixnetResponseListener::new(buffer_requester, controller_sender.clone());
|
buffer_requester,
|
||||||
|
controller_sender.clone(),
|
||||||
|
self.shutdown.clone(),
|
||||||
|
);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
mixnet_response_listener.run().await;
|
mixnet_response_listener.run().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Ok((stream, _remote)) = listener.accept().await {
|
tokio::select! {
|
||||||
// TODO Optimize this
|
Ok((stream, _remote)) = listener.accept() => {
|
||||||
let mut client = SocksClient::new(
|
// TODO Optimize this
|
||||||
stream,
|
let mut client = SocksClient::new(
|
||||||
self.authenticator.clone(),
|
stream,
|
||||||
input_sender.clone(),
|
self.authenticator.clone(),
|
||||||
self.service_provider,
|
input_sender.clone(),
|
||||||
controller_sender.clone(),
|
self.service_provider,
|
||||||
self.self_address,
|
controller_sender.clone(),
|
||||||
);
|
self.self_address,
|
||||||
|
self.shutdown.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
{
|
{
|
||||||
match client.run().await {
|
match client.run().await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("Error! {}", error);
|
error!("Error! {}", error);
|
||||||
let error_text = format!("{}", error);
|
let error_text = format!("{}", error);
|
||||||
|
|
||||||
let response: ResponseCode;
|
let response: ResponseCode;
|
||||||
|
|
||||||
if error_text.contains("Host") {
|
if error_text.contains("Host") {
|
||||||
response = ResponseCode::HostUnreachable;
|
response = ResponseCode::HostUnreachable;
|
||||||
} else if error_text.contains("Network") {
|
} else if error_text.contains("Network") {
|
||||||
response = ResponseCode::NetworkUnreachable;
|
response = ResponseCode::NetworkUnreachable;
|
||||||
} else if error_text.contains("ttl") {
|
} else if error_text.contains("ttl") {
|
||||||
response = ResponseCode::TtlExpired
|
response = ResponseCode::TtlExpired
|
||||||
} else {
|
} else {
|
||||||
response = ResponseCode::Failure
|
response = ResponseCode::Failure
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.error(response).await.is_err() {
|
||||||
|
warn!("Failed to send error code");
|
||||||
|
};
|
||||||
|
if client.shutdown().await.is_err() {
|
||||||
|
warn!("Failed to shutdown TcpStream");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
};
|
||||||
if client.error(response).await.is_err() {
|
// client gets dropped here
|
||||||
warn!("Failed to send error code");
|
}
|
||||||
};
|
});
|
||||||
if client.shutdown().await.is_err() {
|
},
|
||||||
warn!("Failed to shutdown TcpStream");
|
_ = self.shutdown.recv() => {
|
||||||
};
|
log::trace!("SphinxSocksServer: Received shutdown");
|
||||||
}
|
log::debug!("SphinxSocksServer: Exiting");
|
||||||
};
|
return Ok(());
|
||||||
// client gets dropped here
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import expect from 'expect';
|
|||||||
describe('Query: balances', () => {
|
describe('Query: balances', () => {
|
||||||
it('can query for an account balance', async () => {
|
it('can query for an account balance', async () => {
|
||||||
const client = await ValidatorClient.connectForQuery(
|
const client = await ValidatorClient.connectForQuery(
|
||||||
'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
|
'https://rpc.nymtech.net/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
|
||||||
const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
|
const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
|
||||||
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
|
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
|
||||||
}).timeout(5000);
|
}).timeout(5000);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ web3 = { version = "0.17.0", default-features = false }
|
|||||||
async-trait = { version = "0.1.51" }
|
async-trait = { version = "0.1.51" }
|
||||||
|
|
||||||
# internal
|
# internal
|
||||||
|
coconut-interface = { path = "../../coconut-interface", optional = true }
|
||||||
credentials = { path = "../../credentials" }
|
credentials = { path = "../../credentials" }
|
||||||
crypto = { path = "../../crypto" }
|
crypto = { path = "../../crypto" }
|
||||||
gateway-requests = { path = "../../../gateway/gateway-requests" }
|
gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||||
|
network-defaults = { path = "../../network-defaults" }
|
||||||
nymsphinx = { path = "../../nymsphinx" }
|
nymsphinx = { path = "../../nymsphinx" }
|
||||||
pemstore = { path = "../../pemstore" }
|
pemstore = { path = "../../pemstore" }
|
||||||
coconut-interface = { path = "../../coconut-interface", optional = true }
|
|
||||||
network-defaults = { path = "../../network-defaults" }
|
|
||||||
validator-client = { path = "../validator-client", optional = true }
|
validator-client = { path = "../validator-client", optional = true }
|
||||||
|
|
||||||
[dependencies.tungstenite]
|
[dependencies.tungstenite]
|
||||||
@@ -38,12 +38,19 @@ default-features = false
|
|||||||
version = "1.19.1"
|
version = "1.19.1"
|
||||||
features = ["macros", "rt", "net", "sync", "time"]
|
features = ["macros", "rt", "net", "sync", "time"]
|
||||||
|
|
||||||
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
|
||||||
|
version = "0.1.9"
|
||||||
|
features = ["net", "sync", "time"]
|
||||||
|
|
||||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
||||||
version = "0.14"
|
version = "0.14"
|
||||||
|
|
||||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.credential-storage]
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.credential-storage]
|
||||||
path = "../../credential-storage"
|
path = "../../credential-storage"
|
||||||
|
|
||||||
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
|
||||||
|
path = "../../task"
|
||||||
|
|
||||||
# wasm-only dependencies
|
# wasm-only dependencies
|
||||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||||
version = "0.2"
|
version = "0.2"
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ use rand::rngs::OsRng;
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use task::ShutdownListener;
|
||||||
use tungstenite::protocol::Message;
|
use tungstenite::protocol::Message;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -65,6 +67,10 @@ pub struct GatewayClient {
|
|||||||
reconnection_attempts: usize,
|
reconnection_attempts: usize,
|
||||||
/// Delay between each subsequent reconnection attempt.
|
/// Delay between each subsequent reconnection attempt.
|
||||||
reconnection_backoff: Duration,
|
reconnection_backoff: Duration,
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
/// Listen to shutdown messages.
|
||||||
|
shutdown: Option<ShutdownListener>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GatewayClient {
|
impl GatewayClient {
|
||||||
@@ -80,6 +86,7 @@ impl GatewayClient {
|
|||||||
ack_sender: AcknowledgementSender,
|
ack_sender: AcknowledgementSender,
|
||||||
response_timeout_duration: Duration,
|
response_timeout_duration: Duration,
|
||||||
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
|
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
GatewayClient {
|
GatewayClient {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
@@ -91,12 +98,19 @@ impl GatewayClient {
|
|||||||
local_identity,
|
local_identity,
|
||||||
shared_key,
|
shared_key,
|
||||||
connection: SocketState::NotConnected,
|
connection: SocketState::NotConnected,
|
||||||
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender),
|
packet_router: PacketRouter::new(
|
||||||
|
ack_sender,
|
||||||
|
mixnet_message_sender,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
shutdown.clone(),
|
||||||
|
),
|
||||||
response_timeout_duration,
|
response_timeout_duration,
|
||||||
bandwidth_controller,
|
bandwidth_controller,
|
||||||
should_reconnect_on_failure: true,
|
should_reconnect_on_failure: true,
|
||||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +137,7 @@ impl GatewayClient {
|
|||||||
gateway_owner: String,
|
gateway_owner: String,
|
||||||
local_identity: Arc<identity::KeyPair>,
|
local_identity: Arc<identity::KeyPair>,
|
||||||
response_timeout_duration: Duration,
|
response_timeout_duration: Duration,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
|
|
||||||
@@ -130,7 +145,12 @@ impl GatewayClient {
|
|||||||
// perfectly fine here, because it's not meant to be used
|
// perfectly fine here, because it's not meant to be used
|
||||||
let (ack_tx, _) = mpsc::unbounded();
|
let (ack_tx, _) = mpsc::unbounded();
|
||||||
let (mix_tx, _) = mpsc::unbounded();
|
let (mix_tx, _) = mpsc::unbounded();
|
||||||
let packet_router = PacketRouter::new(ack_tx, mix_tx);
|
let packet_router = PacketRouter::new(
|
||||||
|
ack_tx,
|
||||||
|
mix_tx,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
shutdown.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
GatewayClient {
|
GatewayClient {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
@@ -148,6 +168,8 @@ impl GatewayClient {
|
|||||||
should_reconnect_on_failure: false,
|
should_reconnect_on_failure: false,
|
||||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,8 +301,31 @@ impl GatewayClient {
|
|||||||
let mut fused_timeout = timeout.fuse();
|
let mut fused_timeout = timeout.fuse();
|
||||||
let mut fused_stream = conn.fuse();
|
let mut fused_stream = conn.fuse();
|
||||||
|
|
||||||
|
// Bit of an ugly workaround for selecting on an `Option` without having access to
|
||||||
|
// `tokio::select`
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let shutdown = {
|
||||||
|
let m_shutdown = self.shutdown.clone();
|
||||||
|
async {
|
||||||
|
if let Some(mut s) = m_shutdown {
|
||||||
|
s.recv().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fuse()
|
||||||
|
};
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
tokio::pin!(shutdown);
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let mut shutdown = Box::pin(async {}.fuse());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
futures::select! {
|
futures::select! {
|
||||||
|
_ = shutdown => {
|
||||||
|
log::trace!("GatewayClient control response: Received shutdown");
|
||||||
|
log::debug!("GatewayClient control response: Exiting");
|
||||||
|
break Err(GatewayClientError::ConnectionClosedGatewayShutdown);
|
||||||
|
}
|
||||||
_ = &mut fused_timeout => {
|
_ = &mut fused_timeout => {
|
||||||
break Err(GatewayClientError::Timeout);
|
break Err(GatewayClientError::Timeout);
|
||||||
}
|
}
|
||||||
@@ -291,14 +336,16 @@ impl GatewayClient {
|
|||||||
};
|
};
|
||||||
match ws_msg {
|
match ws_msg {
|
||||||
Message::Binary(bin_msg) => {
|
Message::Binary(bin_msg) => {
|
||||||
self.packet_router.route_received(vec![bin_msg]);
|
if let Err(err) = self.packet_router.route_received(vec![bin_msg]) {
|
||||||
|
log::warn!("Route received failed: {:?}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::Text(txt_msg) => {
|
Message::Text(txt_msg) => {
|
||||||
break ServerResponse::try_from(txt_msg).map_err(|_| GatewayClientError::MalformedResponse);
|
break ServerResponse::try_from(txt_msg).map_err(|_| GatewayClientError::MalformedResponse);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -723,6 +770,8 @@ impl GatewayClient {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("no shared key present even though we're authenticated!"),
|
.expect("no shared key present even though we're authenticated!"),
|
||||||
),
|
),
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
self.shutdown.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ pub enum GatewayClientError {
|
|||||||
#[error("Connection was abruptly closed")]
|
#[error("Connection was abruptly closed")]
|
||||||
ConnectionAbruptlyClosed,
|
ConnectionAbruptlyClosed,
|
||||||
|
|
||||||
|
#[error("Connection was abruptly closed as gateway was stopped")]
|
||||||
|
ConnectionClosedGatewayShutdown,
|
||||||
|
|
||||||
#[error("Received response was malformed")]
|
#[error("Received response was malformed")]
|
||||||
MalformedResponse,
|
MalformedResponse,
|
||||||
|
|
||||||
@@ -93,6 +96,9 @@ pub enum GatewayClientError {
|
|||||||
|
|
||||||
#[error("Timed out")]
|
#[error("Timed out")]
|
||||||
Timeout,
|
Timeout,
|
||||||
|
|
||||||
|
#[error("Failed to send mixnet message")]
|
||||||
|
MixnetMsgSenderFailedToSend,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GatewayClientError {
|
impl GatewayClientError {
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ use futures::channel::mpsc;
|
|||||||
use log::*;
|
use log::*;
|
||||||
use nymsphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
|
use nymsphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||||
use nymsphinx::params::packet_sizes::PacketSize;
|
use nymsphinx::params::packet_sizes::PacketSize;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use task::ShutdownListener;
|
||||||
|
|
||||||
|
use crate::error::GatewayClientError;
|
||||||
|
|
||||||
pub type MixnetMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
|
pub type MixnetMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
|
||||||
pub type MixnetMessageReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
|
pub type MixnetMessageReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
|
||||||
@@ -19,20 +23,28 @@ pub type AcknowledgementReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
|
|||||||
pub struct PacketRouter {
|
pub struct PacketRouter {
|
||||||
ack_sender: AcknowledgementSender,
|
ack_sender: AcknowledgementSender,
|
||||||
mixnet_message_sender: MixnetMessageSender,
|
mixnet_message_sender: MixnetMessageSender,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
shutdown: Option<ShutdownListener>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketRouter {
|
impl PacketRouter {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ack_sender: AcknowledgementSender,
|
ack_sender: AcknowledgementSender,
|
||||||
mixnet_message_sender: MixnetMessageSender,
|
mixnet_message_sender: MixnetMessageSender,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PacketRouter {
|
PacketRouter {
|
||||||
ack_sender,
|
ack_sender,
|
||||||
mixnet_message_sender,
|
mixnet_message_sender,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
shutdown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn route_received(&self, unwrapped_packets: Vec<Vec<u8>>) {
|
pub fn route_received(
|
||||||
|
&mut self,
|
||||||
|
unwrapped_packets: Vec<Vec<u8>>,
|
||||||
|
) -> Result<(), GatewayClientError> {
|
||||||
let mut received_messages = Vec::new();
|
let mut received_messages = Vec::new();
|
||||||
let mut received_acks = Vec::new();
|
let mut received_acks = Vec::new();
|
||||||
|
|
||||||
@@ -60,24 +72,29 @@ impl PacketRouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// due to how we are currently using it, those unwraps can't fail, but if we ever
|
|
||||||
// wanted to make `gateway-client` into some more generic library, we would probably need
|
|
||||||
// to catch that error or something.
|
|
||||||
if !received_messages.is_empty() {
|
if !received_messages.is_empty() {
|
||||||
trace!("routing 'real'");
|
trace!("routing 'real'");
|
||||||
self.mixnet_message_sender
|
if let Err(err) = self.mixnet_message_sender.unbounded_send(received_messages) {
|
||||||
.unbounded_send(received_messages)
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
.unwrap();
|
if let Some(shutdown) = &mut self.shutdown {
|
||||||
|
if shutdown.is_shutdown_poll() {
|
||||||
|
// This should ideally not happen, but it's ok
|
||||||
|
log::warn!("Failed to send mixnet message due to receiver task shutdown");
|
||||||
|
return Err(GatewayClientError::MixnetMsgSenderFailedToSend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This should never happen during ordinary operation the way it's currently used.
|
||||||
|
// Abort to be on the safe side
|
||||||
|
panic!("Failed to send mixnet message: {:?}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !received_acks.is_empty() {
|
if !received_acks.is_empty() {
|
||||||
trace!("routing acks");
|
trace!("routing acks");
|
||||||
match self.ack_sender.unbounded_send(received_acks) {
|
if let Err(e) = self.ack_sender.unbounded_send(received_acks) {
|
||||||
Ok(_) => {}
|
error!("failed to send ack: {:?}", e);
|
||||||
Err(e) => {
|
|
||||||
error!("failed to send ack: {:?}", e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use gateway_requests::registration::handshake::SharedKeys;
|
|||||||
use gateway_requests::BinaryResponse;
|
use gateway_requests::BinaryResponse;
|
||||||
use log::*;
|
use log::*;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use task::ShutdownListener;
|
||||||
use tungstenite::Message;
|
use tungstenite::Message;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -44,9 +46,9 @@ pub(crate) struct PartiallyDelegated {
|
|||||||
impl PartiallyDelegated {
|
impl PartiallyDelegated {
|
||||||
fn route_socket_message(
|
fn route_socket_message(
|
||||||
ws_msg: Message,
|
ws_msg: Message,
|
||||||
packet_router: &PacketRouter,
|
packet_router: &mut PacketRouter,
|
||||||
shared_key: &SharedKeys,
|
shared_key: &SharedKeys,
|
||||||
) {
|
) -> Result<(), GatewayClientError> {
|
||||||
match ws_msg {
|
match ws_msg {
|
||||||
Message::Binary(bin_msg) => {
|
Message::Binary(bin_msg) => {
|
||||||
// this function decrypts the request and checks the MAC
|
// this function decrypts the request and checks the MAC
|
||||||
@@ -60,7 +62,7 @@ impl PartiallyDelegated {
|
|||||||
"message received from the gateway was malformed! - {:?}",
|
"message received from the gateway was malformed! - {:?}",
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,18 +76,22 @@ impl PartiallyDelegated {
|
|||||||
// This would also require NOT discarding any text responses here.
|
// This would also require NOT discarding any text responses here.
|
||||||
|
|
||||||
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
|
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
|
||||||
Message::Text(text) => trace!(
|
Message::Text(text) => {
|
||||||
"received a text message - probably a response to some previous query! - {}",
|
trace!(
|
||||||
text
|
"received a text message - probably a response to some previous query! - {}",
|
||||||
),
|
text
|
||||||
_ => (),
|
);
|
||||||
};
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn split_and_listen_for_mixnet_messages(
|
pub(crate) fn split_and_listen_for_mixnet_messages(
|
||||||
conn: WsConn,
|
conn: WsConn,
|
||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
shared_key: Arc<SharedKeys>,
|
shared_key: Arc<SharedKeys>,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))] shutdown: Option<ShutdownListener>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// when called for, it NEEDS TO yield back the stream so that we could merge it and
|
// when called for, it NEEDS TO yield back the stream so that we could merge it and
|
||||||
// read control request responses.
|
// read control request responses.
|
||||||
@@ -97,10 +103,34 @@ impl PartiallyDelegated {
|
|||||||
let mixnet_receiver_future = async move {
|
let mixnet_receiver_future = async move {
|
||||||
let mut fused_receiver = notify_receiver.fuse();
|
let mut fused_receiver = notify_receiver.fuse();
|
||||||
let mut fused_stream = (&mut stream).fuse();
|
let mut fused_stream = (&mut stream).fuse();
|
||||||
|
let mut packet_router = packet_router;
|
||||||
|
|
||||||
|
// Bit of an ugly workaround for selecting on an `Option` without having access to
|
||||||
|
// `tokio::select`
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let shutdown = {
|
||||||
|
let m_shutdown = shutdown.clone();
|
||||||
|
async {
|
||||||
|
if let Some(mut s) = m_shutdown {
|
||||||
|
s.recv().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fuse()
|
||||||
|
};
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
tokio::pin!(shutdown);
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let mut shutdown = Box::pin(async {}.fuse());
|
||||||
|
|
||||||
let ret_err = loop {
|
let ret_err = loop {
|
||||||
futures::select! {
|
futures::select! {
|
||||||
_ = fused_receiver => {
|
_ = shutdown => {
|
||||||
|
log::trace!("GatewayClient listener: Received shutdown");
|
||||||
|
log::debug!("GatewayClient listener: Exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ = &mut fused_receiver => {
|
||||||
break Ok(());
|
break Ok(());
|
||||||
}
|
}
|
||||||
msg = fused_stream.next() => {
|
msg = fused_stream.next() => {
|
||||||
@@ -108,7 +138,9 @@ impl PartiallyDelegated {
|
|||||||
Err(err) => break Err(err),
|
Err(err) => break Err(err),
|
||||||
Ok(msg) => msg
|
Ok(msg) => msg
|
||||||
};
|
};
|
||||||
Self::route_socket_message(ws_msg, &packet_router, shared_key.as_ref());
|
if let Err(err) = Self::route_socket_message(ws_msg, &mut packet_router, shared_key.as_ref()) {
|
||||||
|
log::warn!("Route socket message failed: {:?}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ impl TryFrom<ProtoContractCodeHistoryEntry> for ContractCodeHistoryEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize)]
|
||||||
pub struct GasInfo {
|
pub struct GasInfo {
|
||||||
/// GasWanted is the maximum units of work we allow this tx to perform.
|
/// GasWanted is the maximum units of work we allow this tx to perform.
|
||||||
pub gas_wanted: Gas,
|
pub gas_wanted: Gas,
|
||||||
@@ -645,7 +645,7 @@ impl InstantiateOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct InstantiateResult {
|
pub struct InstantiateResult {
|
||||||
/// The address of the newly instantiated contract
|
/// The address of the newly instantiated contract
|
||||||
pub contract_address: AccountId,
|
pub contract_address: AccountId,
|
||||||
@@ -658,7 +658,7 @@ pub struct InstantiateResult {
|
|||||||
pub gas_info: GasInfo,
|
pub gas_info: GasInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct ChangeAdminResult {
|
pub struct ChangeAdminResult {
|
||||||
pub logs: Vec<Log>,
|
pub logs: Vec<Log>,
|
||||||
|
|
||||||
@@ -668,7 +668,7 @@ pub struct ChangeAdminResult {
|
|||||||
pub gas_info: GasInfo,
|
pub gas_info: GasInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct MigrateResult {
|
pub struct MigrateResult {
|
||||||
pub logs: Vec<Log>,
|
pub logs: Vec<Log>,
|
||||||
|
|
||||||
@@ -678,7 +678,7 @@ pub struct MigrateResult {
|
|||||||
pub gas_info: GasInfo,
|
pub gas_info: GasInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct ExecuteResult {
|
pub struct ExecuteResult {
|
||||||
pub logs: Vec<Log>,
|
pub logs: Vec<Log>,
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use crate::nymd::error::NymdError;
|
|||||||
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
|
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
|
||||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||||
use cosmrs::cosmwasm;
|
use cosmrs::cosmwasm;
|
||||||
|
use cosmrs::rpc::endpoint::block::Response as BlockResponse;
|
||||||
|
use cosmrs::rpc::query::Query;
|
||||||
use cosmrs::rpc::Error as TendermintRpcError;
|
use cosmrs::rpc::Error as TendermintRpcError;
|
||||||
use cosmrs::rpc::HttpClientUrl;
|
use cosmrs::rpc::HttpClientUrl;
|
||||||
use cosmrs::tx::Msg;
|
use cosmrs::tx::Msg;
|
||||||
@@ -212,6 +214,10 @@ impl<C> NymdClient<C> {
|
|||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn current_chain_details(&self) -> &ChainDetails {
|
||||||
|
&self.config.chain_details
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_mixnet_contract_address(&mut self, address: AccountId) {
|
pub fn set_mixnet_contract_address(&mut self, address: AccountId) {
|
||||||
self.config.mixnet_contract_address = Some(address);
|
self.config.mixnet_contract_address = Some(address);
|
||||||
}
|
}
|
||||||
@@ -332,6 +338,13 @@ impl<C> NymdClient<C> {
|
|||||||
&self.client_address.as_ref().unwrap()[0]
|
&self.client_address.as_ref().unwrap()[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn signer(&self) -> &DirectSecp256k1HdWallet
|
||||||
|
where
|
||||||
|
C: SigningCosmWasmClient,
|
||||||
|
{
|
||||||
|
self.client.signer()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn gas_price(&self) -> &GasPrice
|
pub fn gas_price(&self) -> &GasPrice
|
||||||
where
|
where
|
||||||
C: SigningCosmWasmClient,
|
C: SigningCosmWasmClient,
|
||||||
@@ -355,11 +368,26 @@ impl<C> NymdClient<C> {
|
|||||||
address: &AccountId,
|
address: &AccountId,
|
||||||
) -> Result<Option<Account>, NymdError>
|
) -> Result<Option<Account>, NymdError>
|
||||||
where
|
where
|
||||||
C: SigningCosmWasmClient + Sync,
|
C: CosmWasmClient + Sync,
|
||||||
{
|
{
|
||||||
self.client.get_account(address).await
|
self.client.get_account(address).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_account_public_key(
|
||||||
|
&self,
|
||||||
|
address: &AccountId,
|
||||||
|
) -> Result<Option<cosmrs::crypto::PublicKey>, NymdError>
|
||||||
|
where
|
||||||
|
C: CosmWasmClient + Sync,
|
||||||
|
{
|
||||||
|
if let Some(account) = self.client.get_account(address).await? {
|
||||||
|
let base_account = account.try_get_base_account()?;
|
||||||
|
return Ok(base_account.pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_current_block_timestamp(&self) -> Result<TendermintTime, NymdError>
|
pub async fn get_current_block_timestamp(&self) -> Result<TendermintTime, NymdError>
|
||||||
where
|
where
|
||||||
C: CosmWasmClient + Sync,
|
C: CosmWasmClient + Sync,
|
||||||
@@ -377,6 +405,13 @@ impl<C> NymdClient<C> {
|
|||||||
Ok(self.client.get_block(height).await?.block.header.time)
|
Ok(self.client.get_block(height).await?.block.header.time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_block(&self, height: Option<u32>) -> Result<BlockResponse, NymdError>
|
||||||
|
where
|
||||||
|
C: CosmWasmClient + Sync,
|
||||||
|
{
|
||||||
|
self.client.get_block(height).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_current_block_height(&self) -> Result<Height, NymdError>
|
pub async fn get_current_block_height(&self) -> Result<Height, NymdError>
|
||||||
where
|
where
|
||||||
C: CosmWasmClient + Sync,
|
C: CosmWasmClient + Sync,
|
||||||
@@ -421,6 +456,13 @@ impl<C> NymdClient<C> {
|
|||||||
self.client.get_balance(address, denom).await
|
self.client.get_balance(address, denom).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_balances(&self, address: &AccountId) -> Result<Vec<Coin>, NymdError>
|
||||||
|
where
|
||||||
|
C: CosmWasmClient + Sync,
|
||||||
|
{
|
||||||
|
self.client.get_all_balances(address).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError>
|
pub async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError>
|
||||||
where
|
where
|
||||||
C: CosmWasmClient + Sync,
|
C: CosmWasmClient + Sync,
|
||||||
@@ -428,6 +470,13 @@ impl<C> NymdClient<C> {
|
|||||||
self.client.get_tx(id).await
|
self.client.get_tx(id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn search_tx(&self, query: Query) -> Result<Vec<TxResponse>, NymdError>
|
||||||
|
where
|
||||||
|
C: CosmWasmClient + Sync,
|
||||||
|
{
|
||||||
|
self.client.search_tx(query).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_total_supply(&self) -> Result<Vec<Coin>, NymdError>
|
pub async fn get_total_supply(&self) -> Result<Vec<Coin>, NymdError>
|
||||||
where
|
where
|
||||||
C: CosmWasmClient + Sync,
|
C: CosmWasmClient + Sync,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
use crate::nymd::error::NymdError;
|
use crate::nymd::error::NymdError;
|
||||||
use config::defaults;
|
use config::defaults;
|
||||||
use cosmrs::bip32::{DerivationPath, XPrv};
|
use cosmrs::bip32::{DerivationPath, XPrv};
|
||||||
use cosmrs::crypto::secp256k1::SigningKey;
|
use cosmrs::crypto::secp256k1::{Signature, SigningKey};
|
||||||
use cosmrs::crypto::PublicKey;
|
use cosmrs::crypto::PublicKey;
|
||||||
use cosmrs::tx::SignDoc;
|
use cosmrs::tx::SignDoc;
|
||||||
use cosmrs::{tx, AccountId};
|
use cosmrs::{tx, AccountId};
|
||||||
@@ -105,6 +105,17 @@ impl DirectSecp256k1HdWallet {
|
|||||||
self.secret.to_string()
|
self.secret.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sign_raw_with_account(
|
||||||
|
&self,
|
||||||
|
signer: &AccountData,
|
||||||
|
message: &[u8],
|
||||||
|
) -> Result<Signature, NymdError> {
|
||||||
|
signer
|
||||||
|
.private_key
|
||||||
|
.sign(message)
|
||||||
|
.map_err(|_| NymdError::SigningFailure)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sign_direct_with_account(
|
pub fn sign_direct_with_account(
|
||||||
&self,
|
&self,
|
||||||
signer: &AccountData,
|
signer: &AccountData,
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
[package]
|
||||||
|
name = "nym-cli-commands"
|
||||||
|
version = "1.0.0"
|
||||||
|
authors = ["Nym Technologies SA"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base64 = "0.13.0"
|
||||||
|
bip39 = "1.0.1"
|
||||||
|
bs58 = "0.4"
|
||||||
|
comfy-table = "6.0.0"
|
||||||
|
cfg-if = "1.0.0"
|
||||||
|
clap = { version = "3.2", features = ["derive"] }
|
||||||
|
handlebars = "3.0.1"
|
||||||
|
humantime-serde = "1.0"
|
||||||
|
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
|
||||||
|
log = "0.4"
|
||||||
|
rand = {version = "0.6", features = ["std"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
thiserror = "1"
|
||||||
|
time = { version = "0.3.6", features = ["parsing", "formatting"] }
|
||||||
|
toml = "0.5.6"
|
||||||
|
url = "2.2"
|
||||||
|
|
||||||
|
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||||
|
cosmwasm-std = { version = "1.0.0" }
|
||||||
|
|
||||||
|
validator-client = { path = "../client-libs/validator-client", features = ["nymd-client"] }
|
||||||
|
network-defaults = { path = "../network-defaults" }
|
||||||
|
mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||||
|
vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" }
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Common `clap` Command Crate
|
||||||
|
|
||||||
|
This crate contains `clap` commands for common operations:
|
||||||
|
|
||||||
|
- account creation and queries
|
||||||
|
- block queries
|
||||||
|
- cosmwasm uploads, instantiate, execution, query, etc
|
||||||
|
- mixnet actions and queries
|
||||||
|
- sign and verify messages
|
||||||
|
- query for transactions
|
||||||
|
- create vesting schedules and query for them
|
||||||
|
|
||||||
|
For how to use this crate, please see the [Nym CLI](../../tools/nym-cli).
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// TODO: add coconut commands here
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ContextError {
|
||||||
|
#[error("mnemonic was not provided, pass as an argument or an env var called MNEMONIC")]
|
||||||
|
MnemonicNotProvided,
|
||||||
|
|
||||||
|
#[error("failed to parse mnemonic - {0}")]
|
||||||
|
Bip39Error(#[from] bip39::Error),
|
||||||
|
|
||||||
|
// there are lots of error that can occur in the nymd client, so just pass through their display details
|
||||||
|
// TODO: improve this to return known errors
|
||||||
|
#[error("failed to create client - {0}")]
|
||||||
|
NymdError(String),
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use network_defaults::{
|
||||||
|
setup_env,
|
||||||
|
var_names::{API_VALIDATOR, MIXNET_CONTRACT_ADDRESS, NYMD_VALIDATOR, VESTING_CONTRACT_ADDRESS},
|
||||||
|
NymNetworkDetails,
|
||||||
|
};
|
||||||
|
use validator_client::nymd::{self, AccountId, NymdClient, QueryNymdClient, SigningNymdClient};
|
||||||
|
pub use validator_client::validator_api::Client as ValidatorApiClient;
|
||||||
|
|
||||||
|
use crate::context::errors::ContextError;
|
||||||
|
|
||||||
|
pub mod errors;
|
||||||
|
|
||||||
|
pub type SigningClient = validator_client::nymd::NymdClient<SigningNymdClient>;
|
||||||
|
pub type QueryClient = validator_client::nymd::NymdClient<QueryNymdClient>;
|
||||||
|
pub type SigningClientWithValidatorAPI = validator_client::Client<SigningNymdClient>;
|
||||||
|
pub type QueryClientWithValidatorAPI = validator_client::Client<QueryNymdClient>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ClientArgs {
|
||||||
|
pub config_env_file: Option<std::path::PathBuf>,
|
||||||
|
pub nymd_url: Option<String>,
|
||||||
|
pub validator_api_url: Option<String>,
|
||||||
|
pub mnemonic: Option<bip39::Mnemonic>,
|
||||||
|
pub mixnet_contract_address: Option<AccountId>,
|
||||||
|
pub vesting_contract_address: Option<AccountId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_network_details(args: &ClientArgs) -> Result<NymNetworkDetails, ContextError> {
|
||||||
|
// let the network defaults crate handle setting up the env vars if the file arg is set, otherwise
|
||||||
|
// it will default to what is already in env vars, falling back to mainnet
|
||||||
|
setup_env(args.config_env_file.clone());
|
||||||
|
|
||||||
|
// override the env vars with user supplied arguments, if set
|
||||||
|
if let Some(nymd_url) = args.nymd_url.as_ref() {
|
||||||
|
std::env::set_var(NYMD_VALIDATOR, nymd_url);
|
||||||
|
}
|
||||||
|
if let Some(validator_api_url) = args.validator_api_url.as_ref() {
|
||||||
|
std::env::set_var(API_VALIDATOR, validator_api_url);
|
||||||
|
}
|
||||||
|
if let Some(mixnet_contract_address) = args.mixnet_contract_address.as_ref() {
|
||||||
|
std::env::set_var(MIXNET_CONTRACT_ADDRESS, mixnet_contract_address.to_string());
|
||||||
|
}
|
||||||
|
if let Some(vesting_contract_address) = args.vesting_contract_address.as_ref() {
|
||||||
|
std::env::set_var(
|
||||||
|
VESTING_CONTRACT_ADDRESS,
|
||||||
|
vesting_contract_address.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(NymNetworkDetails::new_from_env())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_signing_client(
|
||||||
|
args: ClientArgs,
|
||||||
|
network_details: &NymNetworkDetails,
|
||||||
|
) -> Result<SigningClient, ContextError> {
|
||||||
|
let client_config = nymd::Config::try_from_nym_network_details(network_details)
|
||||||
|
.expect("failed to construct valid validator client config with the provided network");
|
||||||
|
|
||||||
|
// get mnemonic
|
||||||
|
let mnemonic = match std::env::var("MNEMONIC") {
|
||||||
|
Ok(value) => bip39::Mnemonic::parse(value)?,
|
||||||
|
// env var MNEMONIC is not present, so try to fall back to arg --mnemonic ...
|
||||||
|
Err(_) => match args.mnemonic {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return Err(ContextError::MnemonicNotProvided), // no env var or arg provided
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let nymd_url = network_details
|
||||||
|
.endpoints
|
||||||
|
.first()
|
||||||
|
.expect("network details are not defined")
|
||||||
|
.nymd_url
|
||||||
|
.as_str();
|
||||||
|
|
||||||
|
match NymdClient::connect_with_mnemonic(client_config, nymd_url, mnemonic, None) {
|
||||||
|
Ok(client) => Ok(client),
|
||||||
|
Err(e) => Err(ContextError::NymdError(format!("{:?}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_query_client(
|
||||||
|
network_details: &NymNetworkDetails,
|
||||||
|
) -> Result<QueryClient, ContextError> {
|
||||||
|
let client_config = nymd::Config::try_from_nym_network_details(network_details)
|
||||||
|
.expect("failed to construct valid validator client config with the provided network");
|
||||||
|
|
||||||
|
let nymd_url = network_details
|
||||||
|
.endpoints
|
||||||
|
.first()
|
||||||
|
.expect("network details are not defined")
|
||||||
|
.nymd_url
|
||||||
|
.as_str();
|
||||||
|
|
||||||
|
match NymdClient::connect(client_config, nymd_url) {
|
||||||
|
Ok(client) => Ok(client),
|
||||||
|
Err(e) => Err(ContextError::NymdError(format!("{:?}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_signing_client_with_validator_api(
|
||||||
|
args: ClientArgs,
|
||||||
|
network_details: &NymNetworkDetails,
|
||||||
|
) -> Result<SigningClientWithValidatorAPI, ContextError> {
|
||||||
|
let client_config = validator_client::Config::try_from_nym_network_details(network_details)
|
||||||
|
.expect("failed to construct valid validator client config with the provided network");
|
||||||
|
|
||||||
|
// get mnemonic
|
||||||
|
let mnemonic = match std::env::var("MNEMONIC") {
|
||||||
|
Ok(value) => bip39::Mnemonic::parse(value)?,
|
||||||
|
// env var MNEMONIC is not present, so try to fall back to arg --mnemonic ...
|
||||||
|
Err(_) => match args.mnemonic {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return Err(ContextError::MnemonicNotProvided), // no env var or arg provided
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match validator_client::client::Client::new_signing(client_config, mnemonic) {
|
||||||
|
Ok(client) => Ok(client),
|
||||||
|
Err(e) => Err(ContextError::NymdError(format!("{:?}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_query_client_with_validator_api(
|
||||||
|
network_details: &NymNetworkDetails,
|
||||||
|
) -> Result<QueryClientWithValidatorAPI, ContextError> {
|
||||||
|
let client_config = validator_client::Config::try_from_nym_network_details(network_details)
|
||||||
|
.expect("failed to construct valid validator client config with the provided network");
|
||||||
|
|
||||||
|
match validator_client::client::Client::new_query(client_config) {
|
||||||
|
Ok(client) => Ok(client),
|
||||||
|
Err(e) => Err(ContextError::NymdError(format!("{:?}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
pub mod coconut;
|
||||||
|
pub mod context;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod validator;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use cosmwasm_std::{Coin as CosmWasmCoin, Decimal};
|
||||||
|
use log::error;
|
||||||
|
use validator_client::nymd::Coin;
|
||||||
|
|
||||||
|
pub fn pretty_coin(coin: &Coin) -> String {
|
||||||
|
let amount = Decimal::from_ratio(coin.amount, 1_000_000u128);
|
||||||
|
let denom = if coin.denom.starts_with('u') {
|
||||||
|
&coin.denom[1..]
|
||||||
|
} else {
|
||||||
|
&coin.denom
|
||||||
|
};
|
||||||
|
format!("{} {}", amount, denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pretty_cosmwasm_coin(coin: &CosmWasmCoin) -> String {
|
||||||
|
let amount = Decimal::from_ratio(coin.amount, 1_000_000u128);
|
||||||
|
let denom = if coin.denom.starts_with('u') {
|
||||||
|
&coin.denom[1..]
|
||||||
|
} else {
|
||||||
|
&coin.denom
|
||||||
|
};
|
||||||
|
format!("{} {}", amount, denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_error<E>(e: E)
|
||||||
|
where
|
||||||
|
E: Display,
|
||||||
|
{
|
||||||
|
error!("{}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_error_passthrough<E>(e: E) -> E
|
||||||
|
where
|
||||||
|
E: Error + Display,
|
||||||
|
{
|
||||||
|
error!("{}", e);
|
||||||
|
e
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{error, info};
|
||||||
|
|
||||||
|
use validator_client::nymd::AccountId;
|
||||||
|
|
||||||
|
use crate::context::QueryClient;
|
||||||
|
use crate::utils::{pretty_coin, show_error};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
#[clap(help = "The account address to get the balance for")]
|
||||||
|
pub address: Option<AccountId>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
#[clap(help = "Optional currency to show balance for")]
|
||||||
|
pub denom: Option<String>,
|
||||||
|
|
||||||
|
#[clap(long, requires = "denom")]
|
||||||
|
#[clap(help = "Optionally hide the denom")]
|
||||||
|
pub hide_denom: bool,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
#[clap(help = "Show as a raw value")]
|
||||||
|
pub raw: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_balance(
|
||||||
|
args: Args,
|
||||||
|
client: &QueryClient,
|
||||||
|
address_from_mnemonic: Option<AccountId>,
|
||||||
|
) {
|
||||||
|
if args.address.is_none() && address_from_mnemonic.is_none() {
|
||||||
|
error!("Please specify an account address or a mnemonic to get the balance for");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let address = args
|
||||||
|
.address
|
||||||
|
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
|
||||||
|
|
||||||
|
info!("Getting balance for {}...", address);
|
||||||
|
|
||||||
|
match client.get_all_balances(&address).await {
|
||||||
|
Ok(coins) => {
|
||||||
|
if coins.is_empty() {
|
||||||
|
println!("No balance");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let denom = args.denom.unwrap_or_else(|| "".to_string());
|
||||||
|
|
||||||
|
for coin in coins {
|
||||||
|
if denom.is_empty() || denom.eq_ignore_ascii_case(&coin.denom) {
|
||||||
|
if args.raw {
|
||||||
|
if !args.hide_denom {
|
||||||
|
println!("{}", coin);
|
||||||
|
} else {
|
||||||
|
println!("{}", coin.amount);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("{}", pretty_coin(&coin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => show_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
// allowed values are 12, 18 or 24
|
||||||
|
pub word_count: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_account(args: Args, prefix: &str) {
|
||||||
|
let word_count = args.word_count.unwrap_or(24);
|
||||||
|
let mnemonic = bip39::Mnemonic::generate(word_count).expect("failed to generate mnemonic!");
|
||||||
|
|
||||||
|
let wallet =
|
||||||
|
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic).expect("failed to build wallet!");
|
||||||
|
|
||||||
|
// Output address and mnemonics into separate lines for easier parsing
|
||||||
|
println!("{}", wallet.mnemonic());
|
||||||
|
println!("{}", wallet.try_derive_accounts().unwrap()[0].address());
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod balance;
|
||||||
|
pub mod create;
|
||||||
|
pub mod pubkey;
|
||||||
|
pub mod send;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct Account {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: Option<AccountCommands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum AccountCommands {
|
||||||
|
/// Create a new mnemonic - note, this account does not appear on the chain until the account id is used in a transaction
|
||||||
|
Create(crate::validator::account::create::Args),
|
||||||
|
/// Gets the balance of an account
|
||||||
|
Balance(crate::validator::account::balance::Args),
|
||||||
|
/// Gets the public key of an account
|
||||||
|
PubKey(crate::validator::account::pubkey::Args),
|
||||||
|
/// Sends tokens to another account
|
||||||
|
Send(crate::validator::account::send::Args),
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{error, info};
|
||||||
|
|
||||||
|
use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
|
||||||
|
use validator_client::nymd::AccountId;
|
||||||
|
|
||||||
|
use crate::context::QueryClient;
|
||||||
|
use crate::utils::show_error;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
#[clap(
|
||||||
|
help = "Optionally, show the public key for this account address, otherwise generate the account address from the mnemonic"
|
||||||
|
)]
|
||||||
|
pub address: Option<AccountId>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
#[clap(help = "If set, get the public key from the mnemonic, rather than querying for it")]
|
||||||
|
pub from_mnemonic: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_pubkey(
|
||||||
|
args: Args,
|
||||||
|
client: &QueryClient,
|
||||||
|
mnemonic: Option<bip39::Mnemonic>,
|
||||||
|
address_from_mnemonic: Option<AccountId>,
|
||||||
|
) {
|
||||||
|
if args.address.is_none() && address_from_mnemonic.is_none() {
|
||||||
|
error!("Please specify an account address or a mnemonic to get the balance for");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let address = args
|
||||||
|
.address
|
||||||
|
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
|
||||||
|
|
||||||
|
if args.from_mnemonic {
|
||||||
|
let prefix = client
|
||||||
|
.current_chain_details()
|
||||||
|
.bech32_account_prefix
|
||||||
|
.as_str();
|
||||||
|
get_pubkey_from_mnemonic(address, prefix, mnemonic.expect("mnemonic not set"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_pubkey_from_chain(address, client).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pubkey_from_mnemonic(address: AccountId, prefix: &str, mnemonic: bip39::Mnemonic) {
|
||||||
|
match DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic) {
|
||||||
|
Ok(wallet) => match wallet.try_derive_accounts() {
|
||||||
|
Ok(accounts) => match accounts.iter().find(|a| *a.address() == address) {
|
||||||
|
Some(account) => {
|
||||||
|
println!("{}", account.public_key().to_string());
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Could not derive key that matches {}", address)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to derive accounts. {}", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => show_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_pubkey_from_chain(address: AccountId, client: &QueryClient) {
|
||||||
|
info!("Getting public key for address {} from chain...", address);
|
||||||
|
match client.get_account_details(&address).await {
|
||||||
|
Ok(Some(account)) => {
|
||||||
|
if let Ok(base_account) = account.try_get_base_account() {
|
||||||
|
if let Some(pubkey) = base_account.pubkey {
|
||||||
|
println!("{}", pubkey.to_string());
|
||||||
|
} else {
|
||||||
|
println!("No account associated with address {}", address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
println!("No account associated with address {}", address);
|
||||||
|
}
|
||||||
|
Err(e) => show_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use validator_client::nymd::{AccountId, Coin};
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser, help = "The recipient account address")]
|
||||||
|
pub recipient: AccountId,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
value_parser,
|
||||||
|
help = "Amount to transfer in micro denomination (e.g. unym or unyx)"
|
||||||
|
)]
|
||||||
|
pub amount: u128,
|
||||||
|
|
||||||
|
#[clap(long, help = "Override the denomination")]
|
||||||
|
pub denom: Option<String>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub memo: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(args: Args, client: &SigningClient) {
|
||||||
|
let memo = args
|
||||||
|
.memo
|
||||||
|
.unwrap_or_else(|| "Sending tokens with nym-cli".to_owned());
|
||||||
|
let denom = args
|
||||||
|
.denom
|
||||||
|
.unwrap_or_else(|| client.current_chain_details().mix_denom.base.clone());
|
||||||
|
|
||||||
|
let coin = Coin {
|
||||||
|
denom,
|
||||||
|
amount: args.amount,
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Sending {} {} from {} to {}...",
|
||||||
|
coin.amount,
|
||||||
|
coin.denom,
|
||||||
|
client.address(),
|
||||||
|
args.recipient
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.send(&args.recipient, vec![coin], memo, None)
|
||||||
|
.await
|
||||||
|
.expect("failed to send tokens!");
|
||||||
|
|
||||||
|
info!("Sending result: {}", json!(res));
|
||||||
|
|
||||||
|
println!();
|
||||||
|
println!(
|
||||||
|
"Nodesguru: https://nym.explorers.guru/transaction/{}",
|
||||||
|
&res.hash
|
||||||
|
);
|
||||||
|
println!("Mintscan: https://www.mintscan.io/nyx/txs/{}", &res.hash);
|
||||||
|
println!("Transaction result code: {}", &res.tx_result.code.value());
|
||||||
|
println!("Transaction hash: {}", &res.hash);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use crate::context::QueryClient;
|
||||||
|
use crate::utils::show_error;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
#[clap(help = "The block height")]
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_for_block_time(args: Args, client: &QueryClient) {
|
||||||
|
match client.get_block_timestamp(Some(args.height)).await {
|
||||||
|
Ok(res) => {
|
||||||
|
println!("{}", res.to_rfc3339())
|
||||||
|
}
|
||||||
|
Err(e) => show_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use crate::context::QueryClient;
|
||||||
|
use crate::utils::show_error;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {}
|
||||||
|
|
||||||
|
pub async fn query_current_block_height(client: &QueryClient) {
|
||||||
|
match client.get_current_block_height().await {
|
||||||
|
Ok(res) => {
|
||||||
|
println!("Current block height:\n{}", res.value())
|
||||||
|
}
|
||||||
|
Err(e) => show_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use crate::context::QueryClient;
|
||||||
|
use crate::utils::show_error;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
#[clap(help = "The block height")]
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_for_block(args: Args, client: &QueryClient) {
|
||||||
|
match client.get_block(Some(args.height)).await {
|
||||||
|
Ok(res) => {
|
||||||
|
println!("{}", json!(res))
|
||||||
|
}
|
||||||
|
Err(e) => show_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod block_time;
|
||||||
|
pub mod current_height;
|
||||||
|
pub mod get;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct Block {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: Option<BlockCommands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum BlockCommands {
|
||||||
|
/// Gets a block's details and prints as JSON
|
||||||
|
Get(crate::validator::block::get::Args),
|
||||||
|
/// Gets the block time at a height
|
||||||
|
Time(crate::validator::block::block_time::Args),
|
||||||
|
/// Gets the current block height
|
||||||
|
CurrentHeight(crate::validator::block::current_height::Args),
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use cosmrs::AccountId;
|
||||||
|
use log::{error, info};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use validator_client::nymd::Coin;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
#[clap(help = "The address of contract to execute")]
|
||||||
|
pub contract_address: AccountId,
|
||||||
|
|
||||||
|
#[clap(value_parser)]
|
||||||
|
#[clap(help = "JSON encoded method arguments")]
|
||||||
|
pub json_args: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub memo: Option<String>,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
value_parser,
|
||||||
|
requires = "fundsDenom",
|
||||||
|
help = "Amount to supply as funds in micro denomination (e.g. unym or unyx)"
|
||||||
|
)]
|
||||||
|
pub funds: Option<u128>,
|
||||||
|
|
||||||
|
#[clap(long, requires = "funds", help = "Set the denomination for the funds")]
|
||||||
|
pub funds_denom: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(args: Args, client: SigningClient) {
|
||||||
|
info!("Starting contract method execution!");
|
||||||
|
|
||||||
|
let json_args: Value =
|
||||||
|
serde_json::from_str(&args.json_args).expect("Unable to parse JSON args");
|
||||||
|
|
||||||
|
let memo = args
|
||||||
|
.memo
|
||||||
|
.unwrap_or_else(|| "nym-cli execute contract method".to_owned());
|
||||||
|
|
||||||
|
let funds = match args.funds {
|
||||||
|
Some(funds) => vec![Coin::new(
|
||||||
|
funds,
|
||||||
|
args.funds_denom.expect("denom for funds not set"),
|
||||||
|
)],
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
match client
|
||||||
|
.execute(&args.contract_address, &json_args, None, memo, funds)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => info!("SUCCESS ✅\n{}", json!(res)),
|
||||||
|
Err(e) => error!("FAILURE ❌\n{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use cosmrs::{AccountId, Coin as CosmosCoin};
|
||||||
|
use log::info;
|
||||||
|
use network_defaults::NymNetworkDetails;
|
||||||
|
use validator_client::nymd::cosmwasm_client::types::{ContractCodeId, InstantiateOptions};
|
||||||
|
use validator_client::nymd::Coin;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
pub code_id: ContractCodeId,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub memo: Option<String>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub label: Option<String>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub init_message: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub admin: Option<AccountId>,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
requires = "fundsDenom",
|
||||||
|
help = "Amount to supply as funds in micro denomination (e.g. unym or unyx)"
|
||||||
|
)]
|
||||||
|
pub funds: Option<u128>,
|
||||||
|
|
||||||
|
#[clap(long, requires = "funds", help = "Set the denomination for the funds")]
|
||||||
|
pub funds_denom: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn init(args: Args, client: SigningClient, network_details: &NymNetworkDetails) {
|
||||||
|
info!("Starting contract instantiation!");
|
||||||
|
|
||||||
|
let memo = args
|
||||||
|
.memo
|
||||||
|
.unwrap_or_else(|| "contract instantiation".to_owned());
|
||||||
|
let label = args
|
||||||
|
.label
|
||||||
|
.unwrap_or_else(|| "Nym mixnet smart contract".to_owned());
|
||||||
|
|
||||||
|
let funds: Vec<CosmosCoin> = match args.funds {
|
||||||
|
Some(funds) => vec![Coin::new(
|
||||||
|
funds,
|
||||||
|
args.funds_denom
|
||||||
|
.unwrap_or_else(|| network_details.chain_details.mix_denom.base.to_string()),
|
||||||
|
)
|
||||||
|
.into()],
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
// by default we make ourselves an admin, let me know if you don't like that behaviour
|
||||||
|
let opts = Some(InstantiateOptions {
|
||||||
|
funds,
|
||||||
|
admin: Some(args.admin.unwrap_or_else(|| client.address().clone())),
|
||||||
|
});
|
||||||
|
|
||||||
|
let msg: serde_json::Value =
|
||||||
|
serde_json::from_str(&args.init_message).expect("failed to parse init message");
|
||||||
|
|
||||||
|
// the EmptyMsg{} argument is equivalent to `--init-message='{}'`
|
||||||
|
let res = client
|
||||||
|
.instantiate(args.code_id, &msg, label, memo, opts, None)
|
||||||
|
.await
|
||||||
|
.expect("failed to instantiate the contract!");
|
||||||
|
|
||||||
|
info!("Init result: {:?}", res);
|
||||||
|
|
||||||
|
println!("{}", res.contract_address)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use crate::utils::show_error_passthrough;
|
||||||
|
use clap::Parser;
|
||||||
|
use cosmrs::AccountId;
|
||||||
|
use log::info;
|
||||||
|
use validator_client::nymd::cosmwasm_client::types::{ContractCodeId, EmptyMsg};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
pub contract_address: AccountId,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub code_id: ContractCodeId,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub memo: Option<String>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub init_message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn migrate(args: Args, client: SigningClient) {
|
||||||
|
println!("Starting contract migration!");
|
||||||
|
|
||||||
|
let memo = args.memo.unwrap_or_else(|| "contract migration".to_owned());
|
||||||
|
let contract_address = args.contract_address;
|
||||||
|
|
||||||
|
// the EmptyMsg{} argument is equivalent to `--init-message='{}'`
|
||||||
|
let res = if let Some(raw_msg) = args.init_message {
|
||||||
|
let msg: serde_json::Value =
|
||||||
|
serde_json::from_str(&raw_msg).expect("failed to parse init message");
|
||||||
|
|
||||||
|
client
|
||||||
|
.migrate(&contract_address, args.code_id, &msg, memo, None)
|
||||||
|
.await
|
||||||
|
.map_err(show_error_passthrough)
|
||||||
|
.expect("failed to migrate the contract!")
|
||||||
|
} else {
|
||||||
|
client
|
||||||
|
.migrate(&contract_address, args.code_id, &EmptyMsg {}, memo, None)
|
||||||
|
.await
|
||||||
|
.map_err(show_error_passthrough)
|
||||||
|
.expect("failed to migrate the contract!")
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Migrate result: {:?}", res);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod execute_contract;
|
||||||
|
pub mod init_contract;
|
||||||
|
pub mod migrate_contract;
|
||||||
|
pub mod upload_contract;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct Cosmwasm {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: Option<CosmwasmCommands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum CosmwasmCommands {
|
||||||
|
/// Upload a smart contract WASM blob
|
||||||
|
Upload(crate::validator::cosmwasm::upload_contract::Args),
|
||||||
|
/// Init a WASM smart contract
|
||||||
|
Init(crate::validator::cosmwasm::init_contract::Args),
|
||||||
|
/// Migrate a WASM smart contract
|
||||||
|
Migrate(crate::validator::cosmwasm::migrate_contract::Args),
|
||||||
|
/// Execute a WASM smart contract method
|
||||||
|
Execute(crate::validator::cosmwasm::execute_contract::Args),
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub wasm_path: PathBuf,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub memo: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn upload(args: Args, client: SigningClient) {
|
||||||
|
info!("Starting contract upload!");
|
||||||
|
|
||||||
|
let mut file = std::fs::File::open(args.wasm_path).expect("failed to open the wasm blob");
|
||||||
|
let mut data = Vec::new();
|
||||||
|
|
||||||
|
file.read_to_end(&mut data).unwrap();
|
||||||
|
|
||||||
|
let memo = args.memo.unwrap_or_else(|| "contract upload".to_owned());
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.upload(data, memo, None)
|
||||||
|
.await
|
||||||
|
.expect("failed to upload the contract!");
|
||||||
|
|
||||||
|
info!("Upload result: {:?}", res);
|
||||||
|
|
||||||
|
println!("{}", res.code_id)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
use mixnet_contract_common::Coin;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub amount: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delegate_to_mixnode(args: Args, client: SigningClient) {
|
||||||
|
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||||
|
|
||||||
|
info!("Starting delegation to mixnode");
|
||||||
|
|
||||||
|
let coin = Coin::new(args.amount, denom);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.delegate_to_mixnode(&*args.identity_key, coin.into(), None)
|
||||||
|
.await
|
||||||
|
.expect("failed to delegate to mixnode!");
|
||||||
|
|
||||||
|
info!("delegating to mixnode: {:?}", res);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod rewards;
|
||||||
|
|
||||||
|
pub mod delegate_to_mixnode;
|
||||||
|
pub mod query_for_delegations;
|
||||||
|
pub mod undelegate_from_mixnode;
|
||||||
|
pub mod vesting_delegate_to_mixnode;
|
||||||
|
pub mod vesting_undelegate_from_mixnode;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct MixnetDelegators {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetDelegatorsCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetDelegatorsCommands {
|
||||||
|
/// Lists current delegations
|
||||||
|
List(query_for_delegations::Args),
|
||||||
|
/// Manage rewards from delegations
|
||||||
|
Rewards(rewards::MixnetDelegatorsReward),
|
||||||
|
/// Delegate to a mixnode
|
||||||
|
Delegate(delegate_to_mixnode::Args),
|
||||||
|
/// Undelegate from a mixnode
|
||||||
|
Undelegate(undelegate_from_mixnode::Args),
|
||||||
|
/// Delegate to a mixnode with locked tokens
|
||||||
|
DelegateVesting(vesting_delegate_to_mixnode::Args),
|
||||||
|
/// Undelegate from a mixnode (when originally using locked tokens)
|
||||||
|
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use crate::context::SigningClientWithValidatorAPI;
|
||||||
|
use crate::utils::{pretty_cosmwasm_coin, show_error_passthrough};
|
||||||
|
|
||||||
|
use comfy_table::Table;
|
||||||
|
use mixnet_contract_common::mixnode::DelegationEvent;
|
||||||
|
use mixnet_contract_common::Delegation;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {}
|
||||||
|
|
||||||
|
pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
|
||||||
|
info!(
|
||||||
|
"Getting delegations for account {}...",
|
||||||
|
client.nymd.address()
|
||||||
|
);
|
||||||
|
|
||||||
|
let delegations = client
|
||||||
|
.get_all_delegator_delegations(client.nymd.address())
|
||||||
|
.await
|
||||||
|
.map_err(show_error_passthrough);
|
||||||
|
|
||||||
|
let mixnet_contract_events = client
|
||||||
|
.nymd
|
||||||
|
.get_pending_delegation_events(client.nymd.address().to_string(), None)
|
||||||
|
.await
|
||||||
|
.map_err(show_error_passthrough);
|
||||||
|
|
||||||
|
let vesting_contract = client.nymd.vesting_contract_address();
|
||||||
|
|
||||||
|
let vesting_contract_events = client
|
||||||
|
.nymd
|
||||||
|
.get_pending_delegation_events(
|
||||||
|
client.nymd.address().to_string(),
|
||||||
|
Some(vesting_contract.to_string()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(show_error_passthrough);
|
||||||
|
|
||||||
|
if let Ok(res) = delegations {
|
||||||
|
println!();
|
||||||
|
if res.is_empty() {
|
||||||
|
println!("This account has not delegated any tokens to mixnodes");
|
||||||
|
} else {
|
||||||
|
println!("Delegations:");
|
||||||
|
print_delegations(res, &client).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(res) = mixnet_contract_events {
|
||||||
|
if !res.is_empty() {
|
||||||
|
println!();
|
||||||
|
println!("Pending delegations (liquid tokens):");
|
||||||
|
print_delegation_events(res, &client).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(res) = vesting_contract_events {
|
||||||
|
if !res.is_empty() {
|
||||||
|
println!();
|
||||||
|
println!("Pending delegations (locked tokens):");
|
||||||
|
print_delegation_events(res, &client).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidatorAPI) -> String {
|
||||||
|
match client.nymd.get_block_timestamp(Some(block_height)).await {
|
||||||
|
Ok(res) => res.to_rfc3339(),
|
||||||
|
Err(_e) => "-".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientWithValidatorAPI) {
|
||||||
|
let mut table = Table::new();
|
||||||
|
|
||||||
|
table.set_header(vec!["Timestamp", "Identity Key", "Delegation", "Proxy"]);
|
||||||
|
|
||||||
|
for delegation in delegations {
|
||||||
|
table.add_row(vec![
|
||||||
|
to_iso_timestamp(delegation.block_height as u32, client).await,
|
||||||
|
delegation.node_identity.to_string(),
|
||||||
|
pretty_cosmwasm_coin(&delegation.amount),
|
||||||
|
format!("{:?}", delegation.proxy),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{table}");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn print_delegation_events(
|
||||||
|
events: Vec<DelegationEvent>,
|
||||||
|
client: &SigningClientWithValidatorAPI,
|
||||||
|
) {
|
||||||
|
let mut table = Table::new();
|
||||||
|
|
||||||
|
table.set_header(vec![
|
||||||
|
"Timestamp",
|
||||||
|
"Identity Key",
|
||||||
|
"Delegation",
|
||||||
|
"Event Type",
|
||||||
|
]);
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
match event {
|
||||||
|
DelegationEvent::Delegate(delegation) => {
|
||||||
|
table.add_row(vec![
|
||||||
|
to_iso_timestamp(delegation.block_height as u32, client).await,
|
||||||
|
delegation.node_identity.to_string(),
|
||||||
|
pretty_cosmwasm_coin(&delegation.amount),
|
||||||
|
"Delegate".to_string(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
DelegationEvent::Undelegate(undelegate) => {
|
||||||
|
table.add_row(vec![
|
||||||
|
to_iso_timestamp(undelegate.block_height() as u32, client).await,
|
||||||
|
undelegate.mix_identity().to_string(),
|
||||||
|
"-".to_string(),
|
||||||
|
"Undelegate".to_string(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{table}");
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn claim_delegator_reward(args: Args, client: SigningClient) {
|
||||||
|
info!("Claim delegator reward");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.execute_claim_delegator_reward(args.identity_key, None)
|
||||||
|
.await
|
||||||
|
.expect("failed to claim delegator-reward");
|
||||||
|
|
||||||
|
info!("Claiming delegator reward: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod claim_delegator_reward;
|
||||||
|
pub mod vesting_claim_delegator_reward;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct MixnetDelegatorsReward {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetDelegatorsRewardCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetDelegatorsRewardCommands {
|
||||||
|
/// Claim rewards accumulated during the delegation of unlocked tokens
|
||||||
|
Claim(claim_delegator_reward::Args),
|
||||||
|
/// Claim rewards accumulated during the delegation of locked tokens
|
||||||
|
VestingClaim(vesting_claim_delegator_reward::Args),
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn vesting_claim_delegator_reward(args: Args, client: SigningClient) {
|
||||||
|
info!("Claim vesting delegator reward");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.execute_vesting_claim_delegator_reward(args.identity, None)
|
||||||
|
.await
|
||||||
|
.expect("failed to claim vesting delegator-reward");
|
||||||
|
|
||||||
|
info!("Claiming vesting delegator reward: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn undelegate_from_mixnode(args: Args, client: SigningClient) {
|
||||||
|
info!("removing stake from mix-node");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.remove_mixnode_delegation(&*args.identity_key, None)
|
||||||
|
.await
|
||||||
|
.expect("failed to remove stake from mixnode!");
|
||||||
|
|
||||||
|
info!("removing stake from mixnode: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use mixnet_contract_common::Coin;
|
||||||
|
use validator_client::nymd::VestingSigningClient;
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub amount: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn vesting_delegate_to_mixnode(args: Args, client: SigningClient) {
|
||||||
|
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||||
|
|
||||||
|
info!("Starting vesting delegation to mixnode");
|
||||||
|
|
||||||
|
let coin = Coin::new(args.amount, denom);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.vesting_delegate_to_mixnode(&*args.identity_key, coin.into(), None)
|
||||||
|
.await
|
||||||
|
.expect("failed to delegate to mixnode!");
|
||||||
|
|
||||||
|
info!("vesting delegating to mixnode: {:?}", res);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use validator_client::nymd::VestingSigningClient;
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn vesting_undelegate_from_mixnode(args: Args, client: SigningClient) {
|
||||||
|
info!("removing stake from vesting mix-node");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.vesting_undelegate_from_mixnode(&*args.identity_key, None)
|
||||||
|
.await
|
||||||
|
.expect("failed to remove stake from vesting account on mixnode!");
|
||||||
|
|
||||||
|
info!("removing stake from vesting mixnode: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod delegators;
|
||||||
|
pub mod operators;
|
||||||
|
pub mod query;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct Mixnet {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetCommands {
|
||||||
|
/// Query the mixnet directory
|
||||||
|
Query(query::MixnetQuery),
|
||||||
|
/// Manage your delegations
|
||||||
|
Delegators(delegators::MixnetDelegators),
|
||||||
|
/// Manage a mixnode or gateway you operate
|
||||||
|
Operators(operators::MixnetOperators),
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{info, warn};
|
||||||
|
use mixnet_contract_common::Coin;
|
||||||
|
use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub host: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub signature: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub mix_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub clients_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub location: Option<String>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub sphinx_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
|
||||||
|
)]
|
||||||
|
pub amount: u128,
|
||||||
|
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn bond_gateway(args: Args, client: SigningClient) {
|
||||||
|
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||||
|
|
||||||
|
info!("Starting gateway bonding!");
|
||||||
|
|
||||||
|
// if we're trying to bond less than 1 token
|
||||||
|
if args.amount < 1_000_000 && !args.force {
|
||||||
|
warn!("You're trying to bond only {}{} which is less than 1 full token. Are you sure that's what you want? If so, run with `--force` or `-f` flag", args.amount, denom);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let gateway = mixnet_contract_common::Gateway {
|
||||||
|
host: args.host,
|
||||||
|
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
|
||||||
|
clients_port: args.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT),
|
||||||
|
location: args
|
||||||
|
.location
|
||||||
|
.unwrap_or_else(|| "secret gateway location".to_owned()),
|
||||||
|
sphinx_key: args.sphinx_key,
|
||||||
|
identity_key: args.identity_key,
|
||||||
|
version: args.version,
|
||||||
|
};
|
||||||
|
|
||||||
|
let coin = Coin::new(args.amount, denom);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.bond_gateway(gateway, args.signature, coin.into(), None)
|
||||||
|
.await
|
||||||
|
.expect("failed to bond gateway!");
|
||||||
|
|
||||||
|
info!("Bonding result: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod bond_gateway;
|
||||||
|
pub mod unbond_gateway;
|
||||||
|
pub mod vesting_bond_gateway;
|
||||||
|
pub mod vesting_unbond_gateway;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct MixnetOperatorsGateway {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetOperatorsGatewayCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetOperatorsGatewayCommands {
|
||||||
|
/// Bond to a gateway
|
||||||
|
Bond(bond_gateway::Args),
|
||||||
|
/// Unbound from a gateway
|
||||||
|
Unbound(unbond_gateway::Args),
|
||||||
|
/// Bond to a gateway with locked tokens
|
||||||
|
VestingBond(vesting_bond_gateway::Args),
|
||||||
|
/// Unbound from a gateway (when originally using locked tokens)
|
||||||
|
VestingUnbound(vesting_unbond_gateway::Args),
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {}
|
||||||
|
|
||||||
|
pub async fn unbond_gateway(client: SigningClient) {
|
||||||
|
info!("Starting gateway unbonding!");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.unbond_gateway(None)
|
||||||
|
.await
|
||||||
|
.expect("failed to unbond gateway!");
|
||||||
|
|
||||||
|
info!("Unbonding result: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{info, warn};
|
||||||
|
use mixnet_contract_common::{Coin, Gateway};
|
||||||
|
use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||||
|
use validator_client::nymd::VestingSigningClient;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub host: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub signature: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub mix_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub clients_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub location: Option<String>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub sphinx_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
|
||||||
|
)]
|
||||||
|
pub amount: u128,
|
||||||
|
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str) {
|
||||||
|
info!("Starting vesting gateway bonding!");
|
||||||
|
|
||||||
|
// if we're trying to bond less than 1 token
|
||||||
|
if args.amount < 1_000_000 && !args.force {
|
||||||
|
warn!("You're trying to bond only {}{} which is less than 1 full token. Are you sure that's what you want? If so, run with `--force` or `-f` flag", args.amount, denom);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let gateway = Gateway {
|
||||||
|
host: args.host,
|
||||||
|
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
|
||||||
|
clients_port: args.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT),
|
||||||
|
location: args
|
||||||
|
.location
|
||||||
|
.unwrap_or_else(|| "secret gateway location".to_owned()),
|
||||||
|
sphinx_key: args.sphinx_key,
|
||||||
|
identity_key: args.identity_key,
|
||||||
|
version: args.version,
|
||||||
|
};
|
||||||
|
|
||||||
|
let coin = Coin::new(args.amount, denom);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.vesting_bond_gateway(gateway, &*args.signature, coin.into(), None)
|
||||||
|
.await
|
||||||
|
.expect("failed to bond gateway!");
|
||||||
|
|
||||||
|
info!("Vesting bonding gateway result: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {}
|
||||||
|
|
||||||
|
pub async fn vesting_unbond_gateway(client: SigningClient) {
|
||||||
|
info!("Starting vesting gateway unbonding!");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.unbond_gateway(None)
|
||||||
|
.await
|
||||||
|
.expect("failed to unbond vesting gateway!");
|
||||||
|
|
||||||
|
info!("Unbonding vesting result: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{info, warn};
|
||||||
|
|
||||||
|
use mixnet_contract_common::Coin;
|
||||||
|
use network_defaults::{
|
||||||
|
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub host: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub signature: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub mix_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub verloc_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub http_api_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub sphinx_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub profit_margin_percent: Option<u8>,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
|
||||||
|
)]
|
||||||
|
pub amount: u128,
|
||||||
|
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn bond_mixnode(args: Args, client: SigningClient) {
|
||||||
|
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||||
|
|
||||||
|
info!("Starting mixnode bonding!");
|
||||||
|
|
||||||
|
// if we're trying to bond less than 1 token
|
||||||
|
if args.amount < 1_000_000 && !args.force {
|
||||||
|
warn!("You're trying to bond only {}{} which is less than 1 full token. Are you sure that's what you want? If so, run with `--force` or `-f` flag", args.amount, denom);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mixnode = mixnet_contract_common::MixNode {
|
||||||
|
host: args.host,
|
||||||
|
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
|
||||||
|
verloc_port: args.verloc_port.unwrap_or(DEFAULT_VERLOC_LISTENING_PORT),
|
||||||
|
http_api_port: args
|
||||||
|
.http_api_port
|
||||||
|
.unwrap_or(DEFAULT_HTTP_API_LISTENING_PORT),
|
||||||
|
sphinx_key: args.sphinx_key,
|
||||||
|
identity_key: args.identity_key,
|
||||||
|
version: args.version,
|
||||||
|
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
|
||||||
|
};
|
||||||
|
|
||||||
|
let coin = Coin::new(args.amount, denom);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.bond_mixnode(mixnode, args.signature, coin.into(), None)
|
||||||
|
.await
|
||||||
|
.expect("failed to bond mixnode!");
|
||||||
|
|
||||||
|
info!("Bonding result: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_mixnode_key(args: Args) {
|
||||||
|
let b64_decoded = base64::decode(args.key).expect("failed to decode base64 string");
|
||||||
|
let b58_encoded = bs58::encode(&b64_decoded).into_string();
|
||||||
|
|
||||||
|
println!("{}", b58_encoded)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod decode_mixnode_key;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct MixnetOperatorsMixnodeKeys {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetOperatorsMixnodeKeysCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetOperatorsMixnodeKeysCommands {
|
||||||
|
/// Decode a mixnode key
|
||||||
|
DecodeMixnodeKey(decode_mixnode_key::Args),
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod bond_mixnode;
|
||||||
|
pub mod keys;
|
||||||
|
pub mod rewards;
|
||||||
|
pub mod settings;
|
||||||
|
pub mod unbond_mixnode;
|
||||||
|
pub mod vesting_bond_mixnode;
|
||||||
|
pub mod vesting_unbond_mixnode;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct MixnetOperatorsMixnode {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetOperatorsMixnodeCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetOperatorsMixnodeCommands {
|
||||||
|
/// Operations for mixnode keys
|
||||||
|
Keys(keys::MixnetOperatorsMixnodeKeys),
|
||||||
|
/// Manage your mixnode operator rewards
|
||||||
|
Rewards(rewards::MixnetOperatorsMixnodeRewards),
|
||||||
|
/// Manage your mixnode settings stored in the directory
|
||||||
|
Settings(settings::MixnetOperatorsMixnodeSettings),
|
||||||
|
/// Bond to a mixnode
|
||||||
|
Bond(bond_mixnode::Args),
|
||||||
|
/// Unbound from a mixnode
|
||||||
|
Unbound(unbond_mixnode::Args),
|
||||||
|
/// Bond to a mixnode with locked tokens
|
||||||
|
BondVesting(vesting_bond_mixnode::Args),
|
||||||
|
/// Unbound from a mixnode (when originally using locked tokens)
|
||||||
|
UnboundVesting(vesting_unbond_mixnode::Args),
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {}
|
||||||
|
|
||||||
|
pub async fn claim_operator_reward(_args: Args, client: SigningClient) {
|
||||||
|
info!("Claim operator reward");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.execute_claim_operator_reward(None)
|
||||||
|
.await
|
||||||
|
.expect("failed to claim operator reward");
|
||||||
|
|
||||||
|
info!("Claiming operator reward: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod claim_operator_reward;
|
||||||
|
pub mod vesting_claim_operator_reward;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct MixnetOperatorsMixnodeRewards {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetOperatorsMixnodeRewardsCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetOperatorsMixnodeRewardsCommands {
|
||||||
|
/// Claim rewards
|
||||||
|
Claim(claim_operator_reward::Args),
|
||||||
|
/// Claim rewards for a mixnode bonded with locked tokens
|
||||||
|
VestingClaim(vesting_claim_operator_reward::Args),
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub gas: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn vesting_claim_operator_reward(client: SigningClient) {
|
||||||
|
info!("Claim vesting operator reward");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.execute_vesting_claim_operator_reward(None)
|
||||||
|
.await
|
||||||
|
.expect("failed to claim vesting operator reward");
|
||||||
|
|
||||||
|
info!("Claiming vesting operator reward: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod update_profit_percent;
|
||||||
|
pub mod vesting_update_profit_percent;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct MixnetOperatorsMixnodeSettings {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetOperatorsMixnodeSettingsCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetOperatorsMixnodeSettingsCommands {
|
||||||
|
/// Update profit percentage
|
||||||
|
UpdateProfitPercentage(update_profit_percent::Args),
|
||||||
|
/// Update profit percentage for a mixnode bonded with locked tokens
|
||||||
|
VestingUpdateProfitPercentage(vesting_update_profit_percent::Args),
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub profit_percent: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_profit_percent(args: Args, client: SigningClient) {
|
||||||
|
info!("Update mix node profit percent - get those rewards!");
|
||||||
|
|
||||||
|
//profit percent between 1-100
|
||||||
|
let res = client
|
||||||
|
.update_mixnode_config(args.profit_percent, None)
|
||||||
|
.await
|
||||||
|
.expect("updating mix-node profit percent");
|
||||||
|
|
||||||
|
info!("profit percentage updated: {:?}", res)
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
use validator_client::nymd::VestingSigningClient;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub profit_percent: u8,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub gas: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn vesting_update_profit_percent(client: SigningClient, args: Args) {
|
||||||
|
info!("Update vesting mix node profit percent - get those rewards!");
|
||||||
|
|
||||||
|
//profit percent between 1-100
|
||||||
|
let res = client
|
||||||
|
.vesting_update_mixnode_config(args.profit_percent, None)
|
||||||
|
.await
|
||||||
|
.expect("updating vesting mix-node profit percent");
|
||||||
|
|
||||||
|
info!("profit percentage updated: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {}
|
||||||
|
|
||||||
|
pub async fn unbond_mixnode(_args: Args, client: SigningClient) {
|
||||||
|
info!("Starting mixnode unbonding!");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.unbond_mixnode(None)
|
||||||
|
.await
|
||||||
|
.expect("failed to unbond mixnode!");
|
||||||
|
|
||||||
|
info!("Unbonding result: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{info, warn};
|
||||||
|
use mixnet_contract_common::Coin;
|
||||||
|
use mixnet_contract_common::MixNode;
|
||||||
|
use network_defaults::{
|
||||||
|
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
|
||||||
|
};
|
||||||
|
use validator_client::nymd::VestingSigningClient;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub host: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub signature: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub mix_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub verloc_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub http_api_port: Option<u16>,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub sphinx_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub identity_key: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub profit_margin_percent: Option<u8>,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
|
||||||
|
)]
|
||||||
|
pub amount: u128,
|
||||||
|
|
||||||
|
#[clap(long)]
|
||||||
|
pub gas: Option<u64>,
|
||||||
|
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn vesting_bond_mixnode(client: SigningClient, args: Args, denom: &str) {
|
||||||
|
info!("Starting vesting mixnode bonding!");
|
||||||
|
|
||||||
|
// if we're trying to bond less than 1 token
|
||||||
|
if args.amount < 1_000_000 && !args.force {
|
||||||
|
warn!("You're trying to bond only {}{} which is less than 1 full token. Are you sure that's what you want? If so, run with `--force` or `-f` flag", args.amount, denom);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mixnode = MixNode {
|
||||||
|
host: args.host,
|
||||||
|
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
|
||||||
|
verloc_port: args.verloc_port.unwrap_or(DEFAULT_VERLOC_LISTENING_PORT),
|
||||||
|
http_api_port: args
|
||||||
|
.http_api_port
|
||||||
|
.unwrap_or(DEFAULT_HTTP_API_LISTENING_PORT),
|
||||||
|
sphinx_key: args.sphinx_key,
|
||||||
|
identity_key: args.identity_key,
|
||||||
|
version: args.version,
|
||||||
|
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
|
||||||
|
};
|
||||||
|
|
||||||
|
let coin = Coin::new(args.amount, denom);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.vesting_bond_mixnode(mixnode, &*args.signature, coin.into(), None)
|
||||||
|
.await
|
||||||
|
.expect("failed to bond vesting mixnode!");
|
||||||
|
|
||||||
|
info!("Bonding vesting result: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::context::SigningClient;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
use validator_client::nymd::VestingSigningClient;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub gas: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn vesting_unbond_mixnode(client: SigningClient) {
|
||||||
|
info!("Starting vesting mixnode unbonding!");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.vesting_unbond_mixnode(None)
|
||||||
|
.await
|
||||||
|
.expect("failed to unbond vesting mixnode!");
|
||||||
|
|
||||||
|
info!("Unbonding vesting result: {:?}", res)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod gateway;
|
||||||
|
pub mod mixnode;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct MixnetOperators {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetOperatorsCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetOperatorsCommands {
|
||||||
|
/// Manage your mixnode
|
||||||
|
Mixnode(mixnode::MixnetOperatorsMixnode),
|
||||||
|
/// Manage your gateway
|
||||||
|
Gateway(gateway::MixnetOperatorsGateway),
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod query_all_gateways;
|
||||||
|
pub mod query_all_mixnodes;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct MixnetQuery {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: MixnetQueryCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum MixnetQueryCommands {
|
||||||
|
/// Query mixnodes
|
||||||
|
Mixnodes(query_all_mixnodes::Args),
|
||||||
|
/// Query gateways
|
||||||
|
Gateways(query_all_gateways::Args),
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use comfy_table::Table;
|
||||||
|
|
||||||
|
use crate::context::QueryClientWithValidatorAPI;
|
||||||
|
use crate::utils::{pretty_cosmwasm_coin, show_error};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
#[clap(help = "Optionally, the gateway to display")]
|
||||||
|
pub identity_key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
|
||||||
|
match client.validator_api.get_gateways().await {
|
||||||
|
Ok(res) => match args.identity_key {
|
||||||
|
Some(identity_key) => {
|
||||||
|
let node = res.iter().find(|node| {
|
||||||
|
node.gateway
|
||||||
|
.identity_key
|
||||||
|
.to_string()
|
||||||
|
.eq_ignore_ascii_case(&identity_key)
|
||||||
|
});
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
::serde_json::to_string_pretty(&node).expect("json formatting error")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut table = Table::new();
|
||||||
|
|
||||||
|
table.set_header(vec!["Identity Key", "Owner", "Host", "Bond", "Version"]);
|
||||||
|
for node in res {
|
||||||
|
table.add_row(vec![
|
||||||
|
node.gateway.identity_key.to_string(),
|
||||||
|
node.owner.to_string(),
|
||||||
|
node.gateway.host.to_string(),
|
||||||
|
pretty_cosmwasm_coin(&node.pledge_amount),
|
||||||
|
node.gateway.version,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("The gateways in the directory are:");
|
||||||
|
println!("{table}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => show_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use comfy_table::Table;
|
||||||
|
|
||||||
|
use crate::context::QueryClientWithValidatorAPI;
|
||||||
|
use crate::utils::{pretty_cosmwasm_coin, show_error};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
#[clap(help = "Optionally, the mixnode to display")]
|
||||||
|
pub identity_key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
|
||||||
|
match client.validator_api.get_mixnodes().await {
|
||||||
|
Ok(res) => match args.identity_key {
|
||||||
|
Some(identity_key) => {
|
||||||
|
let node = res.iter().find(|node| {
|
||||||
|
node.mix_node
|
||||||
|
.identity_key
|
||||||
|
.to_string()
|
||||||
|
.eq_ignore_ascii_case(&identity_key)
|
||||||
|
});
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
::serde_json::to_string_pretty(&node).expect("json formatting error")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut table = Table::new();
|
||||||
|
|
||||||
|
table.set_header(vec![
|
||||||
|
"Identity Key",
|
||||||
|
"Owner",
|
||||||
|
"Host",
|
||||||
|
"Bond",
|
||||||
|
"Total Delegations",
|
||||||
|
"Version",
|
||||||
|
]);
|
||||||
|
for node in res {
|
||||||
|
table.add_row(vec![
|
||||||
|
node.mix_node.identity_key.to_string(),
|
||||||
|
node.owner.to_string(),
|
||||||
|
node.mix_node.host.to_string(),
|
||||||
|
pretty_cosmwasm_coin(&node.pledge_amount),
|
||||||
|
pretty_cosmwasm_coin(&node.total_delegation()),
|
||||||
|
node.mix_node.version,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("The mixnodes in the directory are:");
|
||||||
|
println!("{table}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => show_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
pub mod account;
|
||||||
|
pub mod block;
|
||||||
|
pub mod cosmwasm;
|
||||||
|
pub mod mixnet;
|
||||||
|
pub mod signature;
|
||||||
|
pub mod transactions;
|
||||||
|
pub mod vesting;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Errors {
|
||||||
|
#[error("signature error - {0}")]
|
||||||
|
SignatureError(#[from] k256::ecdsa::signature::Error),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
CosmrsError(#[from] cosmrs::ErrorReport),
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use cosmrs::crypto::secp256k1::{Signature, VerifyingKey};
|
||||||
|
use cosmrs::crypto::PublicKey;
|
||||||
|
use k256::ecdsa::signature::Verifier;
|
||||||
|
|
||||||
|
use crate::validator::signature::errors::Errors;
|
||||||
|
|
||||||
|
pub fn secp256k1_verify_with_public_key(
|
||||||
|
public_key_as_bytes: &[u8],
|
||||||
|
signature_as_hex: String,
|
||||||
|
message: String,
|
||||||
|
) -> Result<(), k256::ecdsa::signature::Error> {
|
||||||
|
let verifying_key = VerifyingKey::from_sec1_bytes(public_key_as_bytes)?;
|
||||||
|
let signature = Signature::from_str(&signature_as_hex)?;
|
||||||
|
let message_as_bytes = message.into_bytes();
|
||||||
|
verifying_key.verify(&message_as_bytes, &signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn secp256k1_verify_with_public_key_json(
|
||||||
|
public_key_as_json: String,
|
||||||
|
signature_as_hex: String,
|
||||||
|
message: String,
|
||||||
|
) -> Result<(), Errors> {
|
||||||
|
let public_key = PublicKey::from_json(&public_key_as_json)?;
|
||||||
|
let verifying_key = VerifyingKey::from_sec1_bytes(&public_key.to_bytes())?;
|
||||||
|
let signature = Signature::from_str(&signature_as_hex)?;
|
||||||
|
let message_as_bytes = message.into_bytes();
|
||||||
|
Ok(verifying_key.verify(&message_as_bytes, &signature)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_secp256k1 {
|
||||||
|
use crate::validator::signature::helpers::{
|
||||||
|
secp256k1_verify_with_public_key, secp256k1_verify_with_public_key_json,
|
||||||
|
};
|
||||||
|
use cosmrs::crypto::PublicKey;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_with_json_public_key_with_valid_signature() {
|
||||||
|
let json_public_key = r#"{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4FdhUMasPmNhRZjtpKlmjNbq7EEUgPxfdI+E3vSajvc"}"#;
|
||||||
|
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||||
|
let message = "test 1234".to_string();
|
||||||
|
|
||||||
|
let public_key = PublicKey::from_json(json_public_key).unwrap();
|
||||||
|
let public_key_bytes = public_key.to_bytes();
|
||||||
|
|
||||||
|
let result = secp256k1_verify_with_public_key(&public_key_bytes, signature_as_hex, message);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_with_json_public_key_with_invalid_signature() {
|
||||||
|
let json_public_key = r#"{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4FdhUMasPmNhRZjtpKlmjNbq7EEUgPxfdI+E3vSajvc"}"#;
|
||||||
|
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||||
|
let message = "abcdef".to_string();
|
||||||
|
|
||||||
|
let public_key = PublicKey::from_json(json_public_key).unwrap();
|
||||||
|
let public_key_bytes = public_key.to_bytes();
|
||||||
|
|
||||||
|
let result = secp256k1_verify_with_public_key(&public_key_bytes, signature_as_hex, message);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_json_public_key_succeeds() {
|
||||||
|
let json_public_key = r#"{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4FdhUMasPmNhRZjtpKlmjNbq7EEUgPxfdI+E3vSajvc"}"#.to_string();
|
||||||
|
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||||
|
let message = "test 1234".to_string();
|
||||||
|
|
||||||
|
let result =
|
||||||
|
secp256k1_verify_with_public_key_json(json_public_key, signature_as_hex, message);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_json_public_key_fails_with_error() {
|
||||||
|
let bad_json_public_key = r#"This is not JSON ☠️"#.to_string();
|
||||||
|
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||||
|
let message = "abcdef".to_string();
|
||||||
|
|
||||||
|
let result =
|
||||||
|
secp256k1_verify_with_public_key_json(bad_json_public_key, signature_as_hex, message);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod errors;
|
||||||
|
pub mod helpers;
|
||||||
|
pub mod sign;
|
||||||
|
pub mod verify;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||||
|
pub struct Signature {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: Option<SignatureCommands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum SignatureCommands {
|
||||||
|
/// Sign a message
|
||||||
|
Sign(crate::validator::signature::sign::Args),
|
||||||
|
/// Verify a message
|
||||||
|
Verify(crate::validator::signature::verify::Args),
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::utils::show_error;
|
||||||
|
use clap::Parser;
|
||||||
|
use cosmrs::crypto::PublicKey;
|
||||||
|
use log::error;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::json;
|
||||||
|
use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct SignatureOutputJson {
|
||||||
|
pub account_id: String,
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
pub signature: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(value_parser)]
|
||||||
|
#[clap(help = "The message to sign")]
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(args: Args, prefix: &str, mnemonic: Option<bip39::Mnemonic>) {
|
||||||
|
if args.message.trim().is_empty() {
|
||||||
|
error!("Message is empty or contains only whitespace");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if mnemonic.is_none() {
|
||||||
|
error!(
|
||||||
|
"Please provide the mnemonic as an argument or using the MNEMONIC environment variable"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.expect("mnemonic not set")) {
|
||||||
|
Ok(wallet) => match wallet.try_derive_accounts() {
|
||||||
|
Ok(accounts) => match accounts.first() {
|
||||||
|
Some(account) => {
|
||||||
|
let msg = args.message.into_bytes();
|
||||||
|
match wallet.sign_raw_with_account(account, &msg) {
|
||||||
|
Ok(signature) => {
|
||||||
|
let output = SignatureOutputJson {
|
||||||
|
account_id: account.address().to_string(),
|
||||||
|
public_key: account.public_key(),
|
||||||
|
signature: signature.to_string(),
|
||||||
|
};
|
||||||
|
println!("{}", json!(output));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to sign message. {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Could not derive an account key from the mnemonic",)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to derive accounts. {}", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => show_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user