Compare commits

..

35 Commits

Author SHA1 Message Date
Jon Häggblad 230536d0d8 Somewhat working 2022-11-11 14:36:10 +01:00
Jon Häggblad 025fdc913d WIP 2022-11-11 10:23:44 +01:00
Jon Häggblad c175401562 WIP 2022-11-11 09:39:50 +01:00
Jon Häggblad 369c72ad76 WIP 2022-11-10 16:19:45 +01:00
Jon Häggblad 8f1cb67bf7 Update client-core to 1.1.0 2022-11-10 09:44:20 +01:00
Mark Sinclair 89925e49e8 GH Actions: Install same dependencies as build..yml 2022-11-09 18:20:45 +00:00
Mark Sinclair 96fc7208a2 Merge branch 'release/v1.1.0' into develop 2022-11-09 15:27:56 +00:00
Jon Häggblad b8cb683da0 Add qwerty qa net env file 2022-11-09 11:27:28 +01:00
Jon Häggblad d79c25861b Merge pull request #1740 from nymtech/develop-with-release-1.1.0-merged-in
Merge release/v1.1.0 into develop
2022-11-09 11:02:12 +01:00
Jon Häggblad b89ec2e0be clippy 2022-11-09 10:43:53 +01:00
Jon Häggblad 269f50bdd4 rustfmt 2022-11-09 10:25:39 +01:00
durch e3c02dc80a Actually save updated pledge cap 2022-11-09 10:22:14 +01:00
Drazen Urch 65a9320d35 Set default pledge cap to 10% (#1739)
* Set default pledge cap to 10%

* fix clippy beta lints
2022-11-09 10:22:08 +01:00
Jon Häggblad 9b14e00653 Fix merge error 2022-11-07 13:55:36 +01:00
Jon Häggblad ec8b5e6e9d Merge remote-tracking branch 'origin/release/v1.1.0' into develop 2022-11-07 10:13:33 +01:00
Raphaël Walther d4584c305a Set cron 2022-11-04 15:50:08 +01:00
Raphaël Walther afc53d4379 Added audit notification 2022-11-04 15:12:29 +01:00
Raphaël Walther 4278e88d3c Added audit notification 2022-11-04 14:55:47 +01:00
Raphaël Walther e12a34ce6b Added audit notification 2022-11-04 11:58:23 +01:00
Raphaël Walther 1de64f7b52 Added audit notification 2022-11-04 11:42:31 +01:00
Raphaël Walther 66dbe09e66 Added audit notification 2022-11-04 11:22:54 +01:00
Raphaël Walther dcce269921 Added audit notification 2022-11-04 09:56:00 +01:00
Jędrzej Stuczyński c043f0096a Notify about sent packet after actually pushing it through mix_tx (#1735) 2022-11-03 15:11:58 +00:00
Jędrzej Stuczyński fe6da046dc Fixed beta clippy warnings (#1736) 2022-11-03 10:13:16 +00:00
Jon Häggblad 99b30c2570 client: additional error handling in client + socks5-client + network-requester (#1713)
* client: add error type to native client, and start handling them

* client: handle two more error cases

* changelog: add note

* socks5: add error type and start handle run errors

* network-requester: add some error types

* rustfmt

* changelog: update note

* network-requester: remove unused import
2022-10-27 16:00:26 +02:00
Jon Häggblad 2c5d31e685 client: make channel to mix traffic controller bounded and add backpressure handling (#1703)
* clients: change mix traffic channel to bounded

* clients: dynamically adjust sending delay in steps

* rustfmt

* wasm-client: update channel

* client: introduce SendingDelayController

* client-core: downgrade two debug statements to trace

* sending delay controller: tweak parameters

* wasm-client: add tokio dependency

* client-core: rework delay controller

* Revert "client-core: downgrade two debug statements to trace"

This reverts commit e0a7772fafac7bff0e4a2c50ba25e94b52b794e6.

* Remove outdated comment

* Remove WIP comments

* changelog: add note

* out queue controller: simplify with just send

* client-core: document constants

* client: move creating mix msg channel to mix traffic controller

* client-core: downgrade a warning log msg to debug
2022-10-27 15:23:25 +02:00
Jon Häggblad cf65bc1295 socks5: wait to close buffer (#1702)
* socks5: wait to close buffer

This is the fix proposed by @simonwicky in
https://github.com/nymtech/nym/issues/1701

* socks5: fix typo in patch

* socks5: fix tests

* socks5: add type for returned data and index

* socks5: make closed_at_index an Option

* changelog: add note
2022-10-27 12:42:01 +02:00
Jędrzej Stuczyński 2d5f851252 Workaround for clippy #9612 issue 2022-10-27 10:47:13 +01:00
Jędrzej Stuczyński 5ce087dafe Chore/remove unwraps (#1707)
* Disallowing the use of unwraps and expects in vesting and mixnet contracts

* Removed dodgy unwraps from the mixnet contract

* Removed dodgy unwraps from the vesting contract

* Removed unwraps/expects from common contracts crate

* ...but adding the unwraps in tests
2022-10-26 16:48:06 +01:00
Dave Hrycyszyn caf03a09c8 Adding in wss fix for web version 2022-10-26 16:34:09 +01:00
Dave Hrycyszyn 5db47b8931 Optimizing for fat wasm 2022-10-26 15:40:03 +01:00
Dave Hrycyszyn 27c1b29615 Removing accidental yarn lockfile 2022-10-26 15:38:54 +01:00
Dave Hrycyszyn c80c8ef899 Bumping version number of wasm client package 2022-10-26 14:36:33 +01:00
pierre d0cd22c4da Revert "fix(explorer-api): geoip, ip address from domain"
This reverts commit a721e97c06.
2022-10-26 11:57:04 +02:00
pierre a721e97c06 fix(explorer-api): geoip, ip address from domain 2022-10-26 11:52:54 +02:00
82 changed files with 1276 additions and 2889 deletions
+27 -14
View File
@@ -1,36 +1,49 @@
name: Daily security audit
on: workflow_dispatch
on:
schedule:
- cron: '5 9 * * *'
jobs:
security_audit:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/audit-check@v1
- name: Checkout repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
toolchain: stable
- name: Install cargo deny
run: cargo install --locked cargo-deny
- name: Run cargo deny
run: cargo deny check advisories --hide-inclusion-graph &> .github/workflows/support-files/notifications/deny.message
- uses: actions/upload-artifact@v3
with:
name: report
path: .github/workflows/support-files/notifications/deny.message
notification:
if: ${{ failure() }}
needs: security_audit
needs: cargo-deny
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v2
- name: Download report from previous job
uses: actions/download-artifact@v3
with:
name: report
path: .github/workflows/support-files/notifications
- 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: "Nym daily audit"
NYM_NOTIFICATION_KIND: security
NYM_PROJECT_NAME: "Daily security report"
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_NYMBTECH_TEAM }}"
KEYBASE_NYM_CHANNEL: "test"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "security"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+5 -18
View File
@@ -1,21 +1,16 @@
name: Build release of Nym smart contracts
on:
workflow_dispatch:
release:
types: [created]
defaults:
run:
working-directory: contracts
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check the release tag starts with `nym-contracts-`
if: startsWith(github.ref, 'refs/tags/nym-contracts-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-contracts-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
@@ -26,7 +21,7 @@ jobs:
components: rustfmt, clippy
- name: Build release contracts
run: make wasm
run: RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
- name: Upload Mixnet Contract Artifact
uses: actions/upload-artifact@v3
@@ -41,11 +36,3 @@ jobs:
name: vesting_contract.wasm
path: contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
retention-days: 5
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
@@ -1,56 +0,0 @@
name: CI for Network Explorer API
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check the release tag starts with `nym-explorer-api-`
if: startsWith(github.ref, 'refs/tags/nym-explorer-api-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-explorer-api-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build all explorer-api
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path explorer-api/Cargo.toml --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact
path: |
target/release/explorer-api
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/explorer-api
@@ -3,7 +3,7 @@ require('dotenv').config();
const Bot = require('keybase-bot');
let context = {
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect'],
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security'],
};
/**
@@ -0,0 +1,24 @@
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
const { Octokit, App } = require('octokit');
async function addToContextAndValidate(context) {
return
}
async function getMessageBody(context) {
try {
const source = fs
.readFileSync("deny.message").toString();
return source;
} catch (error) {
console.error(error);
}
}
module.exports = {
addToContextAndValidate,
getMessageBody,
};
Generated
+2
View File
@@ -3147,6 +3147,7 @@ dependencies = [
"serde_json",
"sled",
"task",
"thiserror",
"tokio",
"tokio-tungstenite 0.14.0",
"topology",
@@ -3323,6 +3324,7 @@ dependencies = [
"snafu 0.6.10",
"socks5-requests",
"task",
"thiserror",
"tokio",
"topology",
"url",
-3
View File
@@ -92,9 +92,6 @@ build-wallet:
build-connect:
cargo build --manifest-path nym-connect/Cargo.toml --workspace
build-explorer-api:
cargo build --manifest-path explorer-api/Cargo.toml --workspace
build-wasm-client:
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
@@ -92,7 +92,8 @@ where
// while we were messing with topology, wrapping data in sphinx, etc. we actually received
// this ack after all! no need to retransmit then
debug!("We received an ack JUST as we were about to retransmit [2]");
return;
panic!("will we reach this?");
//return;
}
// we no longer need the reference - let's drop it so that if somehow `UpdateTimer` action
// reached the controller before this function terminated, the controller would not panic.
@@ -94,7 +94,7 @@ impl Config {
pub struct RealMessagesController<R>
where
R: CryptoRng + Rng,
R: CryptoRng + Rng + Clone,
{
out_queue_control: OutQueueControl<R>,
ack_control: AcknowledgementController<R>,
@@ -5,8 +5,7 @@ use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
use crate::client::topology_control::TopologyAccessor;
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use futures::{Future, Stream, StreamExt};
use futures::StreamExt;
use log::*;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
@@ -17,7 +16,6 @@ use nymsphinx::params::PacketSize;
use nymsphinx::utils::sample_poisson_duration;
use rand::{CryptoRng, Rng};
use std::collections::VecDeque;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
@@ -44,6 +42,8 @@ const MAX_DELAY_MULTIPLIER: u32 = 6;
// The minium multiplier we apply to the base average Poisson delay.
const MIN_DELAY_MULTIPLIER: u32 = 1;
type SharedSendingDelayController = Arc<std::sync::Mutex<SendingDelayController>>;
/// Configurable parameters of the `OutQueueControl`
pub(crate) struct Config {
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
@@ -182,7 +182,7 @@ impl SendingDelayController {
pub(crate) struct OutQueueControl<R>
where
R: CryptoRng + Rng,
R: CryptoRng + Rng + Clone,
{
/// Configurable parameters of the `ActionController`
config: Config,
@@ -193,17 +193,9 @@ where
/// Channel used for notifying of a real packet being sent out. Used to start up retransmission timer.
sent_notifier: SentPacketNotificationSender,
/// Internal state, determined by `average_message_sending_delay`,
/// used to keep track of when a next packet should be sent out.
#[cfg(not(target_arch = "wasm32"))]
next_delay: Option<Pin<Box<time::Sleep>>>,
#[cfg(target_arch = "wasm32")]
next_delay: Option<Pin<Box<wasm_timer::Delay>>>,
// To make sure we don't overload the mix_tx channel, we limit the rate we are pushing
// messages.
sending_rate_controller: SendingDelayController,
sending_rate_controller: SharedSendingDelayController,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
@@ -252,7 +244,7 @@ pub(crate) enum StreamMessage {
impl<R> OutQueueControl<R>
where
R: CryptoRng + Rng + Unpin,
R: CryptoRng + Rng + Unpin + Clone,
{
// at this point I'm not entirely sure how to deal with this warning without
// some considerable refactoring
@@ -271,11 +263,10 @@ where
config,
ack_key,
sent_notifier,
next_delay: None,
sending_rate_controller: SendingDelayController::new(
sending_rate_controller: Arc::new(std::sync::Mutex::new(SendingDelayController::new(
MIN_DELAY_MULTIPLIER,
MAX_DELAY_MULTIPLIER,
),
))),
mix_tx,
real_receiver,
our_full_destination,
@@ -296,7 +287,7 @@ where
async fn on_message(&mut self, next_message: StreamMessage) {
trace!("created new message");
let next_message = match next_message {
let (next_message, fragment_id) = match next_message {
StreamMessage::Cover => {
// TODO for way down the line: in very rare cases (during topology update) we might have
// to wait a really tiny bit before actually obtaining the permit hence messing with our
@@ -315,20 +306,24 @@ where
}
let topology_ref = topology_ref_option.unwrap();
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
self.config.cover_packet_size,
(
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
self.config.cover_packet_size,
)
.expect(
"Somehow failed to generate a loop cover message with a valid topology",
),
None,
)
.expect("Somehow failed to generate a loop cover message with a valid topology")
}
StreamMessage::Real(real_message) => {
self.sent_notify(real_message.fragment_id);
real_message.mix_packet
(real_message.mix_packet, Some(real_message.fragment_id))
}
};
@@ -336,6 +331,12 @@ where
log::error!("Failed to send - channel closed: {}", err);
}
// notify ack controller about sending our message only after we actually managed to push it
// through the channel
if let Some(fragment_id) = fragment_id {
self.sent_notify(fragment_id);
}
// 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
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
@@ -347,148 +348,30 @@ where
tokio::task::yield_now().await;
}
fn current_average_message_sending_delay(&self) -> Duration {
self.config.average_message_sending_delay
* self.sending_rate_controller.current_multiplier()
}
fn adjust_current_average_message_sending_delay(&mut self) {
let mut ctrl = self.sending_rate_controller.lock().unwrap();
let used_slots = self.mix_tx.max_capacity() - self.mix_tx.capacity();
log::trace!(
"used_slots: {used_slots}, current_multiplier: {}",
self.sending_rate_controller.current_multiplier()
ctrl.current_multiplier()
);
// Even just a single used slot is enough to signal backpressure
if used_slots > 0 {
log::trace!("Backpressure detected");
self.sending_rate_controller.record_backpressure_detected();
ctrl.record_backpressure_detected();
}
// If the buffer is running out, slow down the sending rate
if self.mix_tx.capacity() == 0
&& self.sending_rate_controller.not_increased_delay_recently()
{
self.sending_rate_controller.increase_delay_multiplier();
if self.mix_tx.capacity() == 0 && ctrl.not_increased_delay_recently() {
ctrl.increase_delay_multiplier();
}
// Very carefully step up the sending rate in case it seems like we can solidly handle the
// current rate.
if self.sending_rate_controller.is_sending_reliable() {
self.sending_rate_controller.decrease_delay_multiplier();
}
}
fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// The average delay could change depending on if backpressure in the downstream channel
// (mix_tx) was detected.
self.adjust_current_average_message_sending_delay();
let avg_delay = self.current_average_message_sending_delay();
if let Some(ref mut next_delay) = &mut self.next_delay {
// it is not yet time to return a message
if next_delay.as_mut().poll(cx).is_pending() {
return Poll::Pending;
};
// we know it's time to send a message, so let's prepare delay for the next one
// Get the `now` by looking at the current `delay` deadline
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
// The next interval value is `next_poisson_delay` after the one that just
// yielded.
#[cfg(not(target_arch = "wasm32"))]
{
let now = next_delay.deadline();
let next = now + next_poisson_delay;
next_delay.as_mut().reset(next);
}
#[cfg(target_arch = "wasm32")]
{
next_delay.as_mut().reset(next_poisson_delay);
}
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
}
// decide what kind of message to send
match Pin::new(&mut self.real_receiver).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
// otherwise construct a dummy one
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
}
} else {
// we never set an initial delay - let's do it now
cx.waker().wake_by_ref();
let sampled =
sample_poisson_duration(&mut self.rng, self.config.average_message_sending_delay);
#[cfg(not(target_arch = "wasm32"))]
let next_delay = Box::pin(time::sleep(sampled));
#[cfg(target_arch = "wasm32")]
let next_delay = Box::pin(wasm_timer::Delay::new(sampled));
self.next_delay = Some(next_delay);
Poll::Pending
}
}
fn poll_immediate(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
// if there are more messages immediately available, notify the runtime
// because we should be polled again
if !self.received_buffer.is_empty() {
cx.waker().wake_by_ref()
}
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
}
match Pin::new(&mut self.real_receiver).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
// if there's nothing, then there's nothing
Poll::Pending => Poll::Pending,
}
}
fn poll_next_message(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<StreamMessage>> {
if self.config.disable_poisson_packet_distribution {
self.poll_immediate(cx)
} else {
self.poll_poisson(cx)
if ctrl.is_sending_reliable() {
ctrl.decrease_delay_multiplier();
}
}
@@ -496,44 +379,76 @@ where
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started OutQueueControl with graceful shutdown support");
let mut poisson_delay_timer = PoissonDelayTimer::new(
self.config.average_message_sending_delay,
self.sending_rate_controller.clone(),
self.rng.clone(),
);
let poisson_delay_stream = poisson_delay_timer.as_stream();
tokio::pin!(poisson_delay_stream);
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 => {
messages = self.real_receiver.next() => {
if let Some(real_messages) = messages {
self.received_buffer = real_messages.into();
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
}
_ = poisson_delay_stream.next() => {
self.adjust_current_average_message_sending_delay();
let msg = match self.received_buffer.pop_front() {
Some(msg) => StreamMessage::Real(Box::new(msg)),
None => StreamMessage::Cover
};
self.on_message(msg).await;
}
}
}
assert!(shutdown.is_shutdown_poll());
log::debug!("OutQueueControl: Exiting");
}
}
#[cfg(target_arch = "wasm32")]
pub(super) async fn run(&mut self) {
debug!("Started OutQueueControl without graceful shutdown support");
struct PoissonDelayTimer<R> {
average_message_sending_delay: Duration,
sending_rate_controller: SharedSendingDelayController,
rng: R,
}
while let Some(next_message) = self.next().await {
self.on_message(next_message).await;
impl<R> PoissonDelayTimer<R>
where
R: Rng + CryptoRng,
{
fn new(
average_message_sending_delay: Duration,
sending_rate_controller: SharedSendingDelayController,
rng: R,
) -> Self {
Self {
average_message_sending_delay,
sending_rate_controller,
rng,
}
}
}
impl<R> Stream for OutQueueControl<R>
where
R: CryptoRng + Rng + Unpin,
{
type Item = StreamMessage;
fn current_average_message_sending_delay(&self) -> Duration {
let ctrl = self.sending_rate_controller.lock().unwrap();
self.average_message_sending_delay * ctrl.current_multiplier()
}
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.poll_next_message(cx)
fn as_stream(&mut self) -> impl futures::Stream<Item = ()> + '_ {
futures::stream::unfold(self, |out_queue_control| async {
let avg_delay = out_queue_control.current_average_message_sending_delay();
let next_poisson_delay = sample_poisson_duration(&mut out_queue_control.rng, avg_delay);
time::sleep(next_poisson_delay).await;
Some(((), out_queue_control))
})
}
}
@@ -57,24 +57,15 @@ impl<'a> TopologyReadPermit<'a> {
) -> Option<&'a NymTopology> {
// Note: implicit deref with Deref for TopologyReadPermit is happening here
let topology_ref_option = self.permit.as_ref();
match topology_ref_option {
None => None,
Some(topology_ref) => {
// see if it's possible to route the packet to both gateways
if !topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|| if let Some(packet_recipient) = packet_recipient {
!topology_ref.gateway_exists(packet_recipient.gateway())
} else {
false
}
{
None
topology_ref_option.as_ref().filter(|topology_ref| {
!(!topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|| if let Some(packet_recipient) = packet_recipient {
!topology_ref.gateway_exists(packet_recipient.gateway())
} else {
Some(topology_ref)
}
}
}
false
})
})
}
}
+2
View File
@@ -24,4 +24,6 @@ pub enum ClientCoreError {
ListOfValidatorApisIsEmpty,
#[error("Could not load existing gateway configuration: {0}")]
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
#[error("The current network topology seem to be insufficient to route any packets through")]
InsufficientNetworkTopology,
}
+1
View File
@@ -27,6 +27,7 @@ pretty_env_logger = "0.4" # for formatting log messages
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.34" # for storage of replySURB decryption keys
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
tokio-tungstenite = "0.14" # websocket
+13 -8
View File
@@ -18,6 +18,7 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
@@ -33,6 +34,7 @@ use nymsphinx::receiver::ReconstructedMessage;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use crate::websocket;
pub(crate) mod config;
@@ -232,7 +234,7 @@ impl NymClient {
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) {
) -> Result<(), ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -247,14 +249,16 @@ impl NymClient {
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -328,8 +332,8 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
let shutdown = self.start().await;
pub async fn run_forever(&mut self) -> Result<(), ClientError> {
let shutdown = self.start().await?;
wait_for_signal().await;
println!(
@@ -346,9 +350,10 @@ impl NymClient {
//shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-client");
Ok(())
}
pub async fn start(&mut self) -> ShutdownNotifier {
pub async fn start(&mut self) -> Result<ShutdownNotifier, ClientError> {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -379,7 +384,7 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await;
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
@@ -443,6 +448,6 @@ impl NymClient {
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
shutdown
Ok(shutdown)
}
}
+4 -2
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
@@ -83,16 +84,17 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: bool,
}
pub(crate) async fn execute(args: &Cli) {
pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> {
let bin_name = "nym-native-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Run(m) => run::execute(m).await?,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
Ok(())
}
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
+5 -4
View File
@@ -4,6 +4,7 @@
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
error::ClientError,
};
use clap::Args;
@@ -73,14 +74,14 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) {
pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
return;
return Err(ClientError::FailedToLoadConfig(id.to_string()));
}
};
@@ -89,8 +90,8 @@ pub(crate) async fn execute(args: &Run) {
if !version_check(&config) {
error!("failed the local version check");
return;
return Err(ClientError::FailedLocalVersionCheck);
}
NymClient::new(config).run_forever().await;
NymClient::new(config).run_forever().await
}
+23
View File
@@ -0,0 +1,23 @@
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[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("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
}
+1
View File
@@ -2,4 +2,5 @@
// SPDX-License-Identifier: Apache-2.0
pub mod client;
pub mod error;
pub mod websocket;
+4 -2
View File
@@ -2,21 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use error::ClientError;
use logging::setup_logging;
use network_defaults::setup_env;
pub mod client;
pub mod commands;
pub mod error;
pub mod websocket;
#[tokio::main]
async fn main() {
async fn main() -> Result<(), ClientError> {
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
commands::execute(&args).await
}
fn banner() -> String {
+1
View File
@@ -20,6 +20,7 @@ pretty_env_logger = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
snafu = "0.6"
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
url = "2.2"
+19 -10
View File
@@ -4,6 +4,7 @@
use std::sync::atomic::Ordering;
use crate::client::config::Config;
use crate::error::Socks5ClientError;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
@@ -23,6 +24,7 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use futures::StreamExt;
@@ -232,7 +234,7 @@ impl NymClient {
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) {
) -> Result<(), Socks5ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -247,14 +249,16 @@ impl NymClient {
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -293,8 +297,8 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
let mut shutdown = self.start().await;
pub async fn run_forever(&mut self) -> Result<(), Socks5ClientError> {
let mut shutdown = self.start().await?;
wait_for_signal().await;
log::info!("Sending shutdown");
@@ -305,11 +309,15 @@ impl NymClient {
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
Ok(())
}
// Variant of `run_forever` that listends for remote control messages
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
let mut shutdown = self.start().await;
pub async fn run_and_listen(
&mut self,
mut receiver: Socks5ControlMessageReceiver,
) -> Result<(), Socks5ClientError> {
let mut shutdown = self.start().await?;
tokio::select! {
message = receiver.next() => {
log::debug!("Received message: {:?}", message);
@@ -335,9 +343,10 @@ impl NymClient {
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
Ok(())
}
pub async fn start(&mut self) -> ShutdownNotifier {
pub async fn start(&mut self) -> Result<ShutdownNotifier, Socks5ClientError> {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -368,7 +377,7 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await;
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
@@ -417,6 +426,6 @@ impl NymClient {
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
shutdown
Ok(shutdown)
}
}
+4 -2
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::error::Socks5ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
@@ -83,16 +84,17 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: bool,
}
pub(crate) async fn execute(args: &Cli) {
pub(crate) async fn execute(args: &Cli) -> Result<(), Socks5ClientError> {
let bin_name = "nym-socks5-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Run(m) => run::execute(m).await?,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
Ok(())
}
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
+5 -4
View File
@@ -4,6 +4,7 @@
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
error::Socks5ClientError,
};
use clap::Args;
@@ -80,14 +81,14 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) {
pub(crate) async fn execute(args: &Run) -> Result<(), Socks5ClientError> {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
return;
return Err(Socks5ClientError::FailedToLoadConfig(id.to_string()));
}
};
@@ -96,8 +97,8 @@ pub(crate) async fn execute(args: &Run) {
if !version_check(&config) {
error!("failed the local version check");
return;
return Err(Socks5ClientError::FailedLocalVersionCheck);
}
NymClient::new(config).run_forever().await;
NymClient::new(config).run_forever().await
}
+23
View File
@@ -0,0 +1,23 @@
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
#[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("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
}
+1
View File
@@ -2,4 +2,5 @@
// SPDX-License-Identifier: Apache-2.0
pub mod client;
pub mod error;
pub mod socks;
+4 -2
View File
@@ -2,21 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use error::Socks5ClientError;
use logging::setup_logging;
use network_defaults::setup_env;
pub mod client;
mod commands;
pub mod error;
pub mod socks;
#[tokio::main]
async fn main() {
async fn main() -> Result<(), Socks5ClientError> {
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
commands::execute(&args).await
}
fn banner() -> String {
+5 -3
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.0.0"
version = "1.0.1"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -55,6 +55,8 @@ wee_alloc = { version = "0.4", optional = true }
wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
wasm-opt = true
[profile.release]
lto = true
opt-level = 'z'
@@ -2,6 +2,11 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
entry: {
bootstrap: './bootstrap.js',
worker: './worker.js',
@@ -22,6 +27,7 @@ module.exports = {
},
],
}),
],
experiments: { syncWebAssembly: true },
};
+1
View File
@@ -73,6 +73,7 @@ async function main() {
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
const gatewayEndpoint = await get_gateway(validator, preferredGateway);
gatewayEndpoint.gateway_listener = "wss://gateway1.nymtech.net:443"; // this is needed if we want it to work on the web. However this gateway is a v1 gateway, we will need to change for v2 once we get there
// only really useful if you want to adjust some settings like traffic rate
// (if not needed you can just pass a null)
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
@@ -11,6 +11,8 @@ use cosmwasm_std::Event;
/// * `event`: event to search through.
/// * `key`: key associated with the particular attribute
pub fn must_find_attribute(event: &Event, key: &str) -> String {
// due to how the function is supposed to work, the unwrap is fine in this instance
#[allow(clippy::unwrap_used)]
may_find_attribute(event, key).unwrap()
}
@@ -1,6 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
pub mod events;
pub mod types;
@@ -72,7 +72,7 @@ impl Percent {
impl Display for Percent {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let adjusted = Decimal::from_atomics(100u32, 0).unwrap() * self.0;
let adjusted = Decimal::from_ratio(100u32, 1u32) * self.0;
write!(f, "{}%", adjusted)
}
}
@@ -1,7 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Decimal;
use cosmwasm_std::{Decimal, Uint128};
pub const TOKEN_SUPPLY: Uint128 = Uint128::new(1_000_000_000_000_000);
// I'm still not 100% sure how to feel about existence of this file
// This is equivalent of representing our display coin with 6 decimal places.
@@ -4,8 +4,10 @@
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use crate::constants::TOKEN_SUPPLY;
use crate::helpers::IntoBaseDecimal;
use crate::{Addr, MixId};
use cosmwasm_std::{Coin, Decimal};
use cosmwasm_std::{Coin, Decimal, StdResult};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -60,6 +62,11 @@ impl Delegation {
height: u64,
proxy: Option<Addr>,
) -> Self {
assert!(
amount.amount <= TOKEN_SUPPLY,
"delegation cannot be larger than the token supply"
);
Delegation {
owner,
mix_id,
@@ -87,10 +94,8 @@ impl Delegation {
(mix_id, owner_proxy_subkey)
}
pub fn dec_amount(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed our base coin amount is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.amount.amount, 0).unwrap()
pub fn dec_amount(&self) -> StdResult<Decimal> {
self.amount.amount.into_base_decimal()
}
pub fn proxy_storage_key(&self) -> OwnerProxySubKey {
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Decimal;
use cosmwasm_std::{Decimal, StdError, StdResult, Uint128};
pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
let epsilon = epsilon.unwrap_or_else(|| Decimal::from_ratio(1u128, 100_000_000u128));
@@ -11,3 +11,23 @@ pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
assert!(b - a < epsilon, "{} != {}", a, b)
}
}
pub fn into_base_decimal(val: impl Into<Uint128>) -> StdResult<Decimal> {
val.into_base_decimal()
}
pub trait IntoBaseDecimal {
fn into_base_decimal(self) -> StdResult<Decimal>;
}
impl<T> IntoBaseDecimal for T
where
T: Into<Uint128>,
{
fn into_base_decimal(self) -> StdResult<Decimal> {
let atomics = self.into();
Decimal::from_atomics(atomics, 0).map_err(|_| StdError::GenericErr {
msg: format!("Decimal range exceeded for {atomics} with 0 decimal places."),
})
}
}
@@ -145,14 +145,17 @@ impl JsonSchema for Interval {
impl Interval {
/// Initialize epoch in the contract with default values.
pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self {
// if this fails it means the value provided from the chain itself (via cosmwasm) is invalid,
// so we really have to panic here as anything beyond that point would be invalid anyway
#[allow(clippy::expect_used)]
let current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
.expect("The timestamp provided via env.block.time is invalid");
Interval {
id: 0,
epochs_in_interval,
// I really don't see a way for this to fail, unless the blockchain is lying to us
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64
)
.expect("Invalid timestamp from env.block.time"),
current_epoch_start,
current_epoch_id: 0,
epoch_length,
total_elapsed_epochs: 0,
@@ -1,6 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
mod constants;
pub mod delegation;
pub mod error;
@@ -4,13 +4,14 @@
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use crate::constants::UNIT_DELEGATION_BASE;
use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE};
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@@ -64,7 +65,7 @@ impl MixNodeDetails {
self.rewarding_details.pending_operator_reward(pledge)
}
pub fn pending_detailed_operator_reward(&self) -> Decimal {
pub fn pending_detailed_operator_reward(&self) -> StdResult<Decimal> {
let pledge = self.original_pledge();
self.rewarding_details
.pending_detailed_operator_reward(pledge)
@@ -107,16 +108,21 @@ impl MixNodeRewarding {
cost_params: MixNodeCostParams,
initial_pledge: &Coin,
current_epoch: EpochId,
) -> Self {
MixNodeRewarding {
) -> Result<Self, MixnetContractError> {
assert!(
initial_pledge.amount <= TOKEN_SUPPLY,
"pledge cannot be larger than the token supply"
);
Ok(MixNodeRewarding {
cost_params,
operator: Decimal::from_atomics(initial_pledge.amount, 0).unwrap(),
operator: initial_pledge.amount.into_base_decimal()?,
delegates: Decimal::zero(),
total_unit_reward: Decimal::zero(),
unit_delegation: UNIT_DELEGATION_BASE,
last_rewarded_epoch: current_epoch,
unique_delegations: 0,
}
})
}
/// Determines whether this node is still bonded. This is performed via a simple check,
@@ -135,27 +141,30 @@ impl MixNodeRewarding {
}
}
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> Decimal {
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> StdResult<Decimal> {
let initial_dec = original_pledge.amount.into_base_decimal()?;
if initial_dec > self.operator {
panic!(
"seems slashing has occurred while it has not been implemented nor accounted for!"
)
}
self.operator - initial_dec
Ok(self.operator - initial_dec)
}
pub fn operator_pledge_with_reward(&self, denom: impl Into<String>) -> Coin {
truncate_reward(self.operator, denom)
}
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> Coin {
let delegator_reward = self.determine_delegation_reward(delegation);
truncate_reward(delegator_reward, &delegation.amount.denom)
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> {
let delegator_reward = self.determine_delegation_reward(delegation)?;
Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
}
pub fn withdraw_operator_reward(&mut self, original_pledge: &Coin) -> Coin {
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
pub fn withdraw_operator_reward(
&mut self,
original_pledge: &Coin,
) -> Result<Coin, MixnetContractError> {
let initial_dec = original_pledge.amount.into_base_decimal()?;
if initial_dec > self.operator {
panic!(
"seems slashing has occurred while it has not been implemented nor accounted for!"
@@ -164,14 +173,14 @@ impl MixNodeRewarding {
let diff = self.operator - initial_dec;
self.operator = initial_dec;
truncate_reward(diff, &original_pledge.denom)
Ok(truncate_reward(diff, &original_pledge.denom))
}
pub fn withdraw_delegator_reward(
&mut self,
delegation: &mut Delegation,
) -> Result<Coin, MixnetContractError> {
let reward = self.determine_delegation_reward(delegation);
let reward = self.determine_delegation_reward(delegation)?;
self.decrease_delegates_decimal(reward)?;
delegation.cumulative_reward_ratio = self.full_reward_ratio();
@@ -301,23 +310,27 @@ impl MixNodeRewarding {
self.distribute_rewards(reward_distribution, absolute_epoch_id)
}
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> StdResult<Decimal> {
let starting_ratio = delegation.cumulative_reward_ratio;
let ending_ratio = self.full_reward_ratio();
let adjust = starting_ratio + self.unit_delegation;
(ending_ratio - starting_ratio) * delegation.dec_amount() / adjust
Ok((ending_ratio - starting_ratio) * delegation.dec_amount()? / adjust)
}
// this updates `unique_delegations` field
pub fn add_base_delegation(&mut self, amount: Uint128) {
self.increase_delegates_uint128(amount);
pub fn add_base_delegation(&mut self, amount: Uint128) -> Result<(), MixnetContractError> {
self.increase_delegates_uint128(amount)?;
self.unique_delegations += 1;
Ok(())
}
pub fn increase_delegates_uint128(&mut self, amount: Uint128) {
// the unwrap here is fine as the value is guaranteed to fit under provided constraints
self.delegates += Decimal::from_atomics(amount, 0).unwrap()
pub fn increase_delegates_uint128(
&mut self,
amount: Uint128,
) -> Result<(), MixnetContractError> {
self.delegates += amount.into_base_decimal()?;
Ok(())
}
// this updates `unique_delegations` field
@@ -335,7 +348,7 @@ impl MixNodeRewarding {
&mut self,
amount: Uint128,
) -> Result<(), MixnetContractError> {
let amount_dec = Decimal::from_atomics(amount, 0).unwrap();
let amount_dec = amount.into_base_decimal()?;
self.decrease_delegates_decimal(amount_dec)
}
@@ -368,8 +381,8 @@ impl MixNodeRewarding {
}
pub fn undelegate(&mut self, delegation: &Delegation) -> Result<Coin, MixnetContractError> {
let reward = self.determine_delegation_reward(delegation);
let full_amount = reward + delegation.dec_amount();
let reward = self.determine_delegation_reward(delegation)?;
let full_amount = reward + delegation.dec_amount()?;
self.remove_delegation_decimal(full_amount)?;
Ok(truncate_reward(full_amount, &delegation.amount.denom))
}
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::delegation::OwnerProxySubKey;
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
@@ -41,14 +43,17 @@ pub struct InitialRewardingParams {
}
impl InitialRewardingParams {
pub fn into_rewarding_params(self, epochs_in_interval: u32) -> RewardingParams {
pub fn into_rewarding_params(
self,
epochs_in_interval: u32,
) -> Result<RewardingParams, MixnetContractError> {
let epoch_reward_budget = self.initial_reward_pool
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
/ epochs_in_interval.into_base_decimal()?
* self.interval_pool_emission;
let stake_saturation_point =
self.initial_staking_supply / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
self.initial_staking_supply / self.rewarded_set_size.into_base_decimal()?;
RewardingParams {
Ok(RewardingParams {
interval: IntervalRewardParams {
reward_pool: self.initial_reward_pool,
staking_supply: self.initial_staking_supply,
@@ -61,7 +66,7 @@ impl InitialRewardingParams {
},
rewarded_set_size: self.rewarded_set_size,
active_set_size: self.active_set_size,
}
})
}
}
@@ -1,6 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::helpers::IntoBaseDecimal;
use crate::{error::MixnetContractError, Percent};
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
@@ -110,24 +111,33 @@ impl RewardingParams {
pub fn dec_rewarded_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.rewarded_set_size, 0).unwrap()
#[allow(clippy::unwrap_used)]
self.rewarded_set_size.into_base_decimal().unwrap()
}
pub fn dec_active_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.active_set_size, 0).unwrap()
#[allow(clippy::unwrap_used)]
self.active_set_size.into_base_decimal().unwrap()
}
fn dec_standby_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.rewarded_set_size - self.active_set_size, 0).unwrap()
#[allow(clippy::unwrap_used)]
(self.rewarded_set_size - self.active_set_size)
.into_base_decimal()
.unwrap()
}
pub fn apply_epochs_in_interval_change(&mut self, new_epochs_in_interval: u32) {
self.interval.epoch_reward_budget = self.interval.reward_pool
/ Decimal::from_atomics(new_epochs_in_interval, 0).unwrap()
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
#[allow(clippy::unwrap_used)]
let new_epochs_in_interval = new_epochs_in_interval.into_base_decimal().unwrap();
self.interval.epoch_reward_budget = self.interval.reward_pool / new_epochs_in_interval
* self.interval.interval_pool_emission;
}
@@ -200,13 +210,13 @@ impl RewardingParams {
if recompute_epoch_budget {
self.interval.epoch_reward_budget = self.interval.reward_pool
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
/ epochs_in_interval.into_base_decimal()?
* self.interval.interval_pool_emission;
}
if recompute_saturation_point {
self.interval.stake_saturation_point = self.interval.staking_supply
/ Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
self.interval.stake_saturation_point =
self.interval.staking_supply / self.rewarded_set_size.into_base_decimal()?
}
Ok(())
@@ -29,7 +29,7 @@ pub struct RewardEstimate {
pub operating_cost: Decimal,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct RewardDistribution {
pub operator: Decimal,
pub delegates: Decimal,
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::reward_params::NodeRewardParams;
use crate::rewarding::simulator::simulated_node::SimulatedNode;
use crate::rewarding::RewardDistribution;
@@ -33,7 +34,7 @@ impl Simulator {
}
}
fn advance_epoch(&mut self) {
fn advance_epoch(&mut self) -> Result<(), MixnetContractError> {
let updated = self.interval.advance_epoch();
// we rolled over an interval
@@ -47,10 +48,13 @@ impl Simulator {
.staking_supply_scale_factor
* self.pending_reward_pool_emission;
let epoch_reward_budget = reward_pool
/ Decimal::from_atomics(self.interval.epochs_in_interval(), 0).unwrap()
/ self.interval.epochs_in_interval().into_base_decimal()?
* old.interval_pool_emission.value();
let stake_saturation_point = staking_supply
/ Decimal::from_atomics(self.system_rewarding_params.rewarded_set_size, 0).unwrap();
/ self
.system_rewarding_params
.rewarded_set_size
.into_base_decimal()?;
let updated_params = RewardingParams {
interval: IntervalRewardParams {
@@ -71,9 +75,15 @@ impl Simulator {
self.pending_reward_pool_emission = Decimal::zero();
}
self.interval = updated;
Ok(())
}
pub fn bond(&mut self, pledge: Coin, cost_params: MixNodeCostParams) -> MixId {
pub fn bond(
&mut self,
pledge: Coin,
cost_params: MixNodeCostParams,
) -> Result<MixId, MixnetContractError> {
let mix_id = self.next_mix_id;
self.nodes.insert(
@@ -83,16 +93,24 @@ impl Simulator {
cost_params,
&pledge,
self.interval.current_epoch_absolute_id(),
),
)?,
);
self.next_mix_id += 1;
mix_id
Ok(mix_id)
}
pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin, mix_id: MixId) {
let node = self.nodes.get_mut(&mix_id).expect("node doesn't exist");
pub fn delegate<S: Into<String>>(
&mut self,
delegator: S,
delegation: Coin,
mix_id: MixId,
) -> Result<(), MixnetContractError> {
let node = self
.nodes
.get_mut(&mix_id)
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
node.delegate(delegator, delegation)
}
@@ -103,23 +121,35 @@ impl Simulator {
delegator: S,
mix_id: MixId,
) -> Result<(Coin, Coin), MixnetContractError> {
let node = self.nodes.get_mut(&mix_id).expect("node not found");
let node = self
.nodes
.get_mut(&mix_id)
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
node.undelegate(delegator)
}
pub fn simulate_epoch_single_node(&mut self, params: NodeRewardParams) -> RewardDistribution {
pub fn simulate_epoch_single_node(
&mut self,
params: NodeRewardParams,
) -> Result<RewardDistribution, MixnetContractError> {
assert_eq!(self.nodes.len(), 1);
let id = *self.nodes.keys().next().unwrap();
let mut params_map = BTreeMap::new();
params_map.insert(id, params);
self.simulate_epoch(&params_map).remove(&id).unwrap()
if let Some(&id) = self.nodes.keys().next() {
let mut params_map = BTreeMap::new();
params_map.insert(id, params);
Ok(self
.simulate_epoch(&params_map)?
.remove(&id)
.unwrap_or_default())
} else {
Ok(RewardDistribution::default())
}
}
pub fn simulate_epoch(
&mut self,
node_params: &BTreeMap<MixId, NodeRewardParams>,
) -> BTreeMap<MixId, RewardDistribution> {
) -> Result<BTreeMap<MixId, RewardDistribution>, MixnetContractError> {
let mut params_keys = node_params.keys().copied().collect::<Vec<_>>();
params_keys.sort_unstable();
let mut node_keys = self.nodes.keys().copied().collect::<Vec<_>>();
@@ -147,34 +177,41 @@ impl Simulator {
dist.insert(*mix_id, reward_distribution);
}
self.advance_epoch();
dist
self.advance_epoch()?;
Ok(dist)
}
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
self.nodes[&delegation.mix_id]
pub fn determine_delegation_reward(
&self,
delegation: &Delegation,
) -> Result<Decimal, MixnetContractError> {
Ok(self.nodes[&delegation.mix_id]
.rewarding_details
.determine_delegation_reward(delegation)
.determine_delegation_reward(delegation)?)
}
pub fn determine_total_delegation_reward(&self) -> Decimal {
pub fn determine_total_delegation_reward(&self) -> Result<Decimal, MixnetContractError> {
let mut total = Decimal::zero();
for node in self.nodes.values() {
for delegation in node.delegations.values() {
total += node
.rewarding_details
.determine_delegation_reward(delegation)
.determine_delegation_reward(delegation)?
}
}
total
Ok(total)
}
// assume node state doesn't change in the interval (kinda unrealistic)
pub fn simulate_full_interval(&mut self, node_params: &BTreeMap<MixId, NodeRewardParams>) {
pub fn simulate_full_interval(
&mut self,
node_params: &BTreeMap<MixId, NodeRewardParams>,
) -> Result<(), MixnetContractError> {
for _ in 0..self.interval.epochs_in_interval() {
self.simulate_epoch(node_params);
self.simulate_epoch(node_params)?;
}
Ok(())
}
}
@@ -197,6 +234,10 @@ mod tests {
use cosmwasm_std::testing::mock_env;
use std::time::Duration;
// explicitly marking this as part of #[allow(clippy::unwrap_used)] until
// https://github.com/rust-lang/rust-clippy/pull/9686
// is merged into a release
#[allow(clippy::unwrap_used)]
fn base_simulator(initial_pledge: u128) -> Simulator {
let profit_margin = Percent::from_percentage_value(10).unwrap();
let interval_operating_cost = Coin::new(40_000_000, "unym");
@@ -238,20 +279,32 @@ mod tests {
profit_margin_percent: profit_margin,
interval_operating_cost,
};
simulator.bond(initial_pledge, cost_params);
simulator.bond(initial_pledge, cost_params).unwrap();
simulator
}
// essentially our delegations + estimated rewards HAVE TO equal to what we actually determined
//
// explicitly marking this as part of #[allow(clippy::unwrap_used)] until
// https://github.com/rust-lang/rust-clippy/pull/9686
// is merged into a release
#[allow(clippy::unwrap_used)]
fn check_rewarding_invariant(simulator: &Simulator) {
for node in simulator.nodes.values() {
let delegation_sum: Decimal =
node.delegations.values().map(|d| d.dec_amount()).sum();
let delegation_sum: Decimal = node
.delegations
.values()
.map(|d| d.dec_amount().unwrap())
.sum();
let reward_sum: Decimal = node
.delegations
.values()
.map(|d| node.rewarding_details.determine_delegation_reward(d))
.map(|d| {
node.rewarding_details
.determine_delegation_reward(d)
.unwrap()
})
.sum();
// let reward_sum = simulator.determine_total_delegation_reward();
@@ -269,7 +322,7 @@ mod tests {
let epoch_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards = simulator.simulate_epoch_single_node(epoch_params);
let rewards = simulator.simulate_epoch_single_node(epoch_params).unwrap();
assert_eq!(rewards.delegates, Decimal::zero());
compare_decimals(
@@ -282,11 +335,13 @@ mod tests {
#[test]
fn single_delegation_at_genesis() {
let mut simulator = base_simulator(10000_000000);
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
simulator
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards = simulator.simulate_epoch_single_node(node_params);
let rewards = simulator.simulate_epoch_single_node(node_params).unwrap();
compare_decimals(
rewards.delegates,
@@ -297,7 +352,7 @@ mod tests {
compare_decimals(
rewards.delegates,
simulator.determine_total_delegation_reward(),
simulator.determine_total_delegation_reward().unwrap(),
None,
);
let node = &simulator.nodes[&0];
@@ -317,20 +372,22 @@ mod tests {
let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards1 = simulator.simulate_epoch_single_node(node_params);
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
let expected_operator1 = "1128452.5416104363".parse().unwrap();
assert_eq!(rewards1.delegates, Decimal::zero());
compare_decimals(rewards1.operator, expected_operator1, None);
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
simulator
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
let rewards2 = simulator.simulate_epoch_single_node(node_params);
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
let expected_operator2 = "1363843.413584609".parse().unwrap();
let expected_delegator_reward1 = "1795952.25874404".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward1, None);
compare_decimals(rewards2.operator, expected_operator2, None);
let rewards3 = simulator.simulate_epoch_single_node(node_params);
let rewards3 = simulator.simulate_epoch_single_node(node_params).unwrap();
let expected_operator3 = "1364017.7824440491".parse().unwrap();
let expected_delegator_reward2 = "1796135.9269468693".parse().unwrap();
compare_decimals(rewards3.delegates, expected_delegator_reward2, None);
@@ -364,11 +421,15 @@ mod tests {
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
simulator
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
simulator
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
.unwrap();
// "normal", sanity check rewarding
let rewards1 = simulator.simulate_epoch_single_node(node_params);
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
let expected_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
@@ -378,14 +439,15 @@ mod tests {
let node = simulator.nodes.get_mut(&0).unwrap();
let reward = node
.rewarding_details
.withdraw_operator_reward(&original_pledge);
.withdraw_operator_reward(&original_pledge)
.unwrap();
assert_eq!(reward.amount, truncate_reward_amount(expected_operator1));
assert_eq!(
node.rewarding_details.operator,
Decimal::from_atomics(original_pledge.amount, 0).unwrap()
);
let rewards2 = simulator.simulate_epoch_single_node(node_params);
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
let expected_operator2 = "1411113.0004067947".parse().unwrap();
let expected_delegator_reward2 = "2200183.3879084454".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2, None);
@@ -402,11 +464,15 @@ mod tests {
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
simulator
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
simulator
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
.unwrap();
// "normal", sanity check rewarding
let rewards1 = simulator.simulate_epoch_single_node(node_params);
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
let expected_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
@@ -424,7 +490,7 @@ mod tests {
assert_eq!(reward.amount, truncate_reward_amount(expected_del1_reward));
// new reward after withdrawal
let rewards2 = simulator.simulate_epoch_single_node(node_params);
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
let expected_operator2 = "1411250.1907492676".parse().unwrap();
let expected_delegator_reward2 = "2200004.051009689".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2, None);
@@ -467,22 +533,30 @@ mod tests {
let mut performance = Percent::from_percentage_value(100).unwrap();
for epoch in 0..720 {
if epoch == 0 {
simulator.delegate("a", Coin::new(18000_000000, "unym"), 0)
simulator
.delegate("a", Coin::new(18000_000000, "unym"), 0)
.unwrap()
}
if epoch == 42 {
simulator.delegate("b", Coin::new(2000_000000, "unym"), 0)
simulator
.delegate("b", Coin::new(2000_000000, "unym"), 0)
.unwrap()
}
if epoch == 89 {
is_active = false;
}
if epoch == 123 {
simulator.delegate("c", Coin::new(6666_000000, "unym"), 0)
simulator
.delegate("c", Coin::new(6666_000000, "unym"), 0)
.unwrap()
}
if epoch == 167 {
performance = Percent::from_percentage_value(90).unwrap();
}
if epoch == 245 {
simulator.delegate("d", Coin::new(2050_000000, "unym"), 0)
simulator
.delegate("d", Coin::new(2050_000000, "unym"), 0)
.unwrap()
}
if epoch == 264 {
let (delegation, _reward) = simulator.undelegate("b", 0).unwrap();
@@ -503,13 +577,15 @@ mod tests {
// TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
}
if epoch == 545 {
simulator.delegate("e", Coin::new(5000_000000, "unym"), 0)
simulator
.delegate("e", Coin::new(5000_000000, "unym"), 0)
.unwrap()
}
// this has to always hold
check_rewarding_invariant(&simulator);
let node_params = NodeRewardParams::new(performance, is_active);
simulator.simulate_epoch_single_node(node_params);
simulator.simulate_epoch_single_node(node_params).unwrap();
}
// after everyone undelegates, there should be nothing left in the delegates pool
@@ -563,95 +639,135 @@ mod tests {
let mut simulator = Simulator::new(rewarding_params, interval);
let n0 = simulator.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0);
let n0 = simulator
.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0)
.unwrap();
let n1 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1);
let n1 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1)
.unwrap();
let n2 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2);
let n2 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2)
.unwrap();
let n3 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3);
let n3 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3)
.unwrap();
let n4 = simulator.bond(
Coin::new(1000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4);
let n4 = simulator
.bond(
Coin::new(1000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4)
.unwrap();
let n5 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5);
let n5 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5)
.unwrap();
let n6 = simulator.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6);
let n6 = simulator
.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6)
.unwrap();
let n7 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7);
let n7 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7)
.unwrap();
let n8 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8);
let n8 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8)
.unwrap();
let n9 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9);
let n9 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9)
.unwrap();
let uptime_1 = Percent::from_percentage_value(100).unwrap();
let uptime_09 = Percent::from_percentage_value(90).unwrap();
@@ -673,7 +789,7 @@ mod tests {
.collect::<BTreeMap<_, _>>();
for _ in 0..23 {
simulator.simulate_full_interval(&node_params);
simulator.simulate_full_interval(&node_params).unwrap();
}
// we allow the delta to be within 0.1unym,
@@ -20,21 +20,25 @@ impl SimulatedNode {
cost_params: MixNodeCostParams,
initial_pledge: &Coin,
current_epoch: EpochId,
) -> Self {
SimulatedNode {
) -> Result<Self, MixnetContractError> {
Ok(SimulatedNode {
mix_id,
rewarding_details: MixNodeRewarding::initialise_new(
cost_params,
initial_pledge,
current_epoch,
),
)?,
delegations: HashMap::new(),
}
})
}
pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin) {
pub fn delegate<S: Into<String>>(
&mut self,
delegator: S,
delegation: Coin,
) -> Result<(), MixnetContractError> {
self.rewarding_details
.add_base_delegation(delegation.amount);
.add_base_delegation(delegation.amount)?;
let delegator = delegator.into();
let delegation = Delegation::new(
@@ -47,6 +51,7 @@ impl SimulatedNode {
);
self.delegations.insert(delegator, delegation);
Ok(())
}
pub fn undelegate<S: Into<String>>(
@@ -54,16 +59,19 @@ impl SimulatedNode {
delegator: S,
) -> Result<(Coin, Coin), MixnetContractError> {
let delegator = delegator.into();
let delegation = self
.delegations
.remove(&delegator)
.expect("delegation not found");
let delegation = self.delegations.remove(&delegator).ok_or(
MixnetContractError::NoMixnodeDelegationFound {
mix_id: MixId::MAX,
address: delegator,
proxy: None,
},
)?;
let reward = self
.rewarding_details
.determine_delegation_reward(&delegation);
.determine_delegation_reward(&delegation)?;
self.rewarding_details
.remove_delegation_decimal(delegation.dec_amount() + reward)?;
.remove_delegation_decimal(delegation.dec_amount()? + reward)?;
let reward_denom = &delegation.amount.denom;
let truncated_reward = truncate_reward(reward, reward_denom);
@@ -35,6 +35,10 @@ impl LayerDistribution {
(Layer::Two, self.layer2),
(Layer::Three, self.layer3),
];
// we explicitly put 3 elements into the iterator, so the iterator is DEFINITELY
// not empty and thus the unwrap cannot fail
#[allow(clippy::unwrap_used)]
layers.iter().min_by_key(|x| x.1).unwrap().0
}
@@ -1,12 +1,15 @@
use std::str::FromStr;
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use contracts_common::Percent;
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
use mixnet_contract_common::MixId;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
@@ -69,6 +72,7 @@ impl FromStr for PledgeCap {
impl Default for PledgeCap {
fn default() -> Self {
#[allow(clippy::expect_used)]
PledgeCap::Percent(Percent::from_percentage_value(10).expect("This can never fail!"))
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
+3 -3
View File
@@ -85,7 +85,7 @@ impl SecretKey {
// x || ys.len() || ys
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len();
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) * 32);
bytes.extend_from_slice(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_bytes());
@@ -162,7 +162,7 @@ impl TryFrom<&[u8]> for VerificationKey {
let mut beta_g1_end: u64 = 0;
for i in 0..betas_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let end = start + 48;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective(
&beta_i_bytes,
@@ -178,7 +178,7 @@ impl TryFrom<&[u8]> for VerificationKey {
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize;
let end = (start + 96) as usize;
let end = start + 96;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
+2 -2
View File
@@ -94,10 +94,10 @@ fn prepare_unlinked_fragmented_set(
for i in 1..(pre_casted_frags + 1) {
// we can't use u8 directly here as upper (NON-INCLUSIVE, so it would always fit) bound could be u8::max_value() + 1
let lb = (i as usize - 1) * unlinked_fragment_payload_max_len(max_plaintext_size);
let lb = (i - 1) * unlinked_fragment_payload_max_len(max_plaintext_size);
let ub = usize::min(
message.len(),
i as usize * unlinked_fragment_payload_max_len(max_plaintext_size),
i * unlinked_fragment_payload_max_len(max_plaintext_size),
);
fragments.push(
Fragment::try_new(
+2
View File
@@ -0,0 +1,2 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
+1 -1
View File
@@ -63,7 +63,7 @@ pub fn instantiate(
Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env);
let reward_params = msg
.initial_rewarding_params
.into_rewarding_params(msg.epochs_in_interval);
.into_rewarding_params(msg.epochs_in_interval)?;
interval_storage::initialise_storage(deps.storage, starting_interval)?;
mixnet_params_storage::initialise_storage(deps.storage, state)?;
@@ -107,7 +107,7 @@ pub(crate) fn delegate(
};
// add the amount we're intending to delegate (whether it's fresh or we're adding to the existing one)
mix_rewarding.add_base_delegation(stored_delegation_amount.amount);
mix_rewarding.add_base_delegation(stored_delegation_amount.amount)?;
let cosmos_event = new_delegation_event(
created_at,
+3
View File
@@ -1,6 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
mod constants;
pub mod contract;
mod delegations;
+1 -1
View File
@@ -78,7 +78,7 @@ pub(crate) fn save_new_mixnode(
let mix_id = next_mixnode_id_counter(storage)?;
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch);
let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?;
let mixnode_bond = MixNodeBond::new(
mix_id,
owner,
+18 -12
View File
@@ -4,8 +4,9 @@
use super::storage;
use crate::delegations::storage as delegations_storage;
use crate::interval::storage as interval_storage;
use cosmwasm_std::{Coin, Decimal, Storage};
use cosmwasm_std::{Coin, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::helpers::IntoBaseDecimal;
use mixnet_contract_common::mixnode::{MixNodeDetails, MixNodeRewarding};
use mixnet_contract_common::Delegation;
@@ -22,11 +23,10 @@ pub(crate) fn apply_reward_pool_changes(
+ pending_pool_change.added;
let staking_supply = rewarding_params.interval.staking_supply
+ rewarding_params.interval.staking_supply_scale_factor * pending_pool_change.removed;
let epoch_reward_budget = reward_pool
/ Decimal::from_atomics(interval.epochs_in_interval(), 0).unwrap()
let epoch_reward_budget = reward_pool / interval.epochs_in_interval().into_base_decimal()?
* rewarding_params.interval.interval_pool_emission;
let stake_saturation_point =
staking_supply / Decimal::from_atomics(rewarding_params.rewarded_set_size, 0).unwrap();
staking_supply / rewarding_params.rewarded_set_size.into_base_decimal()?;
rewarding_params.interval.reward_pool = reward_pool;
rewarding_params.interval.staking_supply = staking_supply;
@@ -46,7 +46,7 @@ pub(crate) fn withdraw_operator_reward(
let mix_id = mix_details.mix_id();
let mut mix_rewarding = mix_details.rewarding_details;
let original_pledge = mix_details.bond_information.original_pledge;
let reward = mix_rewarding.withdraw_operator_reward(&original_pledge);
let reward = mix_rewarding.withdraw_operator_reward(&original_pledge)?;
// save updated rewarding info
storage::MIXNODE_REWARDING.save(store, mix_id, &mix_rewarding)?;
@@ -87,7 +87,7 @@ mod tests {
let mut test = TestSetup::new();
let epochs_in_interval = test.current_interval().epochs_in_interval();
let epochs_in_interval_dec = Decimal::from_atomics(epochs_in_interval, 0).unwrap();
let epochs_in_interval_dec = epochs_in_interval.into_base_decimal().unwrap();
let start_rewarding_params = test.rewarding_params();
// nothing changes if pending changes are empty
@@ -95,7 +95,7 @@ mod tests {
assert_eq!(start_rewarding_params, test.rewarding_params());
// normal case of having distributed some rewards
let distributed_rewards = Decimal::from_atomics(100_000_000u32, 0).unwrap();
let distributed_rewards = 100_000_000u32.into_base_decimal().unwrap();
storage::PENDING_REWARD_POOL_CHANGE
.save(
test.deps_mut().storage,
@@ -132,7 +132,10 @@ mod tests {
assert_eq!(
updated_rewarding_params.interval.stake_saturation_point,
updated_rewarding_params.interval.staking_supply
/ Decimal::from_atomics(updated_rewarding_params.rewarded_set_size, 0).unwrap()
/ updated_rewarding_params
.rewarded_set_size
.into_base_decimal()
.unwrap()
);
// resets changes back to 0
@@ -144,7 +147,7 @@ mod tests {
);
// future case of having to also increase the reward pool
let added_credentials = Decimal::from_atomics(50_000_000u32, 0).unwrap();
let added_credentials = 50_000_000u32.into_base_decimal().unwrap();
storage::PENDING_REWARD_POOL_CHANGE
.save(
test.deps_mut().storage,
@@ -181,7 +184,10 @@ mod tests {
assert_eq!(
updated_rewarding_params2.interval.stake_saturation_point,
updated_rewarding_params2.interval.staking_supply
/ Decimal::from_atomics(updated_rewarding_params2.rewarded_set_size, 0).unwrap()
/ updated_rewarding_params2
.rewarded_set_size
.into_base_decimal()
.unwrap()
);
// resets changes back to 0
@@ -198,7 +204,7 @@ mod tests {
let mut test = TestSetup::new();
let pledge = Uint128::new(250_000_000);
let pledge_dec = Decimal::from_atomics(250_000_000u32, 0).unwrap();
let pledge_dec = 250_000_000u32.into_base_decimal().unwrap();
let mix_id = test.add_dummy_mixnode("mix-owner", Some(pledge));
// no rewards
@@ -235,7 +241,7 @@ mod tests {
let mut test = TestSetup::new();
let delegation_amount = Uint128::new(2500_000_000);
let delegation_dec = Decimal::from_atomics(2500_000_000u32, 0).unwrap();
let delegation_dec = 2500_000_000u32.into_base_decimal().unwrap();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegator = "delegator";
test.add_immediate_delegation(delegator, delegation_amount, mix_id);
+17 -11
View File
@@ -7,6 +7,7 @@ use crate::interval::storage as interval_storage;
use crate::mixnodes;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{coin, Coin, Decimal, Deps, StdResult};
use mixnet_contract_common::helpers::into_base_decimal;
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::reward_params::{NodeRewardParams, Performance, RewardingParams};
use mixnet_contract_common::rewarding::helpers::truncate_reward;
@@ -19,16 +20,18 @@ pub(crate) fn query_rewarding_params(deps: Deps<'_>) -> StdResult<RewardingParam
storage::REWARDING_PARAMS.load(deps.storage)
}
fn pending_operator_reward(mix_details: Option<MixNodeDetails>) -> PendingRewardResponse {
match mix_details {
fn pending_operator_reward(
mix_details: Option<MixNodeDetails>,
) -> StdResult<PendingRewardResponse> {
Ok(match mix_details {
Some(mix_details) => PendingRewardResponse {
amount_staked: Some(mix_details.original_pledge().clone()),
amount_earned: Some(mix_details.pending_operator_reward()),
amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()),
amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()?),
mixnode_still_fully_bonded: !mix_details.is_unbonding(),
},
None => PendingRewardResponse::default(),
}
})
}
pub fn query_pending_operator_reward(
@@ -39,7 +42,7 @@ pub fn query_pending_operator_reward(
// in order to determine operator's reward we need to know its original pledge and thus
// we have to load the entire thing
let mix_details = mixnodes::helpers::get_mixnode_details_by_owner(deps.storage, owner_address)?;
Ok(pending_operator_reward(mix_details))
pending_operator_reward(mix_details)
}
pub fn query_pending_mixnode_operator_reward(
@@ -49,7 +52,7 @@ pub fn query_pending_mixnode_operator_reward(
// in order to determine operator's reward we need to know its original pledge and thus
// we have to load the entire thing
let mix_details = mixnodes::helpers::get_mixnode_details_by_id(deps.storage, mix_id)?;
Ok(pending_operator_reward(mix_details))
pending_operator_reward(mix_details)
}
pub fn query_pending_delegator_reward(
@@ -74,8 +77,8 @@ pub fn query_pending_delegator_reward(
None => return Ok(PendingRewardResponse::default()),
};
let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation);
let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation);
let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation)?;
let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation)?;
// check if the mixnode isnt in the process of unbonding (or has already unbonded)
let is_bonded = matches!(mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)?, Some(mix_bond) if !mix_bond.is_unbonding);
@@ -177,8 +180,8 @@ pub(crate) fn query_estimated_current_epoch_delegator_reward(
None => return Ok(EstimatedCurrentEpochRewardResponse::empty_response()),
};
let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
let staked_dec = into_base_decimal(delegation.amount.amount)?;
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation)?;
let amount_staked = delegation.amount;
// check if the mixnode isnt in the process of unbonding (or has already unbonded)
@@ -792,7 +795,10 @@ mod tests {
let delegation = test.delegation(mix_id, owner, &None);
let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
let current_value = staked_dec
+ mix_rewarding
.determine_delegation_reward(&delegation)
.unwrap();
let amount_staked = delegation.amount;
EstimatedCurrentEpochRewardResponse {
+30 -22
View File
@@ -744,7 +744,7 @@ pub mod tests {
performance,
in_active_set: true,
};
let sim_res = sim.simulate_epoch_single_node(node_params);
let sim_res = sim.simulate_epoch_single_node(node_params).unwrap();
assert_eq!(sim_res, dist);
}
test.skip_to_next_epoch_end();
@@ -768,7 +768,7 @@ pub mod tests {
performance,
in_active_set: true,
};
let sim_res = sim.simulate_epoch_single_node(node_params);
let sim_res = sim.simulate_epoch_single_node(node_params).unwrap();
assert_eq!(sim_res, dist);
}
test.skip_to_next_epoch_end();
@@ -809,8 +809,8 @@ pub mod tests {
in_active_set: true,
};
let dist1 = sim1.simulate_epoch_single_node(node_params);
let dist2 = sim2.simulate_epoch_single_node(node_params);
let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap();
let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap();
let env = test.env();
@@ -858,15 +858,17 @@ pub mod tests {
let unit_delegation_base = actual_prior1.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del11 = del11.dec_amount()
+ (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
let pre_rewarding_del11 = del11.dec_amount().unwrap()
+ (prior_unit_reward - del11.cumulative_reward_ratio)
* del11.dec_amount().unwrap()
/ (del11.cumulative_reward_ratio + unit_delegation_base);
let computed_del11_reward =
pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
let pre_rewarding_del12 = del12.dec_amount()
+ (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
let pre_rewarding_del12 = del12.dec_amount().unwrap()
+ (prior_unit_reward - del12.cumulative_reward_ratio)
* del12.dec_amount().unwrap()
/ (del12.cumulative_reward_ratio + unit_delegation_base);
let computed_del12_reward =
@@ -920,8 +922,9 @@ pub mod tests {
let unit_delegation_base = actual_prior2.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del21 = del21.dec_amount()
+ (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
let pre_rewarding_del21 = del21.dec_amount().unwrap()
+ (prior_unit_reward - del21.cumulative_reward_ratio)
* del21.dec_amount().unwrap()
/ (del21.cumulative_reward_ratio + unit_delegation_base);
let computed_del21_reward =
@@ -949,8 +952,8 @@ pub mod tests {
in_active_set: true,
};
let dist1 = sim1.simulate_epoch_single_node(node_params);
let dist2 = sim2.simulate_epoch_single_node(node_params);
let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap();
let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap();
let env = test.env();
@@ -998,22 +1001,25 @@ pub mod tests {
let unit_delegation_base = actual_prior1.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del11 = del11.dec_amount()
+ (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
let pre_rewarding_del11 = del11.dec_amount().unwrap()
+ (prior_unit_reward - del11.cumulative_reward_ratio)
* del11.dec_amount().unwrap()
/ (del11.cumulative_reward_ratio + unit_delegation_base);
let computed_del11_reward =
pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
let pre_rewarding_del12 = del12.dec_amount()
+ (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
let pre_rewarding_del12 = del12.dec_amount().unwrap()
+ (prior_unit_reward - del12.cumulative_reward_ratio)
* del12.dec_amount().unwrap()
/ (del12.cumulative_reward_ratio + unit_delegation_base);
let computed_del12_reward =
pre_rewarding_del12 / prior_delegates1 * delegates_reward1;
let pre_rewarding_del13 = del13.dec_amount()
+ (prior_unit_reward - del13.cumulative_reward_ratio) * del13.dec_amount()
let pre_rewarding_del13 = del13.dec_amount().unwrap()
+ (prior_unit_reward - del13.cumulative_reward_ratio)
* del13.dec_amount().unwrap()
/ (del13.cumulative_reward_ratio + unit_delegation_base);
let computed_del13_reward =
@@ -1067,15 +1073,17 @@ pub mod tests {
let unit_delegation_base = actual_prior2.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del21 = del21.dec_amount()
+ (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
let pre_rewarding_del21 = del21.dec_amount().unwrap()
+ (prior_unit_reward - del21.cumulative_reward_ratio)
* del21.dec_amount().unwrap()
/ (del21.cumulative_reward_ratio + unit_delegation_base);
let computed_del21_reward =
pre_rewarding_del21 / prior_delegates2 * delegates_reward2;
let pre_rewarding_del23 = del23.dec_amount()
+ (prior_unit_reward - del23.cumulative_reward_ratio) * del23.dec_amount()
let pre_rewarding_del23 = del23.dec_amount().unwrap()
+ (prior_unit_reward - del23.cumulative_reward_ratio)
* del23.dec_amount().unwrap()
/ (del23.cumulative_reward_ratio + unit_delegation_base);
let computed_del23_reward =
+8
View File
@@ -69,6 +69,10 @@ pub(crate) fn validate_pledge(
});
}
// throughout this function we've been using the value at `pledge[0]` without problems
// (plus we have even validated that the vec is not empty), so the unwrap here is absolutely fine,
// since it cannot possibly fail without UB
#[allow(clippy::unwrap_used)]
Ok(pledge.pop().unwrap())
}
@@ -106,6 +110,10 @@ pub(crate) fn validate_delegation_stake(
return Err(MixnetContractError::EmptyDelegation);
}
// throughout this function we've been using the value at `delegation[0]` without problems
// (plus we have even validated that the vec is not empty), so the unwrap here is absolutely fine,
// since it cannot possibly fail without UB
#[allow(clippy::unwrap_used)]
Ok(delegation.pop().unwrap())
}
+2 -2
View File
@@ -589,7 +589,7 @@ pub fn try_get_current_vesting_period(
env: Env,
) -> Result<Period, ContractError> {
let account = account_from_address(address, deps.storage, deps.api)?;
Ok(account.get_current_vesting_period(env.block.time))
account.get_current_vesting_period(env.block.time)
}
/// Loads mixnode bond from vesting contract storage.
@@ -691,7 +691,7 @@ pub fn try_get_original_vesting(
deps: Deps<'_>,
) -> Result<OriginalVestingResponse, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
Ok(account.get_original_vesting())
account.get_original_vesting()
}
/// See [crate::traits::VestingAccount::get_delegated_free]
+2
View File
@@ -50,4 +50,6 @@ pub enum ContractError {
MinVestingFunds { sent: u128, need: u128 },
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
#[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())]
UnpopulatedVestingPeriods { owner: Addr },
}
+3
View File
@@ -1,6 +1,9 @@
#![allow(rustdoc::private_intra_doc_links)]
//! Nym vesting contract, providing vesting accounts with ability to stake unvested tokens
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
pub mod contract;
mod errors;
mod queued_migrations;
@@ -55,7 +55,7 @@ pub trait VestingAccount {
/// Returns amount of coins set at account creation
/// See [/vesting-contract/struct.Account.html/method.get_original_vesting] for impl
fn get_original_vesting(&self) -> OriginalVestingResponse;
fn get_original_vesting(&self) -> Result<OriginalVestingResponse, ContractError>;
/// See [/vesting-contract/struct.Account.html/method.get_delegated_free] for impl
fn get_delegated_free(
+31 -9
View File
@@ -69,7 +69,7 @@ impl Account {
pub fn absolute_pledge_cap(&self) -> Result<Uint128, ContractError> {
match self.pledge_cap() {
PledgeCap::Absolute(cap) => Ok(cap),
PledgeCap::Percent(p) => Ok(p * self.get_original_vesting().amount.amount),
PledgeCap::Percent(p) => Ok(p * self.get_original_vesting()?.amount.amount),
}
}
@@ -81,8 +81,13 @@ impl Account {
self.periods.len()
}
pub fn period_duration(&self) -> u64 {
self.periods.get(0).unwrap().period_seconds
pub fn period_duration(&self) -> Result<u64, ContractError> {
self.periods
.get(0)
.ok_or(ContractError::UnpopulatedVestingPeriods {
owner: self.owner_address.clone(),
})
.map(|p| p.period_seconds)
}
pub fn storage_key(&self) -> u32 {
@@ -119,11 +124,28 @@ impl Account {
/// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet.
/// In case vesting is over it will always return NUM_VESTING_PERIODS.
pub fn get_current_vesting_period(&self, block_time: Timestamp) -> Period {
if block_time.seconds() < self.periods.first().unwrap().start_time {
Period::Before
} else if self.periods.last().unwrap().end_time() < block_time {
Period::After
pub fn get_current_vesting_period(
&self,
block_time: Timestamp,
) -> Result<Period, ContractError> {
let first_period =
self.periods
.first()
.ok_or(ContractError::UnpopulatedVestingPeriods {
owner: self.owner_address.clone(),
})?;
let last_period = self
.periods
.last()
.ok_or(ContractError::UnpopulatedVestingPeriods {
owner: self.owner_address.clone(),
})?;
if block_time.seconds() < first_period.start_time {
Ok(Period::Before)
} else if last_period.end_time() < block_time {
Ok(Period::After)
} else {
let mut index = 0;
for period in &self.periods {
@@ -132,7 +154,7 @@ impl Account {
}
index += 1;
}
Period::In(index)
Ok(Period::In(index))
}
}
@@ -67,7 +67,7 @@ impl VestingAccount for Account {
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time);
let period = self.get_current_vesting_period(block_time);
let period = self.get_current_vesting_period(block_time)?;
let denom = MIX_DENOM.load(storage)?;
let amount = match period {
@@ -94,7 +94,7 @@ impl VestingAccount for Account {
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
Ok(Coin {
amount: self.get_original_vesting().amount().amount
amount: self.get_original_vesting()?.amount().amount
- self.get_vested_coins(block_time, env, storage)?.amount,
denom: MIX_DENOM.load(storage)?,
})
@@ -108,12 +108,12 @@ impl VestingAccount for Account {
self.periods[(self.num_vesting_periods() - 1)].end_time()
}
fn get_original_vesting(&self) -> OriginalVestingResponse {
OriginalVestingResponse::new(
fn get_original_vesting(&self) -> Result<OriginalVestingResponse, ContractError> {
Ok(OriginalVestingResponse::new(
self.coin.clone(),
self.num_vesting_periods(),
self.period_duration(),
)
self.period_duration()?,
))
}
fn get_delegated_free(
@@ -123,13 +123,12 @@ impl VestingAccount for Account {
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time);
let period = self.get_current_vesting_period(block_time)?;
let withdrawn = self.load_withdrawn(storage)?;
let max_available = self
.get_vested_coins(Some(block_time), env, storage)?
.amount
.saturating_sub(withdrawn);
let period = self.get_current_vesting_period(block_time);
let start_time = match period {
Period::Before => 0,
Period::After => u64::MAX,
@@ -174,7 +173,7 @@ impl VestingAccount for Account {
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time);
let period = self.get_current_vesting_period(block_time);
let period = self.get_current_vesting_period(block_time)?;
let max_vested = self.get_vested_coins(Some(block_time), env, storage)?;
let start_time = match period {
Period::Before => 0,
+51 -11
View File
@@ -183,12 +183,14 @@ mod tests {
assert_eq!(account.periods().len(), num_vesting_periods as usize);
let current_period = account.get_current_vesting_period(Timestamp::from_seconds(0));
let current_period = account
.get_current_vesting_period(Timestamp::from_seconds(0))
.unwrap();
assert_eq!(Period::Before, current_period);
let block_time =
Timestamp::from_seconds(account.start_time().seconds() + vesting_period + 1);
let current_period = account.get_current_vesting_period(block_time);
let current_period = account.get_current_vesting_period(block_time).unwrap();
assert_eq!(current_period, Period::In(1));
let vested_coins = account
.get_vested_coins(Some(block_time), &env, &deps.storage)
@@ -199,21 +201,37 @@ mod tests {
assert_eq!(
vested_coins.amount,
Uint128::new(
account.get_original_vesting().amount().amount.u128() / num_vesting_periods as u128
account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
/ num_vesting_periods as u128
)
);
assert_eq!(
vesting_coins.amount,
Uint128::new(
account.get_original_vesting().amount().amount.u128()
- account.get_original_vesting().amount().amount.u128()
account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
- account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
/ num_vesting_periods as u128
)
);
let block_time =
Timestamp::from_seconds(account.start_time().seconds() + 5 * vesting_period + 1);
let current_period = account.get_current_vesting_period(block_time);
let current_period = account.get_current_vesting_period(block_time).unwrap();
assert_eq!(current_period, Period::In(5));
let vested_coins = account
.get_vested_coins(Some(block_time), &env, &deps.storage)
@@ -224,15 +242,30 @@ mod tests {
assert_eq!(
vested_coins.amount,
Uint128::new(
5 * account.get_original_vesting().amount().amount.u128()
5 * account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
/ num_vesting_periods as u128
)
);
assert_eq!(
vesting_coins.amount,
Uint128::new(
account.get_original_vesting().amount().amount.u128()
- 5 * account.get_original_vesting().amount().amount.u128()
account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
- 5 * account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
/ num_vesting_periods as u128
)
);
@@ -240,7 +273,7 @@ mod tests {
let block_time = Timestamp::from_seconds(
account.start_time().seconds() + vesting_over_period * vesting_period + 1,
);
let current_period = account.get_current_vesting_period(block_time);
let current_period = account.get_current_vesting_period(block_time).unwrap();
assert_eq!(current_period, Period::After);
let vested_coins = account
.get_vested_coins(Some(block_time), &env, &deps.storage)
@@ -250,7 +283,14 @@ mod tests {
.unwrap();
assert_eq!(
vested_coins.amount,
Uint128::new(account.get_original_vesting().amount().amount.u128())
Uint128::new(
account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
)
);
assert_eq!(vesting_coins.amount, Uint128::zero());
}
+269
View File
@@ -0,0 +1,269 @@
# This template contains all of the possible sections and their default values
# Note that all fields that take a lint level have these possible values:
# * deny - An error will be produced and the check will fail
# * warn - A warning will be produced, but the check will not fail
# * allow - No warning or error will be produced, though in some cases a note
# will be
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
# Root options
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
# dependency, such as, for example, the `nix` crate only being used via the
# `target_family = "unix"` configuration, that only having windows targets in
# this list would mean the nix crate, as well as any of its exclusive
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# When creating the dependency graph used as the source of truth when checks are
# executed, this field can be used to prune crates from the graph, removing them
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
# is pruned from the graph, all of its dependencies will also be pruned unless
# they are connected to another crate in the graph that hasn't been pruned,
# so it should be used with care. The identifiers are [Package ID Specifications]
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
#exclude = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = false
# If set, these feature will be enabled when collecting metadata. If `--features`
# is specified on the cmd line they will take precedence over this option.
#features = []
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory database is cloned/fetched into
db-path = "~/.cargo/advisory-db"
# The url(s) of the advisory databases to use
db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
unmaintained = "allow"
# The lint level for crates that have been yanked from their source registry
yanked = "warn"
# The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db
notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
"RUSTSEC-2020-0159",
"RUSTSEC-2020-0071",
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
# will still output a note when they are encountered.
# * None - CVSS Score 0.0
# * Low - CVSS Score 0.1 - 3.9
# * Medium - CVSS Score 4.0 - 6.9
# * High - CVSS Score 7.0 - 8.9
# * Critical - CVSS Score 9.0 - 10.0
#severity-threshold =
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# See Git Authentication for more information about setting up git authentication.
#git-fetch-with-cli = true
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
# The lint level for crates which do not have a detectable license
unlicensed = "deny"
# List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
#"MIT",
#"Apache-2.0",
]
# List of explicitly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
deny = [
#"Nokia",
]
# Lint level for licenses considered copyleft
copyleft = "warn"
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
# * both - The license will be approved if it is both OSI-approved *AND* FSF
# * either - The license will be approved if it is either OSI-approved *OR* FSF
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
# * neither - This predicate is ignored and the default lint level is used
allow-osi-fsf-free = "neither"
# Lint level used when no other predicates are matched
# 1. License isn't in the allow or deny lists
# 2. License isn't copyleft
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
default = "deny"
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
# [possible values: any between 0.0 and 1.0].
confidence-threshold = 0.8
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], name = "adler32", version = "*" },
]
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
#[[licenses.clarify]]
# The name of the crate the clarification applies to
#name = "ring"
# The optional version constraint for the crate
#version = "*"
# The SPDX expression for the license requirements of the crate
#expression = "MIT AND ISC AND OpenSSL"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries.
# To see how to mark a crate as unpublished (to the official registry),
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
ignore = false
# One or more private registries that you might publish crates to, if a crate
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
]
# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "warn"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
# with multiple versions
# * lowest-version - The path to the lowest versioned duplicate is highlighted
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overriden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overriden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care!
allow = [
#{ name = "ansi_term", version = "=0.11.0" },
]
# List of crates to deny
deny = [
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#{ name = "ansi_term", version = "=0.11.0" },
#
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
]
# List of features to allow/deny
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#[[bans.features]]
#name = "reqwest"
# Features to not allow
#deny = ["json"]
# Features to allow
#allow = [
# "rustls",
# "__rustls",
# "__tls",
# "hyper-rustls",
# "rustls",
# "rustls-pemfile",
# "rustls-tls-webpki-roots",
# "tokio-rustls",
# "webpki-roots",
#]
# If true, the allowed features must exactly match the enabled feature set. If
# this is set there is no point setting `deny`
#exact = true
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#{ name = "ansi_term", version = "=0.11.0" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite.
skip-tree = [
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
# More documentation about the 'sources' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
[sources]
# Lint level for what to happen when a crate from a crate registry that is not
# in the allow list is encountered
unknown-registry = "warn"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = []
[sources.allow-org]
# 1 or more github.com organizations to allow git sources for
github = [""]
# 1 or more gitlab.com organizations to allow git sources for
gitlab = [""]
# 1 or more bitbucket.org organizations to allow git sources for
bitbucket = [""]
+20
View File
@@ -0,0 +1,20 @@
CONFIGURED=true
RUST_LOG=info
RUST_BACKTRACE=1
BECH32_PREFIX=n
MIX_DENOM=unym
MIX_DENOM_DISPLAY=nym
STAKE_DENOM=unyx
STAKE_DENOM_DISPLAY=nyx
DENOMS_EXPONENT=6
MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYMD_VALIDATOR="https://qwerty-validator.qa.nymte.ch/"
API_VALIDATOR="https://qwerty-validator-api.qa.nymte.ch/api"
+1
View File
@@ -3273,6 +3273,7 @@ dependencies = [
"snafu",
"socks5-requests",
"task",
"thiserror",
"tokio",
"topology",
"url",
+14 -1
View File
@@ -18,6 +18,8 @@ pub type StatusReceiver = futures::channel::oneshot::Receiver<Socks5StatusMessag
pub enum Socks5StatusMessage {
/// The SOCKS5 task successfully stopped
Stopped,
/// The SOCKS5 task failed to start
FailedToStart,
}
/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`.
@@ -43,10 +45,18 @@ pub fn start_nym_socks5_client(
// The status channel is used to both get the state of the task, and if it's closed, to check
// for panic.
std::thread::spawn(|| {
tokio::runtime::Runtime::new()
let result = tokio::runtime::Runtime::new()
.expect("Failed to create runtime for SOCKS5 client")
.block_on(async move { socks5_client.run_and_listen(socks5_ctrl_rx).await });
if let Err(err) = result {
log::error!("SOCKS5 proxy failed to start: {err}");
socks5_status_tx
.send(Socks5StatusMessage::FailedToStart)
.expect("Failed to send status message back to main task");
return;
}
log::info!("SOCKS5 task finished");
socks5_status_tx
.send(Socks5StatusMessage::Stopped)
@@ -69,6 +79,9 @@ pub fn start_disconnect_listener(
Ok(Socks5StatusMessage::Stopped) => {
log::info!("SOCKS5 task reported it has finished");
}
Ok(Socks5StatusMessage::FailedToStart) => {
log::info!("SOCKS5 task reported it failed to start");
}
Err(_) => {
log::info!("SOCKS5 task appears to have stopped abruptly");
// TODO: we should probably generate some events here, or otherwise signal to the
+4 -4
View File
@@ -160,9 +160,9 @@ mod qa {
pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
"n1frq2hzkjtatsupc6jtyaz67ytydk9nya437q92qg76ny3y8fcnjsw806vg";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
@@ -177,8 +177,8 @@ mod qa {
//pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://qwerty-validator.qa.nymte.ch/",
Some("https://qwerty-validator-api.qa.nymte.ch/api"),
"https://adv-epoch-qa-validator.qa.nymte.ch/",
Some("https://adv-epoch-qa-val-api.qa.nymte.ch/api"),
)]
}
@@ -42,7 +42,7 @@ const headers: Header[] = [
header: 'Operator rewards',
id: 'operator-rewards',
tooltipText:
'This is your (operator) rewards including the PM and cost. Rewards are automatically compounded every epoch. You can redeem your rewards at any time.',
'This is your (operator) new rewards including the PM and cost. You can compound your rewards manually every epoch or unbond your node to redeem them.',
},
{
header: 'No. delegators',
+9 -2
View File
@@ -130,10 +130,16 @@ const TokenTransfer = () => {
</Grid>
<Grid item>
<Typography variant="subtitle2" sx={{ color: (t) => t.palette.nym.text.muted, mt: 2 }}>
Unlocked transferable tokens
Transferable tokens
</Typography>
<Typography data-testid="refresh-success" sx={{ color: 'text.primary' }} variant="h5" textTransform="uppercase">
<Typography
data-testid="refresh-success"
sx={{ color: 'text.primary' }}
variant="h5"
fontWeight="700"
textTransform="uppercase"
>
{userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()}
</Typography>
</Grid>
@@ -161,6 +167,7 @@ export const VestingCard = ({ onTransfer }: { onTransfer: () => Promise<void> })
<NymCard
title="Vesting Schedule"
data-testid="check-unvested-tokens"
Icon={<InfoOutlined />}
Action={
<IconButton
onClick={async () => {
@@ -33,17 +33,10 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => {
</Grid>
<Grid item container direction={'column'} spacing={2} width={0.5} padding={3}>
<Grid item>
<Box sx={{ mb: 1 }}>
<Error
message={`Remember you should only unbond if you want to remove your ${
isMixnode(bondedNode) ? 'node' : 'gateway'
} from the network for good.`}
/>
</Box>
<Error
message={`Unbonding is irreversible and it wont be possible to restore the current state of your ${
isMixnode(bondedNode) ? 'node' : 'gateway'
} again.`}
} again`}
/>
</Grid>
<Grid item>
@@ -4,20 +4,15 @@ import { yupResolver } from '@hookform/resolvers/yup';
import { Button, Divider, Typography, TextField, Grid, CircularProgress, Box } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { isMixnode } from 'src/types';
import { simulateUpdateMixnodeConfig, simulateVestingUpdateMixnodeConfig, updateMixnodeConfig } from 'src/requests';
import { updateMixnodeConfig } from 'src/requests';
import { TBondedMixnode, TBondedGateway } from 'src/context/bonding';
import { SimpleModal } from 'src/components/Modals/SimpleModal';
import { bondedInfoParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
import { Console } from 'src/utils/console';
import { Alert } from 'src/components/Alert';
import { vestingUpdateMixnodeConfig } from 'src/requests/vesting';
import { ConfirmTx } from 'src/components/ConfirmTX';
import { useGetFee } from 'src/hooks/useGetFee';
import { LoadingModal } from 'src/components/Modals/LoadingModal';
export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => {
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
const { getFee, fee, resetFeeState } = useGetFee();
const theme = useTheme();
@@ -38,7 +33,6 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
verlocPort?: number;
httpApiPort?: number;
}) => {
resetFeeState();
const { host, version, mixPort, verlocPort, httpApiPort } = data;
if (host && version && mixPort && verlocPort && httpApiPort) {
const MixNodeConfigParams = {
@@ -49,11 +43,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
version,
};
try {
if (bondedNode.proxy) {
await vestingUpdateMixnodeConfig(MixNodeConfigParams);
} else {
await updateMixnodeConfig(MixNodeConfigParams);
}
await updateMixnodeConfig(MixNodeConfigParams);
setOpenConfirmationModal(true);
} catch (error) {
Console.error(error);
@@ -63,22 +53,10 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
return (
<Grid container xs item>
{fee && (
<ConfirmTx
open
header="Update node settings"
fee={fee}
onConfirm={handleSubmit((d) => onSubmit(d))}
onPrev={resetFeeState}
onClose={resetFeeState}
/>
)}
{isSubmitting && <LoadingModal />}
<Alert
title={
<Box sx={{ fontWeight: 600 }}>
Changing these values will ONLY change the data about your node on the blockchain. Remember to change your
nodes config file with the same values too
Your changes will be ONLY saved on the display. Remember to change the values on your nodes config file too
</Box>
}
dismissable
@@ -89,6 +67,16 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Port
</Typography>
<Typography
variant="body1"
sx={{
fontSize: 14,
mb: 2,
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
}}
>
Change profit margin of your node
</Typography>
</Grid>
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
<Grid item width={1}>
@@ -132,6 +120,16 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Host
</Typography>
<Typography
variant="body1"
sx={{
fontSize: 14,
mb: 2,
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
}}
>
Lock wallet after certain time
</Typography>
</Grid>
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
<Grid item width={1}>
@@ -153,6 +151,16 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Version
</Typography>
<Typography
variant="body1"
sx={{
fontSize: 14,
mb: 2,
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
}}
>
Lock wallet after certain time
</Typography>
</Grid>
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
<Grid item width={1}>
@@ -174,24 +182,18 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
size="large"
variant="contained"
disabled={isSubmitting || !isDirty || !isValid}
onClick={handleSubmit((data) =>
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeConfig : simulateUpdateMixnodeConfig, {
host: data.host,
mix_port: data.mixPort,
verloc_port: data.verlocPort,
http_api_port: data.httpApiPort,
version: data.version,
}),
)}
sx={{ m: 3 }}
onClick={handleSubmit((d) => onSubmit(d))}
type="submit"
sx={{ m: 3, width: '320px' }}
endIcon={isSubmitting && <CircularProgress size={20} />}
>
Submit changes to the blockchain
Save all display changes
</Button>
</Grid>
</Grid>
<SimpleModal
open={openConfirmationModal}
header="Your changes are submitted to the blockchain"
header="Your changes were ONLY saved on the display"
subHeader="Remember to change the values
on your nodes config file too."
okLabel="close"
@@ -214,6 +216,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
textAlign: 'center',
color: theme.palette.nym.nymWallet.text.blue,
fontSize: 16,
textTransform: 'capitalize',
}}
subHeaderStyles={{
width: '100%',
@@ -221,6 +224,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
textAlign: 'center',
color: 'main',
fontSize: 14,
textTransform: 'capitalize',
}}
/>
</Grid>
@@ -14,27 +14,17 @@ import {
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
import { add, format, fromUnixTime } from 'date-fns';
import { isMixnode } from 'src/types';
import {
getCurrentInterval,
getPendingIntervalEvents,
simulateUpdateMixnodeCostParams,
simulateVestingUpdateMixnodeCostParams,
updateMixnodeCostParams,
vestingUpdateMixnodeCostParams,
} from 'src/requests';
import { TBondedMixnode } from 'src/context/bonding';
import { getCurrentInterval, getPendingIntervalEvents, updateMixnodeCostParams } from 'src/requests';
import { TBondedMixnode, TBondedGateway } from 'src/context/bonding';
import { SimpleModal } from 'src/components/Modals/SimpleModal';
import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
import { Console } from 'src/utils/console';
import { Alert } from 'src/components/Alert';
import { ChangeMixCostParams } from 'src/pages/bonding/types';
import { AppContext } from 'src/context';
import { useGetFee } from 'src/hooks/useGetFee';
import { ConfirmTx } from 'src/components/ConfirmTX';
import { LoadingModal } from 'src/components/Modals/LoadingModal';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => {
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
@@ -44,8 +34,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
const { clientDetails } = useContext(AppContext);
const theme = useTheme();
const { fee, getFee, resetFeeState } = useGetFee();
const defaultValues = {
operatorCost: bondedNode.operatorCost,
profitMargin: bondedNode.profitMargin,
@@ -108,7 +96,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
}, []);
const onSubmit = async (data: { operatorCost: { amount: string; denom: CurrencyDenom }; profitMargin: string }) => {
resetFeeState();
if (data.operatorCost && data.profitMargin) {
const MixNodeCostParams = {
profit_margin_percent: (+data.profitMargin / 100).toString(),
@@ -118,11 +105,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
},
};
try {
if (bondedNode.proxy) {
await vestingUpdateMixnodeCostParams(MixNodeCostParams);
} else {
await updateMixnodeCostParams(MixNodeCostParams);
}
await updateMixnodeCostParams(MixNodeCostParams);
await getPendingEvents();
reset();
setOpenConfirmationModal(true);
@@ -134,17 +117,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
return (
<Grid container xs item>
{fee && (
<ConfirmTx
open
header="Update cost parameters"
fee={fee}
onConfirm={handleSubmit((d) => onSubmit(d))}
onPrev={resetFeeState}
onClose={resetFeeState}
/>
)}
{isSubmitting && <LoadingModal />}
<Alert
title={
<>
@@ -157,7 +129,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
/>
<Grid container direction="column">
<Grid item container alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
<Grid item xl={6}>
<Grid item>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Profit Margin
</Typography>
@@ -251,16 +223,12 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
size="large"
variant="contained"
disabled={isSubmitting || !isDirty || !isValid}
onClick={handleSubmit((data) => {
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeCostParams : simulateUpdateMixnodeCostParams, {
profit_margin_percent: (+data.profitMargin / 100).toString(),
interval_operating_cost: data.operatorCost,
});
})}
onClick={handleSubmit(onSubmit)}
type="submit"
sx={{ m: 3 }}
sx={{ m: 3, width: '320px' }}
endIcon={isSubmitting && <CircularProgress size={20} />}
>
Submit changes to the blockchain
Save all display changes
</Button>
</Grid>
</Grid>
@@ -268,7 +236,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
open={openConfirmationModal}
header="Your changes will take place
in the next interval"
okLabel="Close"
okLabel="close"
hideCloseIcon
displayInfoIcon
onOk={async () => {
@@ -288,6 +256,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
textAlign: 'center',
color: theme.palette.nym.nymWallet.text.blue,
fontSize: 16,
textTransform: 'capitalize',
}}
subHeaderStyles={{
m: 0,
@@ -3,6 +3,7 @@
use crate::allowed_hosts::{HostsStore, OutboundRequestFilter};
use crate::connection::Connection;
use crate::error::NetworkRequesterError;
use crate::statistics::ServiceStatisticsCollector;
use crate::websocket;
use crate::websocket::TSWebsocketStream;
@@ -21,7 +22,6 @@ use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use task::ShutdownListener;
use tokio_tungstenite::tungstenite::protocol::Message;
use websocket::WebsocketConnectionError;
use websocket_requests::{requests::ClientRequest, responses::ServerResponse};
// Since it's an atomic, it's safe to be kept static and shared across threads
@@ -298,8 +298,8 @@ impl ServiceProvider {
}
/// Start all subsystems
pub async fn run(&mut self) {
let websocket_stream = self.connect_websocket(&self.listening_address).await;
pub async fn run(&mut self) -> Result<(), NetworkRequesterError> {
let websocket_stream = self.connect_websocket(&self.listening_address).await?;
// split the websocket so that we could read and write from separate threads
let (websocket_writer, mut websocket_reader) = websocket_stream.split();
@@ -352,7 +352,7 @@ impl ServiceProvider {
Some(msg) => msg,
None => {
error!("The websocket stream has finished!");
return;
return Ok(());
}
};
@@ -371,14 +371,20 @@ impl ServiceProvider {
}
// Make the websocket connection so we can receive incoming Mixnet messages.
async fn connect_websocket(&self, uri: &str) -> TSWebsocketStream {
async fn connect_websocket(
&self,
uri: &str,
) -> Result<TSWebsocketStream, NetworkRequesterError> {
match websocket::Connection::new(uri).connect().await {
Ok(ws_stream) => {
info!("* connected to local websocket server at {}", uri);
ws_stream
Ok(ws_stream)
}
Err(WebsocketConnectionError::ConnectionNotEstablished) => {
panic!("Error: websocket connection attempt failed, is the Nym client running?")
Err(err) => {
log::error!(
"Error: websocket connection attempt failed, is the Nym client running?"
);
Err(err.into())
}
}
}
@@ -0,0 +1,10 @@
use crate::websocket::WebsocketConnectionError;
#[derive(thiserror::Error, Debug)]
pub enum NetworkRequesterError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Websocket error")]
WebsocketConnectionError(#[from] WebsocketConnectionError),
}
@@ -7,12 +7,14 @@ use clap::{Args, Parser};
use completions::{fig_generate, ArgShell};
use logging::setup_logging;
use error::NetworkRequesterError;
use network_defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use nymsphinx::addressing::clients::Recipient;
mod allowed_hosts;
mod connection;
mod core;
mod error;
mod statistics;
mod websocket;
@@ -21,24 +23,20 @@ const ENABLE_STATISTICS: &str = "enable-statistics";
#[derive(Args)]
struct Run {
/// Specifies whether this network requester should run in 'open-proxy' mode
#[clap(long)]
open_proxy: bool,
/// Websocket port to bind to
#[clap(long)]
websocket_port: Option<String>,
/// Enable service anonymized statistics that get sent to a statistics aggregator server
#[clap(long)]
enable_statistics: bool,
/// Mixnet client address where a statistics aggregator is running. The default value is a Nym aggregator client
#[clap(long)]
statistics_recipient: Option<String>,
}
impl Run {
async fn execute(&self) {
async fn execute(&self) -> Result<(), NetworkRequesterError> {
if self.open_proxy {
println!("\n\nYOU HAVE STARTED IN 'OPEN PROXY' MODE. ANYONE WITH YOUR CLIENT ADDRESS CAN MAKE REQUESTS FROM YOUR MACHINE. PLEASE QUIT IF YOU DON'T UNDERSTAND WHAT YOU'RE DOING.\n\n");
}
@@ -68,7 +66,7 @@ impl Run {
self.enable_statistics,
stats_provider_addr,
);
server.run().await;
server.run().await
}
}
@@ -91,20 +89,21 @@ struct Cli {
command: Commands,
}
pub(crate) async fn execute(args: Cli) {
pub(crate) async fn execute(args: Cli) -> Result<(), NetworkRequesterError> {
let bin_name = "nym-network-requester";
match &args.command {
Commands::Run(r) => r.execute().await,
Commands::Run(r) => r.execute().await?,
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name),
}
Ok(())
}
#[tokio::main]
async fn main() {
async fn main() -> Result<(), NetworkRequesterError> {
setup_logging();
let args = Cli::parse();
execute(args).await;
execute(args).await
}
@@ -5,7 +5,7 @@ use thiserror::Error;
#[derive(Debug, Error)]
pub enum StatsError {
#[error("Reqwuest error {0}")]
#[error("Reqwest error {0}")]
ReqwestError(#[from] reqwest::Error),
#[error("Invalid stats provider client address")]
@@ -22,12 +22,13 @@ impl Connection {
pub async fn connect(&self) -> Result<TSWebsocketStream, WebsocketConnectionError> {
match connect_async(&self.uri).await {
Ok((ws_stream, _)) => Ok(ws_stream),
Err(_e) => Err(WebsocketConnectionError::ConnectionNotEstablished),
Err(e) => Err(WebsocketConnectionError::ConnectionNotEstablished(e)),
}
}
}
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum WebsocketConnectionError {
ConnectionNotEstablished,
#[error("Connection not established")]
ConnectionNotEstablished(tokio_tungstenite::tungstenite::Error),
}
@@ -503,7 +503,7 @@ impl PacketPreparer {
}
// convert our hashmap back into a vec
let packets = all_gateway_packets.into_iter().map(|(_, v)| v).collect();
let packets = all_gateway_packets.into_values().collect();
PreparedPackets {
packets,