Compare commits

..

14 Commits

Author SHA1 Message Date
mx 7e7200a7c8 added note that operators can decrease self bond via wallet 2023-04-24 15:07:41 +02:00
Jon Häggblad 3f0d4846df Fix a few clippy warnings in contract test code (#3340)
* ci: don't fail fast for contracts

* contracts: fix clippy in tests
2023-04-24 10:30:22 +02:00
Jon Häggblad 9bfcdbe8e2 Add --all-targets to clippy for contracts (#3337) 2023-04-21 11:47:43 +02:00
Tommy Verrall 8c4885ce2c Merge pull request #3294 from nymtech/feature/fix-clippy-warnings
A branch with all clippy warnings dealt with in contracts
2023-04-21 10:37:33 +01:00
Tommy Verrall 926389df89 Merge pull request #3300 from nymtech/bugfix/empty-ack-stream-map
make sure to clear inner 'ack_map' in 'GatewaysReader'
2023-04-20 08:31:31 +01:00
Tommy Verrall b55db00408 Merge pull request #3324 from nymtech/bugfix/nym-cli-gateway-commands
exposed missing gateway commands in nym-cli
2023-04-20 07:55:56 +01:00
Jędrzej Stuczyński cfcb64f7e5 Feature/reduce pledge (#3254)
* basic contract work for 'decrease_pledge' functionality

note: it doesn't yet return tokens back to the operator

* returning extra tokens after decreasing pledge

* added vesting message to track pledge decrease

* attaching the track message when processing delegation decrease

* checking for zero value request

* fixed event test

* allowing to decrease pledge from the vesting contract

* integration test for the feature

* reorganised the integration tests

* updated nyxd client traits

* wallet support

* typescript helpers

* moved 'pledge more' functionality to operator commands

* cli commands for decreasing pledge

* changed error variant to make clippy happier

* removed unused import

* eslint

* fixed post-rebase imports

* added cargo config

* added PendingMixNodeChanges to MixNodeDetails

* returning event id after creating it

* Streamlined getting mixnode details by identity key

* setting pending pledge changes on increase/decrease

* clearing the value on resolving the event

* checking for correct invariants when clearing events

* further pending events unit tests fixes

* new unit tests for tx endpoints

* queries for pending events (by id)

* migration code

* using default value for pending changes if unavailable

* improved integration test assertions
2023-04-20 07:52:10 +01:00
Jon Häggblad 9c6c5f5170 Add --all-targets to nym-wallet CI clippy (#3326) 2023-04-19 10:44:46 +02:00
Jon Häggblad f28888e3e7 Update Cargo.lock files after bumping internal versions during 1.1.15 release 2023-04-19 09:41:03 +02:00
Jędrzej Stuczyński 9549bed8bb exposed missing gateway commands in nym-cli 2023-04-18 16:28:13 +01:00
Jędrzej Stuczyński 7a50f0c3b2 make sure to clear inner 'ack_map' in 'GatewaysReader' 2023-04-13 10:51:03 +01:00
Dave Hrycyszyn 2da6a2fbfa Adding a clippy.toml so we can see correct warnings in mixnet contract 2023-04-11 14:57:04 +01:00
Dave Hrycyszyn d910a4e0ee The make test target seems to be wrapping differently than local 2023-04-11 14:53:51 +01:00
Dave Hrycyszyn 672ab79421 A branch with all clippy warnings dealt with in contracts 2023-04-11 14:46:45 +01:00
150 changed files with 3789 additions and 1990 deletions
@@ -69,7 +69,7 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build command: build
args: --workspace --release --all --features cpucycles args: --workspace --release --all
- name: Install Rust stable - name: Install Rust stable
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
+2 -1
View File
@@ -30,6 +30,7 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' }} continue-on-error: ${{ matrix.rust == 'nightly' }}
needs: matrix_prep needs: matrix_prep
strategy: strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}} matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -63,4 +64,4 @@ jobs:
if: ${{ matrix.rust != 'nightly' }} if: ${{ matrix.rust != 'nightly' }}
with: with:
command: clippy command: clippy
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings args: --manifest-path contracts/Cargo.toml --workspace --all-targets -- -D warnings
+1 -1
View File
@@ -64,4 +64,4 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: clippy command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features -- -D warnings args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features --all-targets -- -D warnings
Generated
+25 -6
View File
@@ -3809,9 +3809,8 @@ dependencies = [
"curve25519-dalek", "curve25519-dalek",
"fastrand", "fastrand",
"getrandom 0.2.8", "getrandom 0.2.8",
"rand 0.7.3",
"rayon", "rayon",
"sphinx-packet", "sphinx-packet 0.1.0 (git+https://github.com/nymtech/sphinx.git)",
"thiserror", "thiserror",
"zeroize", "zeroize",
] ]
@@ -4062,7 +4061,6 @@ dependencies = [
"nym-sphinx-addressing", "nym-sphinx-addressing",
"nym-sphinx-params", "nym-sphinx-params",
"nym-sphinx-types", "nym-sphinx-types",
"thiserror",
] ]
[[package]] [[package]]
@@ -4090,9 +4088,7 @@ dependencies = [
name = "nym-sphinx-types" name = "nym-sphinx-types"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"nym-outfox", "sphinx-packet 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sphinx-packet",
"thiserror",
] ]
[[package]] [[package]]
@@ -5893,6 +5889,29 @@ dependencies = [
"subtle 2.4.1", "subtle 2.4.1",
] ]
[[package]]
name = "sphinx-packet"
version = "0.1.0"
source = "git+https://github.com/nymtech/sphinx.git#ca107d94360cdf8bbfbdb12fe5320ed74f80e40c"
dependencies = [
"aes 0.7.5",
"arrayref",
"blake2",
"bs58",
"byteorder",
"chacha",
"curve25519-dalek",
"digest 0.9.0",
"hkdf 0.11.0",
"hmac 0.11.0",
"lioness",
"log",
"rand 0.7.3",
"rand_distr",
"sha2 0.9.9",
"subtle 2.4.1",
]
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.5.2" version = "0.5.2"
+4 -12
View File
@@ -16,7 +16,6 @@ use nym_client_core::client::received_buffer::{
}; };
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
use nym_task::TaskManager; use nym_task::TaskManager;
use nym_validator_client::nyxd::QueryNyxdClient; use nym_validator_client::nyxd::QueryNyxdClient;
@@ -120,7 +119,6 @@ impl SocketClient {
self_address, self_address,
shared_lane_queue_lengths, shared_lane_queue_lengths,
reply_controller_sender, reply_controller_sender,
None,
); );
websocket::Listener::new(config.get_listening_ip(), config.get_listening_port()) websocket::Listener::new(config.get_listening_ip(), config.get_listening_port())
@@ -180,10 +178,7 @@ impl SocketClient {
Ok(started_client.task_manager) Ok(started_client.task_manager)
} }
pub async fn start_direct( pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
self,
packet_type: Option<PacketType>,
) -> Result<DirectClient, ClientError> {
if self.config.get_socket_type().is_websocket() { if self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode); return Err(ClientError::InvalidSocketMode);
} }
@@ -229,7 +224,6 @@ impl SocketClient {
reconstructed_receiver, reconstructed_receiver,
address, address,
shutdown_notifier: started_client.task_manager, shutdown_notifier: started_client.task_manager,
packet_type,
}) })
} }
} }
@@ -243,7 +237,6 @@ pub struct DirectClient {
// we need to keep reference to this guy otherwise things will start dropping // we need to keep reference to this guy otherwise things will start dropping
shutdown_notifier: TaskManager, shutdown_notifier: TaskManager,
packet_type: Option<PacketType>,
} }
impl DirectClient { impl DirectClient {
@@ -264,7 +257,7 @@ impl DirectClient {
/// well enough in local tests) /// well enough in local tests)
pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec<u8>) { pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec<u8>) {
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type); let input_msg = InputMessage::new_regular(recipient, message, lane);
self.client_input self.client_input
.input_sender .input_sender
@@ -283,8 +276,7 @@ impl DirectClient {
reply_surbs: u32, reply_surbs: u32,
) { ) {
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let input_msg = let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
self.client_input self.client_input
.input_sender .input_sender
@@ -298,7 +290,7 @@ impl DirectClient {
/// well enough in local tests) /// well enough in local tests)
pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec<u8>) { pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec<u8>) {
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type); let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
self.client_input self.client_input
.input_sender .input_sender
+3 -10
View File
@@ -14,7 +14,6 @@ use nym_client_core::client::{
use nym_client_websocket_requests::{requests::ClientRequest, responses::ServerResponse}; use nym_client_websocket_requests::{requests::ClientRequest, responses::ServerResponse};
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_sphinx::receiver::ReconstructedMessage; use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::connections::{ use nym_task::connections::{
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane, ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
@@ -42,7 +41,6 @@ pub(crate) struct HandlerBuilder {
self_full_address: Recipient, self_full_address: Recipient,
lane_queue_lengths: LaneQueueLengths, lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender, reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
} }
impl HandlerBuilder { impl HandlerBuilder {
@@ -53,7 +51,6 @@ impl HandlerBuilder {
self_full_address: &Recipient, self_full_address: &Recipient,
lane_queue_lengths: LaneQueueLengths, lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender, reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
) -> Self { ) -> Self {
Self { Self {
msg_input, msg_input,
@@ -62,7 +59,6 @@ impl HandlerBuilder {
self_full_address: *self_full_address, self_full_address: *self_full_address,
lane_queue_lengths, lane_queue_lengths,
reply_controller_sender, reply_controller_sender,
packet_type,
} }
} }
@@ -77,7 +73,6 @@ impl HandlerBuilder {
received_response_type: Default::default(), received_response_type: Default::default(),
lane_queue_lengths: self.lane_queue_lengths.clone(), lane_queue_lengths: self.lane_queue_lengths.clone(),
reply_controller_sender: self.reply_controller_sender.clone(), reply_controller_sender: self.reply_controller_sender.clone(),
packet_type: self.packet_type,
} }
} }
} }
@@ -91,7 +86,6 @@ pub(crate) struct Handler {
received_response_type: ReceivedResponseType, received_response_type: ReceivedResponseType,
lane_queue_lengths: LaneQueueLengths, lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender, reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
} }
impl Drop for Handler { impl Drop for Handler {
@@ -166,7 +160,7 @@ impl Handler {
}); });
// the ack control is now responsible for chunking, etc. // the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type); let input_msg = InputMessage::new_regular(recipient, message, lane);
self.msg_input self.msg_input
.send(input_msg) .send(input_msg)
.await .await
@@ -197,8 +191,7 @@ impl Handler {
TransmissionLane::ConnectionId(id) TransmissionLane::ConnectionId(id)
}); });
let input_msg = let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
self.msg_input self.msg_input
.send(input_msg) .send(input_msg)
.await .await
@@ -225,7 +218,7 @@ impl Handler {
TransmissionLane::ConnectionId(id) TransmissionLane::ConnectionId(id)
}); });
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type); let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
self.msg_input self.msg_input
.send(input_msg) .send(input_msg)
.await .await
+3 -9
View File
@@ -15,7 +15,6 @@ use nym_client_core::client::{inbound_messages::InputMessage, key_manager::KeyMa
use nym_credential_storage::ephemeral_storage::EphemeralStorage; use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
use nym_task::TaskManager; use nym_task::TaskManager;
use rand::rngs::OsRng; use rand::rngs::OsRng;
@@ -36,7 +35,6 @@ pub struct NymClient {
// even though we don't use graceful shutdowns, other components rely on existence of this struct // even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline // and if it's dropped, everything will start going offline
_task_manager: TaskManager, _task_manager: TaskManager,
packet_type: Option<PacketType>,
} }
#[wasm_bindgen] #[wasm_bindgen]
@@ -54,7 +52,6 @@ pub struct NymClientBuilder {
bandwidth_controller: bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>, Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
disabled_credentials: bool, disabled_credentials: bool,
packet_type: Option<PacketType>,
} }
#[wasm_bindgen] #[wasm_bindgen]
@@ -69,7 +66,6 @@ impl NymClientBuilder {
on_message, on_message,
bandwidth_controller: None, bandwidth_controller: None,
disabled_credentials: true, disabled_credentials: true,
packet_type: None,
} }
} }
@@ -143,7 +139,6 @@ impl NymClientBuilder {
self_address, self_address,
client_input: Arc::new(client_input), client_input: Arc::new(client_input),
_task_manager: started_client.task_manager, _task_manager: started_client.task_manager,
packet_type: self.packet_type,
})) }))
}) })
} }
@@ -195,7 +190,7 @@ impl NymClient {
}; };
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type); let input_msg = InputMessage::new_regular(recipient, message, lane);
self.client_input.send_message(input_msg) self.client_input.send_message(input_msg)
} }
@@ -224,8 +219,7 @@ impl NymClient {
}; };
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let input_msg = let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
self.client_input.send_message(input_msg) self.client_input.send_message(input_msg)
} }
@@ -245,7 +239,7 @@ impl NymClient {
}; };
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(sender_tag, message, lane, self.packet_type); let input_msg = InputMessage::new_reply(sender_tag, message, lane);
self.client_input.send_message(input_msg) self.client_input.send_message(input_msg)
} }
} }
@@ -400,7 +400,7 @@ where
Ok(()) Ok(())
} }
// controller for sending packets to mixnet (either real traffic or cover traffic) // controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership // TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for // over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests? // requests?
@@ -515,11 +515,11 @@ where
task_manager.subscribe(), task_manager.subscribe(),
); );
// The message_sender is the transmitter for any component generating sphinx packets // The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real // that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream. // traffic stream.
// The MixTrafficController then sends the actual traffic // The MixTrafficController then sends the actual traffic
let message_sender = let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe()); Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic // Channels that the websocket listener can use to signal downstream to the real traffic
@@ -541,7 +541,7 @@ where
shared_topology_accessor.clone(), shared_topology_accessor.clone(),
ack_receiver, ack_receiver,
input_receiver, input_receiver,
message_sender.clone(), sphinx_message_sender.clone(),
reply_storage, reply_storage,
reply_controller_sender.clone(), reply_controller_sender.clone(),
reply_controller_receiver, reply_controller_receiver,
@@ -560,7 +560,7 @@ where
self.key_manager.ack_key(), self.key_manager.ack_key(),
self_address, self_address,
shared_topology_accessor.clone(), shared_topology_accessor.clone(),
message_sender, sphinx_message_sender,
task_manager.subscribe(), task_manager.subscribe(),
); );
} }
@@ -45,7 +45,7 @@ where
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
next_delay: Pin<Box<wasm_timer::Delay>>, next_delay: Pin<Box<wasm_timer::Delay>>,
/// Channel used for sending prepared nym packets to `MixTrafficController` that sends them /// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays. /// out to the network without any further delays.
mix_tx: BatchMixMessageSender, mix_tx: BatchMixMessageSender,
@@ -1,6 +1,5 @@
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>; pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
@@ -42,36 +41,14 @@ pub enum InputMessage {
data: Vec<u8>, data: Vec<u8>,
lane: TransmissionLane, lane: TransmissionLane,
}, },
MessageWrapper {
message: Box<InputMessage>,
packet_type: PacketType,
},
} }
impl InputMessage { impl InputMessage {
pub fn new_wrapper(message: InputMessage, packet_type: PacketType) -> Self { pub fn new_regular(recipient: Recipient, data: Vec<u8>, lane: TransmissionLane) -> Self {
InputMessage::MessageWrapper { InputMessage::Regular {
message: Box::new(message),
packet_type,
}
}
pub fn new_regular(
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Regular {
recipient, recipient,
data, data,
lane, lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
} }
} }
@@ -80,18 +57,12 @@ impl InputMessage {
data: Vec<u8>, data: Vec<u8>,
reply_surbs: u32, reply_surbs: u32,
lane: TransmissionLane, lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self { ) -> Self {
let message = InputMessage::Anonymous { InputMessage::Anonymous {
recipient, recipient,
data, data,
reply_surbs, reply_surbs,
lane, lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
} }
} }
@@ -99,17 +70,11 @@ impl InputMessage {
recipient_tag: AnonymousSenderTag, recipient_tag: AnonymousSenderTag,
data: Vec<u8>, data: Vec<u8>,
lane: TransmissionLane, lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self { ) -> Self {
let message = InputMessage::Reply { InputMessage::Reply {
recipient_tag, recipient_tag,
data, data,
lane, lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
} }
} }
@@ -118,7 +83,6 @@ impl InputMessage {
InputMessage::Regular { lane, .. } InputMessage::Regular { lane, .. }
| InputMessage::Anonymous { lane, .. } | InputMessage::Anonymous { lane, .. }
| InputMessage::Reply { lane, .. } => lane, | InputMessage::Reply { lane, .. } => lane,
InputMessage::MessageWrapper { message, .. } => message.lane(),
} }
} }
} }
+3 -3
View File
@@ -39,15 +39,15 @@ where
pub fn new( pub fn new(
gateway_client: GatewayClient<C, St>, gateway_client: GatewayClient<C, St>,
) -> (MixTrafficController<C, St>, BatchMixMessageSender) { ) -> (MixTrafficController<C, St>, BatchMixMessageSender) {
let (message_sender, message_receiver) = let (sphinx_message_sender, sphinx_message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE); tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
( (
MixTrafficController { MixTrafficController {
gateway_client, gateway_client,
mix_rx: message_receiver, mix_rx: sphinx_message_receiver,
consecutive_gateway_failure_count: 0, consecutive_gateway_failure_count: 0,
}, },
message_sender, sphinx_message_sender,
) )
} }
@@ -7,7 +7,6 @@ use crate::client::replies::reply_controller::ReplyControllerSender;
use log::*; use log::*;
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng}; use rand::{CryptoRng, Rng};
@@ -58,11 +57,10 @@ where
recipient: Recipient, recipient: Recipient,
content: Vec<u8>, content: Vec<u8>,
lane: TransmissionLane, lane: TransmissionLane,
packet_type: PacketType,
) { ) {
if let Err(err) = self if let Err(err) = self
.message_handler .message_handler
.try_send_plain_message(recipient, content, lane, packet_type) .try_send_plain_message(recipient, content, lane)
.await .await
{ {
warn!("failed to send a plain message - {err}") warn!("failed to send a plain message - {err}")
@@ -75,11 +73,10 @@ where
content: Vec<u8>, content: Vec<u8>,
reply_surbs: u32, reply_surbs: u32,
lane: TransmissionLane, lane: TransmissionLane,
packet_type: PacketType,
) { ) {
if let Err(err) = self if let Err(err) = self
.message_handler .message_handler
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type) .try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane)
.await .await
{ {
warn!("failed to send a repliable message - {err}") warn!("failed to send a repliable message - {err}")
@@ -92,17 +89,14 @@ where
recipient, recipient,
data, data,
lane, lane,
} => { } => self.handle_plain_message(recipient, data, lane).await,
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
.await
}
InputMessage::Anonymous { InputMessage::Anonymous {
recipient, recipient,
data, data,
reply_surbs, reply_surbs,
lane, lane,
} => { } => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix) self.handle_repliable_message(recipient, data, reply_surbs, lane)
.await .await
} }
InputMessage::Reply { InputMessage::Reply {
@@ -112,37 +106,6 @@ where
} => { } => {
self.handle_reply(recipient_tag, data, lane).await; self.handle_reply(recipient_tag, data, lane).await;
} }
InputMessage::MessageWrapper {
message,
packet_type,
} => match *message {
InputMessage::Regular {
recipient,
data,
lane,
} => {
self.handle_plain_message(recipient, data, lane, packet_type)
.await
}
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
.await
}
InputMessage::Reply {
recipient_tag,
data,
lane,
} => {
self.handle_reply(recipient_tag, data, lane).await;
}
// MessageWrappers can't be nested
InputMessage::MessageWrapper { .. } => unimplemented!(),
},
}; };
} }
@@ -11,9 +11,9 @@ use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use crate::client::replies::reply_controller::ReplyControllerSender; use crate::client::replies::reply_controller::ReplyControllerSender;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::chunking::fragment::Fragment; use nym_sphinx::chunking::fragment::Fragment;
use nym_sphinx::preparer::PreparedFragment; use nym_sphinx::preparer::PreparedFragment;
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng}; use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@@ -51,10 +51,8 @@ where
) -> Result<PreparedFragment, PreparationError> { ) -> Result<PreparedFragment, PreparationError> {
debug!("retransmitting normal packet..."); debug!("retransmitting normal packet...");
// TODO: Figure out retransmission packet type signaling
self.message_handler self.message_handler
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, PacketType::Mix) .try_prepare_single_chunk_for_sending(packet_recipient, chunk_data)
.await .await
} }
@@ -15,7 +15,7 @@ use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessa
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey}; use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier}; use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx::message::NymMessage; use nym_sphinx::message::NymMessage;
use nym_sphinx::params::{PacketSize, PacketType, DEFAULT_NUM_MIX_HOPS}; use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment}; use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
use nym_sphinx::Delay; use nym_sphinx::Delay;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
@@ -406,10 +406,9 @@ where
recipient: Recipient, recipient: Recipient,
message: Vec<u8>, message: Vec<u8>,
lane: TransmissionLane, lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> { ) -> Result<(), PreparationError> {
let message = NymMessage::new_plain(message); let message = NymMessage::new_plain(message);
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type) self.try_split_and_send_non_reply_message(message, recipient, lane)
.await .await
} }
@@ -418,7 +417,6 @@ where
message: NymMessage, message: NymMessage,
recipient: Recipient, recipient: Recipient,
lane: TransmissionLane, lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> { ) -> Result<(), PreparationError> {
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised // TODO: I really dislike existence of this assertion, it implies code has to be re-organised
debug_assert!(!matches!(message, NymMessage::Reply(_))); debug_assert!(!matches!(message, NymMessage::Reply(_)));
@@ -444,7 +442,6 @@ where
topology, topology,
&self.config.ack_key, &self.config.ack_key,
&recipient, &recipient,
packet_type,
)?; )?;
let real_message = let real_message =
@@ -466,7 +463,6 @@ where
&mut self, &mut self,
recipient: Recipient, recipient: Recipient,
amount: u32, amount: u32,
packet_type: PacketType,
) -> Result<(), PreparationError> { ) -> Result<(), PreparationError> {
let sender_tag = self.get_or_create_sender_tag(&recipient); let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) = let (reply_surbs, reply_keys) =
@@ -481,7 +477,6 @@ where
message, message,
recipient, recipient,
TransmissionLane::AdditionalReplySurbs, TransmissionLane::AdditionalReplySurbs,
packet_type,
) )
.await?; .await?;
@@ -497,7 +492,6 @@ where
message: Vec<u8>, message: Vec<u8>,
num_reply_surbs: u32, num_reply_surbs: u32,
lane: TransmissionLane, lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), SurbWrappedPreparationError> { ) -> Result<(), SurbWrappedPreparationError> {
let sender_tag = self.get_or_create_sender_tag(&recipient); let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) = self let (reply_surbs, reply_keys) = self
@@ -507,7 +501,7 @@ where
let message = let message =
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs)); NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type) self.try_split_and_send_non_reply_message(message, recipient, lane)
.await?; .await?;
log::trace!("storing {} reply keys", reply_keys.len()); log::trace!("storing {} reply keys", reply_keys.len());
@@ -520,20 +514,13 @@ where
&mut self, &mut self,
recipient: Recipient, recipient: Recipient,
chunk: Fragment, chunk: Fragment,
packet_type: PacketType,
) -> Result<PreparedFragment, PreparationError> { ) -> Result<PreparedFragment, PreparationError> {
let topology_permit = self.topology_access.get_read_permit().await; let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?; let topology = self.get_topology(&topology_permit)?;
let prepared_fragment = self let prepared_fragment = self
.message_preparer .message_preparer
.prepare_chunk_for_sending( .prepare_chunk_for_sending(chunk, topology, &self.config.ack_key, &recipient)
chunk,
topology,
&self.config.ack_key,
&recipient,
packet_type,
)
.unwrap(); .unwrap();
Ok(prepared_fragment) Ok(prepared_fragment)
@@ -92,7 +92,7 @@ where
// messages. // messages.
sending_delay_controller: SendingDelayController, sending_delay_controller: SendingDelayController,
/// Channel used for sending prepared packets to `MixTrafficController` that sends them /// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays. /// out to the network without any further delays.
mix_tx: BatchMixMessageSender, mix_tx: BatchMixMessageSender,
@@ -136,7 +136,7 @@ impl From<PreparedFragment> for RealMessage {
impl RealMessage { impl RealMessage {
pub(crate) fn packet_size(&self) -> usize { pub(crate) fn packet_size(&self) -> usize {
self.mix_packet.packet().len() self.mix_packet.sphinx_packet().len()
} }
pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self { pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self {
@@ -386,7 +386,7 @@ where
// On every iteration we get new messages from upstream. Given that these come bunched // On every iteration we get new messages from upstream. Given that these come bunched
// in `Vec`, this ensures that on average we will fetch messages faster than we can // in `Vec`, this ensures that on average we will fetch messages faster than we can
// send, which is a condition for being able to multiplex packets from multiple // send, which is a condition for being able to multiplex sphinx packets from multiple
// data streams. // data streams.
match Pin::new(&mut self.real_receiver).poll_recv(cx) { match Pin::new(&mut self.real_receiver).poll_recv(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed // in the case our real message channel stream was closed, we should also indicate we are closed
@@ -512,11 +512,7 @@ where
let to_send = min(remaining, 100); let to_send = min(remaining, 100);
if let Err(err) = self if let Err(err) = self
.message_handler .message_handler
.try_send_additional_reply_surbs( .try_send_additional_reply_surbs(recipient, to_send)
recipient,
to_send,
nym_sphinx::params::PacketType::Mix,
)
.await .await
{ {
warn!("failed to send additional surbs to {recipient} - {err}"); warn!("failed to send additional surbs to {recipient} - {err}");
@@ -28,7 +28,7 @@ impl SizedData for RealMessage {
impl SizedData for Fragment { impl SizedData for Fragment {
fn data_size(&self) -> usize { fn data_size(&self) -> usize {
// note that raw `Fragment` is smaller than packet payload // note that raw `Fragment` is smaller than sphinx packet payload
// as it doesn't include surb-ack or the [shared] key materials // as it doesn't include surb-ack or the [shared] key materials
self.payload_size() self.payload_size()
} }
@@ -605,7 +605,7 @@ where
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 { fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
packets packets
.iter() .iter()
.map(|packet| packet.packet().len()) .map(|packet| packet.sphinx_packet().len())
.sum::<usize>() as i64 .sum::<usize>() as i64
} }
@@ -686,9 +686,9 @@ where
if !self.authenticated { if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated); return Err(GatewayClientError::NotAuthenticated);
} }
if (mix_packet.packet().len() as i64) > self.bandwidth_remaining { if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth( return Err(GatewayClientError::NotEnoughBandwidth(
mix_packet.packet().len() as i64, mix_packet.sphinx_packet().len() as i64,
self.bandwidth_remaining, self.bandwidth_remaining,
)); ));
} }
+18 -15
View File
@@ -4,11 +4,10 @@
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress; use nym_sphinx::framing::codec::SphinxCodec;
use nym_sphinx::framing::codec::NymCodec; use nym_sphinx::framing::packet::FramedSphinxPacket;
use nym_sphinx::framing::packet::FramedNymPacket; use nym_sphinx::params::PacketMode;
use nym_sphinx::params::PacketType; use nym_sphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket};
use nym_sphinx::NymPacket;
use std::collections::HashMap; use std::collections::HashMap;
use std::io; use std::io;
use std::net::SocketAddr; use std::net::SocketAddr;
@@ -51,8 +50,8 @@ pub trait SendWithoutResponse {
fn send_without_response( fn send_without_response(
&mut self, &mut self,
address: NymNodeRoutingAddress, address: NymNodeRoutingAddress,
packet: NymPacket, packet: SphinxPacket,
packet_type: PacketType, packet_mode: PacketMode,
) -> io::Result<()>; ) -> io::Result<()>;
} }
@@ -62,12 +61,12 @@ pub struct Client {
} }
struct ConnectionSender { struct ConnectionSender {
channel: mpsc::Sender<FramedNymPacket>, channel: mpsc::Sender<FramedSphinxPacket>,
current_reconnection_attempt: Arc<AtomicU32>, current_reconnection_attempt: Arc<AtomicU32>,
} }
impl ConnectionSender { impl ConnectionSender {
fn new(channel: mpsc::Sender<FramedNymPacket>) -> Self { fn new(channel: mpsc::Sender<FramedSphinxPacket>) -> Self {
ConnectionSender { ConnectionSender {
channel, channel,
current_reconnection_attempt: Arc::new(AtomicU32::new(0)), current_reconnection_attempt: Arc::new(AtomicU32::new(0)),
@@ -85,7 +84,7 @@ impl Client {
async fn manage_connection( async fn manage_connection(
address: SocketAddr, address: SocketAddr,
receiver: mpsc::Receiver<FramedNymPacket>, receiver: mpsc::Receiver<FramedSphinxPacket>,
connection_timeout: Duration, connection_timeout: Duration,
current_reconnection: &AtomicU32, current_reconnection: &AtomicU32,
) { ) {
@@ -97,7 +96,7 @@ impl Client {
debug!("Managed to establish connection to {}", address); debug!("Managed to establish connection to {}", address);
// if we managed to connect, reset the reconnection count (whatever it might have been) // if we managed to connect, reset the reconnection count (whatever it might have been)
current_reconnection.store(0, Ordering::Release); current_reconnection.store(0, Ordering::Release);
Framed::new(stream, NymCodec) Framed::new(stream, SphinxCodec)
} }
Err(err) => { Err(err) => {
debug!( debug!(
@@ -149,7 +148,11 @@ impl Client {
} }
} }
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) { fn make_connection(
&mut self,
address: NymNodeRoutingAddress,
pending_packet: FramedSphinxPacket,
) {
let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size); let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
// this CAN'T fail because we just created the channel which has a non-zero capacity // this CAN'T fail because we just created the channel which has a non-zero capacity
@@ -197,12 +200,12 @@ impl SendWithoutResponse for Client {
fn send_without_response( fn send_without_response(
&mut self, &mut self,
address: NymNodeRoutingAddress, address: NymNodeRoutingAddress,
packet: NymPacket, packet: SphinxPacket,
packet_type: PacketType, packet_mode: PacketMode,
) -> io::Result<()> { ) -> io::Result<()> {
trace!("Sending packet to {:?}", address); trace!("Sending packet to {:?}", address);
let framed_packet = let framed_packet =
FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version); FramedSphinxPacket::new(packet, packet_mode, self.config.use_legacy_version);
if let Some(sender) = self.conn_new.get_mut(&address) { if let Some(sender) = self.conn_new.get_mut(&address) {
if let Err(err) = sender.channel.try_send(framed_packet) { if let Err(err) = sender.channel.try_send(framed_packet) {
@@ -59,14 +59,14 @@ impl PacketForwarder {
trace!("Going to forward packet to {:?}", mix_packet.next_hop()); trace!("Going to forward packet to {:?}", mix_packet.next_hop());
let next_hop = mix_packet.next_hop(); let next_hop = mix_packet.next_hop();
let packet_type = mix_packet.packet_type(); let packet_mode = mix_packet.packet_mode();
let packet = mix_packet.into_packet(); let sphinx_packet = mix_packet.into_sphinx_packet();
// we don't care about responses, we just want to fire packets // we don't care about responses, we just want to fire packets
// as quickly as possible // as quickly as possible
if let Err(err) = if let Err(err) =
self.mixnet_client self.mixnet_client
.send_without_response(next_hop, packet, packet_type) .send_without_response(next_hop, sphinx_packet, packet_mode)
{ {
debug!("failed to forward the packet - {err}") debug!("failed to forward the packet - {err}")
} }
@@ -24,8 +24,9 @@ use nym_mixnet_contract_common::{
MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse, MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse,
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse,
PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse, PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse,
PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse, PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventResponse,
PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg, PendingEpochEventsResponse, PendingIntervalEventResponse, PendingIntervalEventsResponse,
QueryMsg as MixnetQueryMsg,
}; };
use serde::Deserialize; use serde::Deserialize;
@@ -174,6 +175,16 @@ pub trait MixnetQueryClient {
.await .await
} }
async fn get_mixnode_details_by_identity(
&self,
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
}
async fn get_mixnode_rewarding_details( async fn get_mixnode_rewarding_details(
&self, &self,
mix_id: MixId, mix_id: MixId,
@@ -374,14 +385,20 @@ pub trait MixnetQueryClient {
.await .await
} }
async fn get_mixnode_details_by_identity( async fn get_pending_epoch_event(
&self, &self,
mix_identity: IdentityKey, event_id: EpochEventId,
) -> Result<Option<MixNodeDetails>, NyxdError> { ) -> Result<PendingEpochEventResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity { self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvent { event_id })
mix_identity, .await
}) }
.await
async fn get_pending_interval_event(
&self,
event_id: IntervalEventId,
) -> Result<PendingIntervalEventResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvent { event_id })
.await
} }
async fn get_number_of_pending_events( async fn get_number_of_pending_events(
@@ -331,6 +331,38 @@ pub trait MixnetSigningClient {
.await .await
} }
async fn decrease_pledge(
&self,
decrease_by: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DecreasePledge {
decrease_by: decrease_by.into(),
},
vec![],
)
.await
}
async fn decrease_pledge_on_behalf(
&self,
owner: AccountId,
decrease_by: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DecreasePledgeOnBehalf {
owner: owner.to_string(),
decrease_by: decrease_by.into(),
},
vec![],
)
.await
}
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> { async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![]) self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
.await .await
@@ -91,6 +91,21 @@ pub trait VestingSigningClient {
.await .await
} }
async fn vesting_decrease_pledge(
&self,
decrease_by: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::DecreasePledge {
amount: decrease_by.into(),
},
vec![],
)
.await
}
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError>; async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError>;
async fn vesting_track_unbond_mixnode( async fn vesting_track_unbond_mixnode(
@@ -6,11 +6,9 @@ use clap::{Args, Subcommand};
pub mod rewards; pub mod rewards;
pub mod delegate_to_mixnode; pub mod delegate_to_mixnode;
pub mod pledge_more;
pub mod query_for_delegations; pub mod query_for_delegations;
pub mod undelegate_from_mixnode; pub mod undelegate_from_mixnode;
pub mod vesting_delegate_to_mixnode; pub mod vesting_delegate_to_mixnode;
pub mod vesting_pledge_more;
pub mod vesting_undelegate_from_mixnode; pub mod vesting_undelegate_from_mixnode;
#[derive(Debug, Args)] #[derive(Debug, Args)]
@@ -34,8 +32,4 @@ pub enum MixnetDelegatorsCommands {
DelegateVesting(vesting_delegate_to_mixnode::Args), DelegateVesting(vesting_delegate_to_mixnode::Args),
/// Undelegate from a mixnode (when originally using locked tokens) /// Undelegate from a mixnode (when originally using locked tokens)
UndelegateVesting(vesting_undelegate_from_mixnode::Args), UndelegateVesting(vesting_undelegate_from_mixnode::Args),
/// Pledge more
PledgeMore(pledge_more::Args),
/// Pledge more with locked tokens
PledgeMoreVesting(vesting_pledge_more::Args),
} }
@@ -26,7 +26,7 @@ pub struct Args {
pub version: Option<String>, pub version: Option<String>,
} }
pub async fn vesting_update_config(client: SigningClient, args: Args) { pub async fn vesting_update_config(args: Args, client: SigningClient) {
info!("Update vesting gateway config!"); info!("Update vesting gateway config!");
let current_details = match client let current_details = match client
@@ -45,7 +45,9 @@ pub struct Args {
pub force: bool, pub force: bool,
} }
pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str) { pub async fn vesting_bond_gateway(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting vesting gateway bonding!"); info!("Starting vesting gateway bonding!");
// if we're trying to bond less than 1 token // if we're trying to bond less than 1 token
@@ -0,0 +1,29 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use nym_validator_client::nyxd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub decrease_by: u128,
}
pub async fn decrease_pledge(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting to decrease pledge");
let coin = Coin::new(args.decrease_by, denom);
let res = client
.pledge_more(coin.into(), None)
.await
.expect("failed to decrease pledge!");
info!("decreasing pledge: {:?}", res);
}
@@ -4,13 +4,17 @@
use clap::{Args, Subcommand}; use clap::{Args, Subcommand};
pub mod bond_mixnode; pub mod bond_mixnode;
pub mod decrease_pledge;
pub mod families; pub mod families;
pub mod keys; pub mod keys;
pub mod mixnode_bonding_sign_payload; pub mod mixnode_bonding_sign_payload;
pub mod pledge_more;
pub mod rewards; pub mod rewards;
pub mod settings; pub mod settings;
pub mod unbond_mixnode; pub mod unbond_mixnode;
pub mod vesting_bond_mixnode; pub mod vesting_bond_mixnode;
pub mod vesting_decrease_pledge;
pub mod vesting_pledge_more;
pub mod vesting_unbond_mixnode; pub mod vesting_unbond_mixnode;
#[derive(Debug, Args)] #[derive(Debug, Args)]
@@ -40,4 +44,12 @@ pub enum MixnetOperatorsMixnodeCommands {
UnbondVesting(vesting_unbond_mixnode::Args), UnbondVesting(vesting_unbond_mixnode::Args),
/// Create base58-encoded payload required for producing valid bonding signature. /// Create base58-encoded payload required for producing valid bonding signature.
CreateMixnodeBondingSignPayload(mixnode_bonding_sign_payload::Args), CreateMixnodeBondingSignPayload(mixnode_bonding_sign_payload::Args),
/// Pledge more
PledgeMore(pledge_more::Args),
/// Pledge more with locked tokens
PledgeMoreVesting(vesting_pledge_more::Args),
/// Decrease pledge
DecreasePledge(decrease_pledge::Args),
/// Decrease pledge with locked tokens
DecreasePledgeVesting(vesting_decrease_pledge::Args),
} }
@@ -0,0 +1,29 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use nym_validator_client::nyxd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub decrease_by: u128,
}
pub async fn vesting_decrease_pledge(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting vesting to decrease pledge");
let coin = Coin::new(args.decrease_by, denom);
let res = client
.vesting_decrease_pledge(coin.into(), None)
.await
.expect("failed to vesting decrease pledge!");
info!("vesting decreasing pledge: {:?}", res);
}
@@ -1,13 +1,16 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::{EpochState, IdentityKey, MixId}; use crate::{EpochEventId, EpochState, IdentityKey, MixId};
use contracts_common::signing::verifier::ApiVerifierError; use contracts_common::signing::verifier::ApiVerifierError;
use cosmwasm_std::{Addr, Coin, Decimal}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug, PartialEq)] #[derive(Error, Debug, PartialEq)]
pub enum MixnetContractError { pub enum MixnetContractError {
#[error("could not perform contract migration: {comment}")]
FailedMigration { comment: String },
#[error("{source}")] #[error("{source}")]
StdErr { StdErr {
#[from] #[from]
@@ -26,6 +29,17 @@ pub enum MixnetContractError {
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")] #[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
InsufficientPledge { received: Coin, minimum: Coin }, InsufficientPledge { received: Coin, minimum: Coin },
#[error("Attempted to reduce node pledge ({current}{denom} - {decrease_by}{denom}) below the minimum amount: {minimum}{denom}")]
InvalidPledgeReduction {
current: Uint128,
decrease_by: Uint128,
minimum: Uint128,
denom: String,
},
#[error("A pledge change is already pending in this epoch. The event id: {pending_event_id}")]
PendingPledgeChange { pending_event_id: EpochEventId },
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")] #[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
InsufficientDelegation { received: Coin, minimum: Coin }, InsufficientDelegation { received: Coin, minimum: Coin },
@@ -190,6 +204,9 @@ pub enum MixnetContractError {
#[error("epoch duration must be > 0")] #[error("epoch duration must be > 0")]
EpochDurationZero, EpochDurationZero,
#[error("attempted to perform the operation with 0 coins. This is not allowed")]
ZeroCoinAmount,
#[error("this validator ({current_validator}) is not the one responsible for advancing this epoch. It's responsibility of {chosen_validator}.")] #[error("this validator ({current_validator}) is not the one responsible for advancing this epoch. It's responsibility of {chosen_validator}.")]
RewardingValidatorMismatch { RewardingValidatorMismatch {
current_validator: Addr, current_validator: Addr,
@@ -226,3 +243,11 @@ pub enum MixnetContractError {
source: ApiVerifierError, source: ApiVerifierError,
}, },
} }
impl MixnetContractError {
pub fn inconsistent_state<S: Into<String>>(comment: S) -> Self {
MixnetContractError::InconsistentState {
comment: comment.into(),
}
}
}
@@ -15,6 +15,8 @@ pub enum MixnetEventType {
MixnodeBonding, MixnodeBonding,
PendingPledgeIncrease, PendingPledgeIncrease,
PledgeIncrease, PledgeIncrease,
PendingPledgeDecrease,
PledgeDecrease,
GatewayBonding, GatewayBonding,
GatewayUnbonding, GatewayUnbonding,
PendingMixnodeUnbonding, PendingMixnodeUnbonding,
@@ -58,6 +60,8 @@ impl ToString for MixnetEventType {
MixnetEventType::MixnodeBonding => "mixnode_bonding", MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase", MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase",
MixnetEventType::PledgeIncrease => "pledge_increase", MixnetEventType::PledgeIncrease => "pledge_increase",
MixnetEventType::PendingPledgeDecrease => "pending_pledge_decrease",
MixnetEventType::PledgeDecrease => "pledge_decrease",
MixnetEventType::GatewayBonding => "gateway_bonding", MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding", MixnetEventType::GatewayUnbonding => "gateway_unbonding",
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding", MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
@@ -354,6 +358,19 @@ pub fn new_pledge_increase_event(created_at: BlockHeight, mix_id: MixId, amount:
.add_attribute(AMOUNT_KEY, amount.to_string()) .add_attribute(AMOUNT_KEY, amount.to_string())
} }
pub fn new_pending_pledge_decrease_event(mix_id: MixId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PendingPledgeDecrease)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pledge_decrease_event(created_at: BlockHeight, mix_id: MixId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PledgeDecrease)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Event { pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding) Event::new(MixnetEventType::MixnodeUnbonding)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string()) .add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
@@ -3,7 +3,10 @@
use crate::error::MixnetContractError; use crate::error::MixnetContractError;
use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent}; use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent};
use crate::{EpochId, IntervalId, MixId}; use crate::{
EpochEventId, EpochId, IntervalEventId, IntervalId, MixId, PendingEpochEventData,
PendingIntervalEventData,
};
use cosmwasm_std::{Addr, Env}; use cosmwasm_std::{Addr, Env};
use schemars::gen::SchemaGenerator; use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject}; use schemars::schema::{InstanceType, Schema, SchemaObject};
@@ -528,6 +531,30 @@ impl PendingIntervalEventsResponse {
} }
} }
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingEpochEventResponse {
pub event_id: EpochEventId,
pub event: Option<PendingEpochEventData>,
}
impl PendingEpochEventResponse {
pub fn new(event_id: EpochEventId, event: Option<PendingEpochEventData>) -> Self {
PendingEpochEventResponse { event_id, event }
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingIntervalEventResponse {
pub event_id: IntervalEventId,
pub event: Option<PendingIntervalEventData>,
}
impl PendingIntervalEventResponse {
pub fn new(event_id: IntervalEventId, event: Option<PendingIntervalEventData>) -> Self {
PendingIntervalEventResponse { event_id, event }
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct NumberOfPendingEventsResponse { pub struct NumberOfPendingEventsResponse {
pub epoch_events: u32, pub epoch_events: u32,
@@ -32,7 +32,8 @@ pub use gateway::{
}; };
pub use interval::{ pub use interval::{
CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse, CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse,
PendingEpochEventsResponse, PendingIntervalEventsResponse, PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEventResponse,
PendingIntervalEventsResponse,
}; };
pub use mixnode::{ pub use mixnode::{
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails, Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
@@ -10,7 +10,7 @@ use crate::helpers::IntoBaseDecimal;
use crate::reward_params::{NodeRewardParams, RewardingParams}; use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward; use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution; use crate::rewarding::RewardDistribution;
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey}; use crate::{Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128}; use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -37,13 +37,20 @@ impl RewardedSetNodeStatus {
pub struct MixNodeDetails { pub struct MixNodeDetails {
pub bond_information: MixNodeBond, pub bond_information: MixNodeBond,
pub rewarding_details: MixNodeRewarding, pub rewarding_details: MixNodeRewarding,
#[serde(default)]
pub pending_changes: PendingMixNodeChanges,
} }
impl MixNodeDetails { impl MixNodeDetails {
pub fn new(bond_information: MixNodeBond, rewarding_details: MixNodeRewarding) -> Self { pub fn new(
bond_information: MixNodeBond,
rewarding_details: MixNodeRewarding,
pending_changes: PendingMixNodeChanges,
) -> Self {
MixNodeDetails { MixNodeDetails {
bond_information, bond_information,
rewarding_details, rewarding_details,
pending_changes,
} }
} }
@@ -73,6 +80,10 @@ impl MixNodeDetails {
pub fn total_stake(&self) -> Decimal { pub fn total_stake(&self) -> Decimal {
self.rewarding_details.node_bond() self.rewarding_details.node_bond()
} }
pub fn pending_pledge_change(&self) -> Option<EpochEventId> {
self.pending_changes.pledge_change
}
} }
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
@@ -332,6 +343,22 @@ impl MixNodeRewarding {
Ok(()) Ok(())
} }
/// Decreases total pledge of operator by the specified amount.
pub fn decrease_operator_uint128(
&mut self,
amount: Uint128,
) -> Result<(), MixnetContractError> {
let amount_decimal = amount.into_base_decimal()?;
if self.operator < amount_decimal {
return Err(MixnetContractError::OverflowDecimalSubtraction {
minuend: self.operator,
subtrahend: amount_decimal,
});
}
self.operator -= amount_decimal;
Ok(())
}
pub fn increase_delegates_uint128( pub fn increase_delegates_uint128(
&mut self, &mut self,
amount: Uint128, amount: Uint128,
@@ -601,6 +628,25 @@ impl From<Layer> for u8 {
} }
} }
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/PendingMixnodeChanges.ts")
)]
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PendingMixNodeChanges {
pub pledge_change: Option<EpochEventId>,
// pub cost_params_change: Option<IntervalEventId>,
}
impl PendingMixNodeChanges {
pub fn new_empty() -> PendingMixNodeChanges {
PendingMixNodeChanges {
pledge_change: None,
}
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr( #[cfg_attr(
feature = "generate-ts", feature = "generate-ts",
@@ -10,10 +10,13 @@ use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{ use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams, IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
}; };
use crate::{delegation, ContractStateParams, Layer, LayerAssignment, MixId, Percent}; use crate::{
delegation, ContractStateParams, EpochEventId, IntervalEventId, Layer, LayerAssignment, MixId,
Percent,
};
use crate::{Gateway, IdentityKey, MixNode}; use crate::{Gateway, IdentityKey, MixNode};
use contracts_common::signing::MessageSignature; use contracts_common::signing::MessageSignature;
use cosmwasm_std::Decimal; use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
@@ -161,6 +164,13 @@ pub enum ExecuteMsg {
PledgeMoreOnBehalf { PledgeMoreOnBehalf {
owner: String, owner: String,
}, },
DecreasePledge {
decrease_by: Coin,
},
DecreasePledgeOnBehalf {
owner: String,
decrease_by: Coin,
},
UnbondMixnode {}, UnbondMixnode {},
UnbondMixnodeOnBehalf { UnbondMixnodeOnBehalf {
owner: String, owner: String,
@@ -297,6 +307,10 @@ impl ExecuteMsg {
} }
ExecuteMsg::PledgeMore {} => "pledging additional tokens".into(), ExecuteMsg::PledgeMore {} => "pledging additional tokens".into(),
ExecuteMsg::PledgeMoreOnBehalf { .. } => "pledging additional tokens on behalf".into(), ExecuteMsg::PledgeMoreOnBehalf { .. } => "pledging additional tokens on behalf".into(),
ExecuteMsg::DecreasePledge { .. } => "decreasing mixnode pledge".into(),
ExecuteMsg::DecreasePledgeOnBehalf { .. } => {
"decreasing mixnode pledge on behalf".into()
}
ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(), ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(),
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(), ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(), ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
@@ -506,6 +520,12 @@ pub enum QueryMsg {
limit: Option<u32>, limit: Option<u32>,
start_after: Option<u32>, start_after: Option<u32>,
}, },
GetPendingEpochEvent {
event_id: EpochEventId,
},
GetPendingIntervalEvent {
event_id: IntervalEventId,
},
GetNumberOfPendingEvents {}, GetNumberOfPendingEvents {},
// signing-related // signing-related
@@ -38,6 +38,10 @@ pub enum PendingEpochEventKind {
mix_id: MixId, mix_id: MixId,
amount: Coin, amount: Coin,
}, },
DecreasePledge {
mix_id: MixId,
decrease_by: Coin,
},
UnbondMixnode { UnbondMixnode {
mix_id: MixId, mix_id: MixId,
}, },
@@ -66,7 +70,7 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingIntervalEvent { pub struct PendingIntervalEvent {
pub id: EpochEventId, pub id: IntervalEventId,
pub event: PendingIntervalEventData, pub event: PendingIntervalEventData,
} }
@@ -15,6 +15,7 @@ pub const VESTING_GATEWAY_BONDING_EVENT_TYPE: &str = "vesting_gateway_bonding";
pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbonding"; pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbonding";
pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding"; pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
pub const VESTING_PLEDGE_MORE_EVENT_TYPE: &str = "vesting_pledge_more"; pub const VESTING_PLEDGE_MORE_EVENT_TYPE: &str = "vesting_pledge_more";
pub const VESTING_DECREASE_PLEDGE_EVENT_TYPE: &str = "vesting_pledge_decrease";
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding"; pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config"; pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
pub const VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE: &str = "vesting_update_gateway_config"; pub const VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE: &str = "vesting_update_gateway_config";
@@ -22,6 +23,7 @@ pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
"vesting_update_mixnode_cost_params"; "vesting_update_mixnode_cost_params";
pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond"; pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond";
pub const TRACK_MIXNODE_PLEDGE_DECREASE_EVENT_TYPE: &str = "track_mixnode_pledge_decrease";
pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond"; pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond";
pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation"; pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation";
pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward"; pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward";
@@ -118,6 +120,10 @@ pub fn new_vesting_pledge_more_event() -> Event {
Event::new(VESTING_PLEDGE_MORE_EVENT_TYPE) Event::new(VESTING_PLEDGE_MORE_EVENT_TYPE)
} }
pub fn new_vesting_decrease_pledge_event() -> Event {
Event::new(VESTING_DECREASE_PLEDGE_EVENT_TYPE)
}
pub fn new_vesting_update_mixnode_config_event() -> Event { pub fn new_vesting_update_mixnode_config_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE) Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
} }
@@ -146,6 +152,10 @@ pub fn new_track_mixnode_unbond_event() -> Event {
Event::new(TRACK_MIXNODE_UNBOND_EVENT_TYPE) Event::new(TRACK_MIXNODE_UNBOND_EVENT_TYPE)
} }
pub fn new_track_mixnode_pledge_decrease_event() -> Event {
Event::new(TRACK_MIXNODE_PLEDGE_DECREASE_EVENT_TYPE)
}
pub fn new_track_gateway_unbond_event() -> Event { pub fn new_track_gateway_unbond_event() -> Event {
Event::new(TRACK_GATEWAY_UNBOND_EVENT_TYPE) Event::new(TRACK_GATEWAY_UNBOND_EVENT_TYPE)
} }
@@ -123,11 +123,18 @@ pub enum ExecuteMsg {
PledgeMore { PledgeMore {
amount: Coin, amount: Coin,
}, },
DecreasePledge {
amount: Coin,
},
UnbondMixnode {}, UnbondMixnode {},
TrackUnbondMixnode { TrackUnbondMixnode {
owner: String, owner: String,
amount: Coin, amount: Coin,
}, },
TrackDecreasePledge {
owner: String,
amount: Coin,
},
BondGateway { BondGateway {
gateway: Gateway, gateway: Gateway,
owner_signature: MessageSignature, owner_signature: MessageSignature,
@@ -175,8 +182,10 @@ impl ExecuteMsg {
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation", ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode", ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
ExecuteMsg::PledgeMore { .. } => "VestingExecuteMsg::PledgeMore", ExecuteMsg::PledgeMore { .. } => "VestingExecuteMsg::PledgeMore",
ExecuteMsg::DecreasePledge { .. } => "VestingExecuteMsg::DecreasePledge",
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode", ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode", ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
ExecuteMsg::TrackDecreasePledge { .. } => "VestingExecuteMsg::TrackDecreasePledge",
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway", ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway", ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway", ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
@@ -3,15 +3,12 @@
use nym_sphinx_acknowledgements::surb_ack::SurbAckRecoveryError; use nym_sphinx_acknowledgements::surb_ack::SurbAckRecoveryError;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddressError; use nym_sphinx_addressing::nodes::NymNodeRoutingAddressError;
use nym_sphinx_types::{NymPacketError, SphinxError}; use nym_sphinx_types::Error as SphinxError;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum MixProcessingError { pub enum MixProcessingError {
#[error("failed to process received packet: {0}")] #[error("failed to process received packet: {0}")]
NymPacketProcessingError(#[from] NymPacketError),
#[error("failed to process received sphinx packet: {0}")]
SphinxProcessingError(#[from] SphinxError), SphinxProcessingError(#[from] SphinxError),
#[error("the forward hop address was malformed: {0}")] #[error("the forward hop address was malformed: {0}")]
@@ -7,11 +7,11 @@ use log::*;
use nym_sphinx_acknowledgements::surb_ack::SurbAck; use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress; use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx_forwarding::packet::MixPacket; use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_framing::packet::FramedNymPacket; use nym_sphinx_framing::packet::FramedSphinxPacket;
use nym_sphinx_params::{PacketSize, PacketType}; use nym_sphinx_params::{PacketMode, PacketSize};
use nym_sphinx_types::{ use nym_sphinx_types::{
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, NymPacket, Payload, Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, Payload, PrivateKey,
PrivateKey, ProcessedPacket, ProcessedPacket, SphinxPacket,
}; };
use std::convert::TryFrom; use std::convert::TryFrom;
use std::sync::Arc; use std::sync::Arc;
@@ -53,14 +53,14 @@ impl SphinxPacketProcessor {
feature = "cpucycles", feature = "cpucycles",
instrument(skip(self, packet), fields(cpucycles)) instrument(skip(self, packet), fields(cpucycles))
)] )]
fn perform_initial_packet_processing( fn perform_initial_sphinx_packet_processing(
&self, &self,
packet: NymPacket, packet: SphinxPacket,
) -> Result<ProcessedPacket, MixProcessingError> { ) -> Result<ProcessedPacket, MixProcessingError> {
measure!({ measure!({
packet.process(&self.sphinx_key).map_err(|err| { packet.process(&self.sphinx_key).map_err(|err| {
debug!("Failed to unwrap Sphinx packet: {err}"); debug!("Failed to unwrap Sphinx packet: {err}");
MixProcessingError::NymPacketProcessingError(err) MixProcessingError::SphinxProcessingError(err)
}) })
}) })
} }
@@ -72,12 +72,17 @@ impl SphinxPacketProcessor {
)] )]
fn perform_initial_unwrapping( fn perform_initial_unwrapping(
&self, &self,
received: FramedNymPacket, received: FramedSphinxPacket,
) -> Result<ProcessedPacket, MixProcessingError> { ) -> Result<ProcessedPacket, MixProcessingError> {
measure!({ measure!({
let packet = received.into_inner(); let packet_mode = received.packet_mode();
let sphinx_packet = received.into_inner();
self.perform_initial_packet_processing(packet) if packet_mode.is_old_vpn() {
return Err(MixProcessingError::ReceivedOldTypeVpnPacket);
}
self.perform_initial_sphinx_packet_processing(sphinx_packet)
}) })
} }
@@ -85,14 +90,14 @@ impl SphinxPacketProcessor {
/// and packs all the data in a way that can be easily sent to the next hop. /// and packs all the data in a way that can be easily sent to the next hop.
fn process_forward_hop( fn process_forward_hop(
&self, &self,
packet: NymPacket, packet: SphinxPacket,
forward_address: NodeAddressBytes, forward_address: NodeAddressBytes,
delay: SphinxDelay, delay: SphinxDelay,
packet_type: PacketType, packet_mode: PacketMode,
) -> Result<MixProcessingResult, MixProcessingError> { ) -> Result<MixProcessingResult, MixProcessingError> {
let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?; let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?;
let mix_packet = MixPacket::new(next_hop_address, packet, packet_type); let mix_packet = MixPacket::new(next_hop_address, packet, packet_mode);
Ok(MixProcessingResult::ForwardHop(mix_packet, Some(delay))) Ok(MixProcessingResult::ForwardHop(mix_packet, Some(delay)))
} }
@@ -119,25 +124,21 @@ impl SphinxPacketProcessor {
&self, &self,
data: Vec<u8>, data: Vec<u8>,
packet_size: PacketSize, packet_size: PacketSize,
packet_type: PacketType, packet_mode: PacketMode,
) -> Result<(Option<MixPacket>, Vec<u8>), MixProcessingError> { ) -> Result<(Option<MixPacket>, Vec<u8>), MixProcessingError> {
match packet_size { match packet_size {
PacketSize::AckPacket | PacketSize::OutfoxAckPacket => { PacketSize::AckPacket => {
trace!("received an ack packet!"); trace!("received an ack packet!");
Ok((None, data)) Ok((None, data))
} }
PacketSize::RegularPacket PacketSize::RegularPacket
| PacketSize::ExtendedPacket8 | PacketSize::ExtendedPacket8
| PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket16
| PacketSize::ExtendedPacket32 | PacketSize::ExtendedPacket32 => {
| PacketSize::OutfoxRegularPacket
| PacketSize::OutfoxExtendedPacket8
| PacketSize::OutfoxExtendedPacket16
| PacketSize::OutfoxExtendedPacket32 => {
trace!("received a normal packet!"); trace!("received a normal packet!");
let (ack_data, message) = self.split_hop_data_into_ack_and_message(data)?; let (ack_data, message) = self.split_hop_data_into_ack_and_message(data)?;
let (ack_first_hop, ack_packet) = SurbAck::try_recover_first_hop_packet(&ack_data)?; let (ack_first_hop, ack_packet) = SurbAck::try_recover_first_hop_packet(&ack_data)?;
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type); let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_mode);
Ok((Some(forward_ack), message)) Ok((Some(forward_ack), message))
} }
} }
@@ -151,12 +152,12 @@ impl SphinxPacketProcessor {
destination: DestinationAddressBytes, destination: DestinationAddressBytes,
payload: Payload, payload: Payload,
packet_size: PacketSize, packet_size: PacketSize,
packet_type: PacketType, packet_mode: PacketMode,
) -> Result<MixProcessingResult, MixProcessingError> { ) -> Result<MixProcessingResult, MixProcessingError> {
let packet_message = payload.recover_plaintext()?; let packet_message = payload.recover_plaintext()?;
let (forward_ack, message) = let (forward_ack, message) =
self.split_into_ack_and_message(packet_message, packet_size, packet_type)?; self.split_into_ack_and_message(packet_message, packet_size, packet_mode)?;
Ok(MixProcessingResult::FinalHop(ProcessedFinalHop { Ok(MixProcessingResult::FinalHop(ProcessedFinalHop {
destination, destination,
@@ -171,16 +172,16 @@ impl SphinxPacketProcessor {
&self, &self,
packet: ProcessedPacket, packet: ProcessedPacket,
packet_size: PacketSize, packet_size: PacketSize,
packet_type: PacketType, packet_mode: PacketMode,
) -> Result<MixProcessingResult, MixProcessingError> { ) -> Result<MixProcessingResult, MixProcessingError> {
match packet { match packet {
ProcessedPacket::ForwardHop(packet, address, delay) => { ProcessedPacket::ForwardHop(packet, address, delay) => {
self.process_forward_hop(NymPacket::Sphinx(*packet), address, delay, packet_type) self.process_forward_hop(*packet, address, delay, packet_mode)
} }
// right now there's no use for the surb_id included in the header - probably it should get removed from the // right now there's no use for the surb_id included in the header - probably it should get removed from the
// sphinx all together? // sphinx all together?
ProcessedPacket::FinalHop(destination, _, payload) => { ProcessedPacket::FinalHop(destination, _, payload) => {
self.process_final_hop(destination, payload, packet_size, packet_type) self.process_final_hop(destination, payload, packet_size, packet_mode)
} }
} }
} }
@@ -191,19 +192,19 @@ impl SphinxPacketProcessor {
)] )]
pub fn process_received( pub fn process_received(
&self, &self,
received: FramedNymPacket, received: FramedSphinxPacket,
) -> Result<MixProcessingResult, MixProcessingError> { ) -> Result<MixProcessingResult, MixProcessingError> {
// explicit packet size will help to correctly parse final hop // explicit packet size will help to correctly parse final hop
measure!({ measure!({
let packet_size = received.packet_size(); let packet_size = received.packet_size();
let packet_type = received.packet_type(); let packet_mode = received.packet_mode();
// unwrap the sphinx packet and if possible and appropriate, cache keys // unwrap the sphinx packet and if possible and appropriate, cache keys
let processed_packet = self.perform_initial_unwrapping(received)?; let processed_packet = self.perform_initial_unwrapping(received)?;
// for forward packets, extract next hop and set delay (but do NOT delay here) // for forward packets, extract next hop and set delay (but do NOT delay here)
// for final packets, extract SURBAck // for final packets, extract SURBAck
self.perform_final_processing(processed_packet, packet_size, packet_type) self.perform_final_processing(processed_packet, packet_size, packet_mode)
}) })
} }
} }
@@ -10,8 +10,11 @@ use nym_sphinx_addressing::nodes::{
use nym_sphinx_params::packet_sizes::PacketSize; use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::DEFAULT_NUM_MIX_HOPS; use nym_sphinx_params::DEFAULT_NUM_MIX_HOPS;
use nym_sphinx_types::builder::SphinxPacketBuilder; use nym_sphinx_types::builder::SphinxPacketBuilder;
use nym_sphinx_types::delays::{self, Delay}; use nym_sphinx_types::Error as SphinxError;
use nym_sphinx_types::{NymPacket, NymPacketError}; use nym_sphinx_types::{
delays::{self, Delay},
SphinxPacket,
};
use nym_topology::{NymTopology, NymTopologyError}; use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use std::convert::TryFrom; use std::convert::TryFrom;
@@ -19,7 +22,7 @@ use std::time;
use thiserror::Error; use thiserror::Error;
pub struct SurbAck { pub struct SurbAck {
surb_ack_packet: NymPacket, surb_ack_packet: SphinxPacket,
first_hop_address: NymNodeRoutingAddress, first_hop_address: NymNodeRoutingAddress,
expected_total_delay: Delay, expected_total_delay: Delay,
} }
@@ -32,8 +35,8 @@ pub enum SurbAckRecoveryError {
#[error("could not extract first hop address information - {0}")] #[error("could not extract first hop address information - {0}")]
InvalidAddress(#[from] NymNodeRoutingAddressError), InvalidAddress(#[from] NymNodeRoutingAddressError),
#[error("packet: {0}")] #[error("the contained sphinx packet was not correctly formed - {0}")]
NymPacket(#[from] NymPacketError), InvalidSphinxPacket(#[from] SphinxError),
} }
impl SurbAck { impl SurbAck {
@@ -55,12 +58,10 @@ impl SurbAck {
let surb_ack_payload = prepare_identifier(rng, ack_key, marshaled_fragment_id); let surb_ack_payload = prepare_identifier(rng, ack_key, marshaled_fragment_id);
let surb_ack_packet = NymPacket::Sphinx( let surb_ack_packet = SphinxPacketBuilder::new()
SphinxPacketBuilder::new() .with_payload_size(PacketSize::AckPacket.payload_size())
.with_payload_size(PacketSize::AckPacket.payload_size()) .build_packet(surb_ack_payload, &route, &destination, &delays)
.build_packet(surb_ack_payload, &route, &destination, &delays) .unwrap();
.unwrap(),
);
// in our case, the last hop is a gateway that does NOT do any delays // in our case, the last hop is a gateway that does NOT do any delays
let expected_total_delay = delays.iter().take(delays.len() - 1).sum(); let expected_total_delay = delays.iter().take(delays.len() - 1).sum();
@@ -84,21 +85,21 @@ impl SurbAck {
self.expected_total_delay self.expected_total_delay
} }
pub fn prepare_for_sending(self) -> Result<(Delay, Vec<u8>), SurbAckRecoveryError> { pub fn prepare_for_sending(self) -> (Delay, Vec<u8>) {
// SURB_FIRST_HOP || SURB_ACK // SURB_FIRST_HOP || SURB_ACK
let surb_bytes: Vec<_> = self let surb_bytes: Vec<_> = self
.first_hop_address .first_hop_address
.as_zero_padded_bytes(MAX_NODE_ADDRESS_UNPADDED_LEN) .as_zero_padded_bytes(MAX_NODE_ADDRESS_UNPADDED_LEN)
.into_iter() .into_iter()
.chain(self.surb_ack_packet.to_bytes()?.into_iter()) .chain(self.surb_ack_packet.to_bytes().into_iter())
.collect(); .collect();
Ok((self.expected_total_delay, surb_bytes)) (self.expected_total_delay, surb_bytes)
} }
// partial reciprocal of `prepare_for_sending` performed by the gateway // partial reciprocal of `prepare_for_sending` performed by the gateway
pub fn try_recover_first_hop_packet( pub fn try_recover_first_hop_packet(
b: &[u8], b: &[u8],
) -> Result<(NymNodeRoutingAddress, NymPacket), SurbAckRecoveryError> { ) -> Result<(NymNodeRoutingAddress, SphinxPacket), SurbAckRecoveryError> {
if b.len() != Self::len() { if b.len() != Self::len() {
Err(SurbAckRecoveryError::InvalidPacketSize { Err(SurbAckRecoveryError::InvalidPacketSize {
received: b.len(), received: b.len(),
@@ -110,7 +111,7 @@ impl SurbAck {
// TODO: this will be variable once/if we decide to introduce optimization described // TODO: this will be variable once/if we decide to introduce optimization described
// in common/nymsphinx/chunking/src/lib.rs:available_plaintext_size() // in common/nymsphinx/chunking/src/lib.rs:available_plaintext_size()
let address_offset = MAX_NODE_ADDRESS_UNPADDED_LEN; let address_offset = MAX_NODE_ADDRESS_UNPADDED_LEN;
let packet = NymPacket::sphinx_from_bytes(&b[address_offset..])?; let packet = SphinxPacket::from_bytes(&b[address_offset..])?;
Ok((address, packet)) Ok((address, packet))
} }
@@ -7,7 +7,7 @@ use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN}; use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
use nym_sphinx_params::packet_sizes::PacketSize; use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS}; use nym_sphinx_params::{ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::{delays, NymPacket, SURBMaterial, SphinxError, SURB}; use nym_sphinx_types::{delays, Error as SphinxError, SURBMaterial, SphinxPacket, SURB};
use nym_topology::{NymTopology, NymTopologyError}; use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use serde::de::{Error as SerdeError, Visitor}; use serde::de::{Error as SerdeError, Visitor};
@@ -173,7 +173,7 @@ impl ReplySurb {
self, self,
message: M, message: M,
packet_size: PacketSize, packet_size: PacketSize,
) -> Result<(NymPacket, NymNodeRoutingAddress), ReplySurbError> { ) -> Result<(SphinxPacket, NymNodeRoutingAddress), ReplySurbError> {
let message_bytes = message.as_ref(); let message_bytes = message.as_ref();
if message_bytes.len() != packet_size.plaintext_size() { if message_bytes.len() != packet_size.plaintext_size() {
return Err(ReplySurbError::UnpaddedMessageError); return Err(ReplySurbError::UnpaddedMessageError);
@@ -187,6 +187,6 @@ impl ReplySurb {
let first_hop_address = NymNodeRoutingAddress::try_from(first_hop).unwrap(); let first_hop_address = NymNodeRoutingAddress::try_from(first_hop).unwrap();
Ok((NymPacket::Sphinx(packet), first_hop_address)) Ok((packet, first_hop_address))
} }
} }
+11 -13
View File
@@ -3,7 +3,7 @@
use nym_crypto::shared_key::new_ephemeral_shared_key; use nym_crypto::shared_key::new_ephemeral_shared_key;
use nym_crypto::symmetric::stream_cipher; use nym_crypto::symmetric::stream_cipher;
use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError}; use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_acknowledgements::AckKey; use nym_sphinx_acknowledgements::AckKey;
use nym_sphinx_addressing::clients::Recipient; use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress; use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
@@ -11,10 +11,10 @@ use nym_sphinx_chunking::fragment::COVER_FRAG_ID;
use nym_sphinx_forwarding::packet::MixPacket; use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::packet_sizes::PacketSize; use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{ use nym_sphinx_params::{
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketType, DEFAULT_NUM_MIX_HOPS, PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketMode, DEFAULT_NUM_MIX_HOPS,
}; };
use nym_sphinx_types::builder::SphinxPacketBuilder; use nym_sphinx_types::builder::SphinxPacketBuilder;
use nym_sphinx_types::{delays, NymPacket}; use nym_sphinx_types::{delays, Error as SphinxError};
use nym_topology::{NymTopology, NymTopologyError}; use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use std::convert::TryFrom; use std::convert::TryFrom;
@@ -28,8 +28,8 @@ pub enum CoverMessageError {
#[error("Could not construct cover message due to invalid topology - {0}")] #[error("Could not construct cover message due to invalid topology - {0}")]
InvalidTopologyError(#[from] NymTopologyError), InvalidTopologyError(#[from] NymTopologyError),
#[error("SurbAck: {0}")] #[error("Could not construct a valid sphinx packet - {0}")]
SurbAck(#[from] SurbAckRecoveryError), SphinxError(#[from] SphinxError),
} }
pub fn generate_loop_cover_surb_ack<R>( pub fn generate_loop_cover_surb_ack<R>(
@@ -67,7 +67,7 @@ where
// we don't care about total ack delay - we will not be retransmitting it anyway // we don't care about total ack delay - we will not be retransmitting it anyway
let (_, ack_bytes) = let (_, ack_bytes) =
generate_loop_cover_surb_ack(rng, topology, ack_key, full_address, average_ack_delay)? generate_loop_cover_surb_ack(rng, topology, ack_key, full_address, average_ack_delay)?
.prepare_for_sending()?; .prepare_for_sending();
// cover message can't be distinguishable from a normal traffic so we have to go through // cover message can't be distinguishable from a normal traffic so we have to go through
// all the effort of key generation, encryption, etc. Note here we are generating shared key // all the effort of key generation, encryption, etc. Note here we are generating shared key
@@ -111,17 +111,15 @@ where
let destination = full_address.as_sphinx_destination(); let destination = full_address.as_sphinx_destination();
// once merged, that's an easy rng injection point for sphinx packets : ) // once merged, that's an easy rng injection point for sphinx packets : )
let packet = NymPacket::Sphinx( let packet = SphinxPacketBuilder::new()
SphinxPacketBuilder::new() .with_payload_size(packet_size.payload_size())
.with_payload_size(packet_size.payload_size()) .build_packet(packet_payload, &route, &destination, &delays)
.build_packet(packet_payload, &route, &destination, &delays) .unwrap();
.unwrap(),
);
let first_hop_address = let first_hop_address =
NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap(); NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap();
Ok(MixPacket::new(first_hop_address, packet, PacketType::Mix)) Ok(MixPacket::new(first_hop_address, packet, PacketMode::Mix))
} }
/// Helper function used to determine if given message represents a loop cover message. /// Helper function used to determine if given message represents a loop cover message.
-1
View File
@@ -12,4 +12,3 @@ nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-params = { path = "../params" } nym-sphinx-params = { path = "../params" }
nym-sphinx-types = { path = "../types" } nym-sphinx-types = { path = "../types" }
nym-outfox = { path = "../../../nym-outfox" } nym-outfox = { path = "../../../nym-outfox" }
thiserror = "1"
+58 -41
View File
@@ -2,28 +2,42 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
use nym_sphinx_params::{PacketSize, PacketType}; use nym_sphinx_params::{PacketMode, PacketSize};
use nym_sphinx_types::{NymPacket, NymPacketError}; use nym_sphinx_types::SphinxPacket;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug)]
pub enum MixPacketFormattingError { pub enum MixPacketFormattingError {
#[error("too few bytes provided to recover from bytes")]
TooFewBytesProvided, TooFewBytesProvided,
#[error("provided packet mode is invalid")] InvalidPacketMode,
InvalidPacketType,
#[error("received request had invalid size - received {0}")]
InvalidPacketSize(usize), InvalidPacketSize(usize),
#[error("address field was incorrectly encoded")]
InvalidAddress, InvalidAddress,
#[error("received sphinx packet was malformed")]
MalformedSphinxPacket, MalformedSphinxPacket,
#[error("Packet: {0}")]
Packet(#[from] NymPacketError),
} }
impl Display for MixPacketFormattingError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use MixPacketFormattingError::*;
match self {
TooFewBytesProvided => write!(f, "Too few bytes provided to recover from bytes"),
InvalidAddress => write!(f, "address field was incorrectly encoded"),
InvalidPacketSize(actual) =>
write!(
f,
"received request had invalid size. (actual: {}, but expected one of: {} (ACK), {} (REGULAR), {}, {}, {} (EXTENDED))",
actual, PacketSize::AckPacket.size(), PacketSize::RegularPacket.size(),
PacketSize::ExtendedPacket8.size(), PacketSize::ExtendedPacket16.size(),
PacketSize::ExtendedPacket32.size()
),
MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"),
InvalidPacketMode => write!(f, "provided packet mode is invalid")
}
}
}
impl std::error::Error for MixPacketFormattingError {}
impl From<NymNodeRoutingAddressError> for MixPacketFormattingError { impl From<NymNodeRoutingAddressError> for MixPacketFormattingError {
fn from(_: NymNodeRoutingAddressError) -> Self { fn from(_: NymNodeRoutingAddressError) -> Self {
MixPacketFormattingError::InvalidAddress MixPacketFormattingError::InvalidAddress
@@ -32,16 +46,19 @@ impl From<NymNodeRoutingAddressError> for MixPacketFormattingError {
pub struct MixPacket { pub struct MixPacket {
next_hop: NymNodeRoutingAddress, next_hop: NymNodeRoutingAddress,
packet: NymPacket, sphinx_packet: SphinxPacket,
packet_type: PacketType, packet_mode: PacketMode,
} }
impl Debug for MixPacket { impl Debug for MixPacket {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"MixPacket to {:?} with packet_type {:?}. Packet {:?}", "MixPacket to {:?} with packet_mode {:?}. Sphinx header: {:?}, payload length: {}",
self.next_hop, self.packet_type, self.packet self.next_hop,
self.packet_mode,
self.sphinx_packet.header,
self.sphinx_packet.payload.len()
) )
} }
} }
@@ -49,13 +66,13 @@ impl Debug for MixPacket {
impl MixPacket { impl MixPacket {
pub fn new( pub fn new(
next_hop: NymNodeRoutingAddress, next_hop: NymNodeRoutingAddress,
packet: NymPacket, sphinx_packet: SphinxPacket,
packet_type: PacketType, packet_mode: PacketMode,
) -> Self { ) -> Self {
MixPacket { MixPacket {
next_hop, next_hop,
packet, sphinx_packet,
packet_type, packet_mode,
} }
} }
@@ -63,52 +80,52 @@ impl MixPacket {
self.next_hop self.next_hop
} }
pub fn packet(&self) -> &NymPacket { pub fn sphinx_packet(&self) -> &SphinxPacket {
&self.packet &self.sphinx_packet
} }
pub fn into_packet(self) -> NymPacket { pub fn into_sphinx_packet(self) -> SphinxPacket {
self.packet self.sphinx_packet
} }
pub fn packet_type(&self) -> PacketType { pub fn packet_mode(&self) -> PacketMode {
self.packet_type self.packet_mode
} }
// the message is formatted as follows: // the message is formatted as follows:
// packet_type || FIRST_HOP || packet // PACKET_MODE || FIRST_HOP || SPHINX_PACKET
pub fn try_from_bytes(b: &[u8]) -> Result<Self, MixPacketFormattingError> { pub fn try_from_bytes(b: &[u8]) -> Result<Self, MixPacketFormattingError> {
let packet_type = match PacketType::try_from(b[0]) { let packet_mode = match PacketMode::try_from(b[0]) {
Ok(mode) => mode, Ok(mode) => mode,
Err(_) => return Err(MixPacketFormattingError::InvalidPacketType), Err(_) => return Err(MixPacketFormattingError::InvalidPacketMode),
}; };
let next_hop = NymNodeRoutingAddress::try_from_bytes(&b[1..])?; let next_hop = NymNodeRoutingAddress::try_from_bytes(&b[1..])?;
let addr_offset = next_hop.bytes_min_len(); let addr_offset = next_hop.bytes_min_len();
let packet_data = &b[addr_offset + 1..]; let sphinx_packet_data = &b[addr_offset + 1..];
let packet_size = packet_data.len(); let packet_size = sphinx_packet_data.len();
if PacketSize::get_type(packet_size).is_err() { if PacketSize::get_type(packet_size).is_err() {
Err(MixPacketFormattingError::InvalidPacketSize(packet_size)) Err(MixPacketFormattingError::InvalidPacketSize(packet_size))
} else { } else {
let packet = match packet_type { let sphinx_packet = match SphinxPacket::from_bytes(sphinx_packet_data) {
PacketType::Outfox => NymPacket::outfox_from_bytes(packet_data)?, Ok(packet) => packet,
_ => NymPacket::sphinx_from_bytes(packet_data)?, Err(_) => return Err(MixPacketFormattingError::MalformedSphinxPacket),
}; };
Ok(MixPacket { Ok(MixPacket {
next_hop, next_hop,
packet, sphinx_packet,
packet_type, packet_mode,
}) })
} }
} }
pub fn into_bytes(self) -> Result<Vec<u8>, MixPacketFormattingError> { pub fn into_bytes(self) -> Vec<u8> {
Ok(std::iter::once(self.packet_type as u8) std::iter::once(self.packet_mode as u8)
.chain(self.next_hop.as_bytes().into_iter()) .chain(self.next_hop.as_bytes().into_iter())
.chain(self.packet.to_bytes()?.into_iter()) .chain(self.sphinx_packet.to_bytes().into_iter())
.collect()) .collect()
} }
} }
+93 -127
View File
@@ -1,55 +1,65 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::packet::{FramedNymPacket, Header}; use crate::packet::{FramedSphinxPacket, Header};
use bytes::{Buf, BufMut, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use nym_sphinx_params::packet_modes::InvalidPacketMode;
use nym_sphinx_params::packet_sizes::{InvalidPacketSize, PacketSize}; use nym_sphinx_params::packet_sizes::{InvalidPacketSize, PacketSize};
use nym_sphinx_params::packet_types::InvalidPacketType; use nym_sphinx_types::Error as SphinxError;
use nym_sphinx_params::PacketType; use nym_sphinx_types::SphinxPacket;
use nym_sphinx_types::{NymPacket, NymPacketError};
use std::io; use std::io;
use thiserror::Error; use thiserror::Error;
use tokio_util::codec::{Decoder, Encoder}; use tokio_util::codec::{Decoder, Encoder};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum NymCodecError { pub enum SphinxCodecError {
#[error("the packet size information was malformed - {0}")] #[error("the packet size information was malformed - {0}")]
InvalidPacketSize(#[from] InvalidPacketSize), InvalidPacketSize(#[from] InvalidPacketSize),
#[error("the packet mode information was malformed - {0}")] #[error("the packet mode information was malformed - {0}")]
InvalidPacketType(#[from] InvalidPacketType), InvalidPacketMode(#[from] InvalidPacketMode),
#[error("the actual sphinx packet was malformed - {0}")]
MalformedSphinxPacket(#[from] SphinxError),
#[error("encountered an IO error - {0}")] #[error("encountered an IO error - {0}")]
IoError(#[from] io::Error), IoError(#[from] io::Error),
}
#[error("encountered a packet error - {0}")] impl From<SphinxCodecError> for io::Error {
NymPacket(#[from] NymPacketError), fn from(err: SphinxCodecError) -> Self {
match err {
#[error("could not convert to bytes")] SphinxCodecError::InvalidPacketSize(source) => {
ToBytes, io::Error::new(io::ErrorKind::InvalidInput, source)
}
#[error("could not convert to bytes")] SphinxCodecError::InvalidPacketMode(source) => {
FromBytes, io::Error::new(io::ErrorKind::InvalidInput, source)
}
SphinxCodecError::MalformedSphinxPacket(source) => {
io::Error::new(io::ErrorKind::InvalidData, source)
}
SphinxCodecError::IoError(err) => err,
}
}
} }
// TODO: in the future it could be extended to have state containing symmetric encryption key // TODO: in the future it could be extended to have state containing symmetric encryption key
// so that all data could be encrypted easily (alternatively we could just slap TLS) // so that all data could be encrypted easily (alternatively we could just slap TLS)
pub struct NymCodec; pub struct SphinxCodec;
impl Encoder<FramedNymPacket> for NymCodec { impl Encoder<FramedSphinxPacket> for SphinxCodec {
type Error = NymCodecError; type Error = SphinxCodecError;
fn encode(&mut self, item: FramedNymPacket, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: FramedSphinxPacket, dst: &mut BytesMut) -> Result<(), Self::Error> {
item.header.encode(dst); item.header.encode(dst);
let packet_bytes = item.packet.to_bytes()?; dst.put(item.packet.to_bytes().as_ref());
dst.put(packet_bytes.as_slice());
Ok(()) Ok(())
} }
} }
impl Decoder for NymCodec { impl Decoder for SphinxCodec {
type Item = FramedNymPacket; type Item = FramedSphinxPacket;
type Error = NymCodecError; type Error = SphinxCodecError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if src.is_empty() { if src.is_empty() {
@@ -66,32 +76,23 @@ impl Decoder for NymCodec {
None => return Ok(None), // we have some data but not enough to get header back None => return Ok(None), // we have some data but not enough to get header back
}; };
let packet_size = header.packet_size.size(); let sphinx_packet_size = header.packet_size.size();
let frame_len = header.size() + packet_size; let frame_len = header.size() + sphinx_packet_size;
if src.len() < frame_len { if src.len() < frame_len {
// we don't have enough bytes to read the rest of frame // we don't have enough bytes to read the rest of frame
src.reserve(packet_size); src.reserve(sphinx_packet_size);
return Ok(None); return Ok(None);
} }
// advance buffer past the header - at this point we have enough bytes // advance buffer past the header - at this point we have enough bytes
src.advance(header.size()); src.advance(header.size());
let packet_bytes = src.split_to(packet_size); let sphinx_packet_bytes = src.split_to(sphinx_packet_size);
let packet = if let Some(slice) = packet_bytes.get(..) {
// here it could be debatable whether stream is corrupt or not,
// but let's go with the safer approach and assume it is.
match header.packet_type {
PacketType::Outfox => NymPacket::outfox_from_bytes(slice)?,
PacketType::Mix => NymPacket::sphinx_from_bytes(slice)?,
PacketType::Vpn => NymPacket::sphinx_from_bytes(slice)?,
}
} else {
return Ok(None);
};
// let packet = SphinxPacket::from_bytes(&sphinx_packet_bytes)?; // here it could be debatable whether stream is corrupt or not,
let nymsphinx_packet = FramedNymPacket { header, packet }; // but let's go with the safer approach and assume it is.
let packet = SphinxPacket::from_bytes(&sphinx_packet_bytes)?;
let nymsphinx_packet = FramedSphinxPacket { header, packet };
// As per docs: // As per docs:
// Before returning from the function, implementations should ensure that the buffer // Before returning from the function, implementations should ensure that the buffer
@@ -119,6 +120,7 @@ impl Decoder for NymCodec {
}; };
} }
src.reserve(allocate_for_next_packet); src.reserve(allocate_for_next_packet);
Ok(Some(nymsphinx_packet)) Ok(Some(nymsphinx_packet))
} }
} }
@@ -126,36 +128,13 @@ impl Decoder for NymCodec {
#[cfg(test)] #[cfg(test)]
mod packet_encoding { mod packet_encoding {
use super::*; use super::*;
use nym_sphinx_types::builder::SphinxPacketBuilder;
use nym_sphinx_types::{ use nym_sphinx_types::{
crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes, crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes,
DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH,
}; };
fn make_valid_outfox_packet(size: PacketSize) -> NymPacket { fn make_valid_sphinx_packet(size: PacketSize) -> SphinxPacket {
let (_, node1_pk) = crypto::keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
node1_pk,
);
let (_, node2_pk) = crypto::keygen();
let node2 = Node::new(
NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
node2_pk,
);
let (_, node3_pk) = crypto::keygen();
let node3 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pk,
);
let route = &[node1, node2, node3];
let payload = vec![1; 48];
NymPacket::outfox_build(payload, route, Some(size.plaintext_size())).unwrap()
}
fn make_valid_sphinx_packet(size: PacketSize) -> NymPacket {
let (_, node1_pk) = crypto::keygen(); let (_, node1_pk) = crypto::keygen();
let node1 = Node::new( let node1 = Node::new(
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]), NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
@@ -182,7 +161,9 @@ mod packet_encoding {
SphinxDelay::new_from_nanos(42), SphinxDelay::new_from_nanos(42),
SphinxDelay::new_from_nanos(42), SphinxDelay::new_from_nanos(42),
]; ];
NymPacket::sphinx_build(size.payload_size(), b"foomp", &route, &destination, &delays) SphinxPacketBuilder::new()
.with_payload_size(size.payload_size())
.build_packet(b"foomp", &route, &destination, &delays)
.unwrap() .unwrap()
} }
@@ -190,50 +171,32 @@ mod packet_encoding {
fn whole_packet_can_be_decoded_from_a_valid_encoded_instance() { fn whole_packet_can_be_decoded_from_a_valid_encoded_instance() {
let header = Default::default(); let header = Default::default();
let sphinx_packet = make_valid_sphinx_packet(Default::default()); let sphinx_packet = make_valid_sphinx_packet(Default::default());
let sphinx_bytes = sphinx_packet.to_bytes().unwrap(); let sphinx_bytes = sphinx_packet.to_bytes();
let packet = FramedNymPacket { let packet = FramedSphinxPacket {
header, header,
packet: sphinx_packet, packet: sphinx_packet,
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
NymCodec.encode(packet, &mut bytes).unwrap(); SphinxCodec.encode(packet, &mut bytes).unwrap();
let decoded = NymCodec.decode(&mut bytes).unwrap().unwrap(); let decoded = SphinxCodec.decode(&mut bytes).unwrap().unwrap();
assert_eq!(decoded.header, header); assert_eq!(decoded.header, header);
assert_eq!(decoded.packet.to_bytes().unwrap(), sphinx_bytes) assert_eq!(decoded.packet.to_bytes(), sphinx_bytes)
}
#[test]
fn whole_outfox_can_be_decoded_from_a_valid_encoded_instance() {
let header = Header::outfox();
let packet = make_valid_outfox_packet(PacketSize::OutfoxRegularPacket);
let packet_bytes = packet.to_bytes().unwrap();
NymPacket::outfox_from_bytes(packet_bytes.as_slice()).unwrap();
let packet = FramedNymPacket { header, packet };
let mut bytes = BytesMut::new();
NymCodec.encode(packet, &mut bytes).unwrap();
let decoded = NymCodec.decode(&mut bytes).unwrap().unwrap();
assert_eq!(decoded.header, header);
assert_eq!(decoded.packet.to_bytes().unwrap(), packet_bytes)
} }
#[cfg(test)] #[cfg(test)]
mod decode_will_allocate_enough_bytes_for_next_call { mod decode_will_allocate_enough_bytes_for_next_call {
use super::*; use super::*;
use nym_sphinx_params::packet_version::PacketVersion; use nym_sphinx_params::packet_version::PacketVersion;
use nym_sphinx_params::PacketType; use nym_sphinx_params::PacketMode;
#[test] #[test]
fn for_empty_bytes() { fn for_empty_bytes() {
// empty bytes should allocate for header + ack packet // empty bytes should allocate for header + ack packet
let mut empty_bytes = BytesMut::new(); let mut empty_bytes = BytesMut::new();
assert!(NymCodec.decode(&mut empty_bytes).unwrap().is_none()); assert!(SphinxCodec.decode(&mut empty_bytes).unwrap().is_none());
assert_eq!( assert_eq!(
empty_bytes.capacity(), empty_bytes.capacity(),
Header::LEGACY_SIZE + PacketSize::AckPacket.size() Header::LEGACY_SIZE + PacketSize::AckPacket.size()
@@ -254,11 +217,11 @@ mod packet_encoding {
let header = Header { let header = Header {
packet_version: PacketVersion::Legacy, packet_version: PacketVersion::Legacy,
packet_size, packet_size,
..Default::default() packet_mode: Default::default(),
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
header.encode(&mut bytes); header.encode(&mut bytes);
assert!(NymCodec.decode(&mut bytes).unwrap().is_none()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
assert_eq!(bytes.capacity(), Header::LEGACY_SIZE + packet_size.size()) assert_eq!(bytes.capacity(), Header::LEGACY_SIZE + packet_size.size())
} }
@@ -278,11 +241,11 @@ mod packet_encoding {
let header = Header { let header = Header {
packet_version: PacketVersion::Versioned(123), packet_version: PacketVersion::Versioned(123),
packet_size, packet_size,
..Default::default() packet_mode: Default::default(),
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
header.encode(&mut bytes); header.encode(&mut bytes);
assert!(NymCodec.decode(&mut bytes).unwrap().is_none()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
assert_eq!( assert_eq!(
bytes.capacity(), bytes.capacity(),
@@ -294,17 +257,18 @@ mod packet_encoding {
#[test] #[test]
fn for_full_frame_with_legacy_header() { fn for_full_frame_with_legacy_header() {
// if full frame is used exactly, there should be enough space for header + ack packet // if full frame is used exactly, there should be enough space for header + ack packet
let packet = FramedNymPacket { let packet = FramedSphinxPacket {
header: Header { header: Header {
packet_version: PacketVersion::Legacy, packet_version: PacketVersion::Legacy,
..Default::default() packet_size: Default::default(),
packet_mode: Default::default(),
}, },
packet: make_valid_sphinx_packet(Default::default()), packet: make_valid_sphinx_packet(Default::default()),
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
NymCodec.encode(packet, &mut bytes).unwrap(); SphinxCodec.encode(packet, &mut bytes).unwrap();
assert!(NymCodec.decode(&mut bytes).unwrap().is_some()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert_eq!( assert_eq!(
bytes.capacity(), bytes.capacity(),
Header::LEGACY_SIZE + PacketSize::AckPacket.size() Header::LEGACY_SIZE + PacketSize::AckPacket.size()
@@ -314,14 +278,14 @@ mod packet_encoding {
#[test] #[test]
fn for_full_frame_with_versioned_header() { fn for_full_frame_with_versioned_header() {
// if full frame is used exactly, there should be enough space for header + ack packet // if full frame is used exactly, there should be enough space for header + ack packet
let packet = FramedNymPacket { let packet = FramedSphinxPacket {
header: Header::default(), header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()), packet: make_valid_sphinx_packet(Default::default()),
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
NymCodec.encode(packet, &mut bytes).unwrap(); SphinxCodec.encode(packet, &mut bytes).unwrap();
assert!(NymCodec.decode(&mut bytes).unwrap().is_some()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert_eq!( assert_eq!(
bytes.capacity(), bytes.capacity(),
Header::VERSIONED_SIZE + PacketSize::AckPacket.size() Header::VERSIONED_SIZE + PacketSize::AckPacket.size()
@@ -340,19 +304,20 @@ mod packet_encoding {
]; ];
for packet_size in packet_sizes { for packet_size in packet_sizes {
let first_packet = FramedNymPacket { let first_packet = FramedSphinxPacket {
header: Header { header: Header {
packet_version: PacketVersion::Legacy, packet_version: PacketVersion::Legacy,
..Default::default() packet_size: Default::default(),
packet_mode: Default::default(),
}, },
packet: make_valid_sphinx_packet(Default::default()), packet: make_valid_sphinx_packet(Default::default()),
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
NymCodec.encode(first_packet, &mut bytes).unwrap(); SphinxCodec.encode(first_packet, &mut bytes).unwrap();
bytes.put_u8(packet_size as u8); bytes.put_u8(packet_size as u8);
bytes.put_u8(PacketType::default() as u8); bytes.put_u8(PacketMode::default() as u8);
assert!(NymCodec.decode(&mut bytes).unwrap().is_some()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert!(bytes.capacity() >= Header::LEGACY_SIZE + packet_size.size()) assert!(bytes.capacity() >= Header::LEGACY_SIZE + packet_size.size())
} }
@@ -370,53 +335,53 @@ mod packet_encoding {
]; ];
for packet_size in packet_sizes { for packet_size in packet_sizes {
let first_packet = FramedNymPacket { let first_packet = FramedSphinxPacket {
header: Header::default(), header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()), packet: make_valid_sphinx_packet(Default::default()),
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
NymCodec.encode(first_packet, &mut bytes).unwrap(); SphinxCodec.encode(first_packet, &mut bytes).unwrap();
bytes.put_u8(PacketVersion::new_versioned(123).as_u8().unwrap()); bytes.put_u8(PacketVersion::new_versioned(123).as_u8().unwrap());
bytes.put_u8(packet_size as u8); bytes.put_u8(packet_size as u8);
bytes.put_u8(PacketType::default() as u8); bytes.put_u8(PacketMode::default() as u8);
assert!(NymCodec.decode(&mut bytes).unwrap().is_some()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
// assert!(bytes.capacity() >= Header::VERSIONED_SIZE + packet_size.size()) assert!(bytes.capacity() >= Header::VERSIONED_SIZE + packet_size.size())
} }
} }
} }
#[test] #[test]
fn can_decode_two_packets_immediately() { fn can_decode_two_packets_immediately() {
let packet1 = FramedNymPacket { let packet1 = FramedSphinxPacket {
header: Header::default(), header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()), packet: make_valid_sphinx_packet(Default::default()),
}; };
let packet2 = FramedNymPacket { let packet2 = FramedSphinxPacket {
header: Header::default(), header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()), packet: make_valid_sphinx_packet(Default::default()),
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
NymCodec.encode(packet1, &mut bytes).unwrap(); SphinxCodec.encode(packet1, &mut bytes).unwrap();
NymCodec.encode(packet2, &mut bytes).unwrap(); SphinxCodec.encode(packet2, &mut bytes).unwrap();
assert!(NymCodec.decode(&mut bytes).unwrap().is_some()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert!(NymCodec.decode(&mut bytes).unwrap().is_some()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert!(NymCodec.decode(&mut bytes).unwrap().is_none()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
} }
#[test] #[test]
fn can_decode_two_packets_in_separate_calls() { fn can_decode_two_packets_in_separate_calls() {
let packet1 = FramedNymPacket { let packet1 = FramedSphinxPacket {
header: Header::default(), header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()), packet: make_valid_sphinx_packet(Default::default()),
}; };
let packet2 = FramedNymPacket { let packet2 = FramedSphinxPacket {
header: Header::default(), header: Header::default(),
packet: make_valid_sphinx_packet(Default::default()), packet: make_valid_sphinx_packet(Default::default()),
}; };
@@ -424,17 +389,18 @@ mod packet_encoding {
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
let mut bytes_tmp = BytesMut::new(); let mut bytes_tmp = BytesMut::new();
NymCodec.encode(packet1, &mut bytes).unwrap(); SphinxCodec.encode(packet1, &mut bytes).unwrap();
NymCodec.encode(packet2, &mut bytes_tmp).unwrap(); SphinxCodec.encode(packet2, &mut bytes_tmp).unwrap();
let tmp = bytes_tmp.split_off(100); let tmp = bytes_tmp.split_off(100);
bytes.put(bytes_tmp); bytes.put(bytes_tmp);
assert!(NymCodec.decode(&mut bytes).unwrap().is_some()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert!(NymCodec.decode(&mut bytes).unwrap().is_none()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
bytes.put(tmp); bytes.put(tmp);
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
assert!(NymCodec.decode(&mut bytes).unwrap().is_none()); assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
} }
} }
+25 -38
View File
@@ -1,52 +1,47 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::codec::NymCodecError; use crate::codec::SphinxCodecError;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use nym_sphinx_params::packet_sizes::PacketSize; use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::packet_version::PacketVersion; use nym_sphinx_params::packet_version::PacketVersion;
use nym_sphinx_params::PacketType; use nym_sphinx_params::PacketMode;
use nym_sphinx_types::NymPacket; use nym_sphinx_types::SphinxPacket;
use std::convert::TryFrom; use std::convert::TryFrom;
#[derive(Debug)] pub struct FramedSphinxPacket {
pub struct FramedNymPacket {
/// Contains any metadata helping receiver to handle the underlying packet. /// Contains any metadata helping receiver to handle the underlying packet.
pub(crate) header: Header, pub(crate) header: Header,
/// The actual SphinxPacket being sent. /// The actual SphinxPacket being sent.
pub(crate) packet: NymPacket, pub(crate) packet: SphinxPacket,
} }
impl FramedNymPacket { impl FramedSphinxPacket {
pub fn new(packet: NymPacket, packet_type: PacketType, use_legacy_version: bool) -> Self { pub fn new(packet: SphinxPacket, packet_mode: PacketMode, use_legacy_version: bool) -> Self {
// If this fails somebody is using the library in a super incorrect way, because they // If this fails somebody is using the library in a super incorrect way, because they
// already managed to somehow create a sphinx packet // already managed to somehow create a sphinx packet
let packet_size = PacketSize::get_type(packet.len()).unwrap(); let packet_size = PacketSize::get_type(packet.len()).unwrap();
FramedNymPacket { FramedSphinxPacket {
header: Header { header: Header {
packet_version: PacketVersion::new(use_legacy_version), packet_version: PacketVersion::new(use_legacy_version),
packet_size, packet_size,
packet_type, packet_mode,
}, },
packet, packet,
} }
} }
pub fn header(&self) -> Header {
self.header
}
pub fn packet_size(&self) -> PacketSize { pub fn packet_size(&self) -> PacketSize {
self.header.packet_size self.header.packet_size
} }
pub fn packet_type(&self) -> PacketType { pub fn packet_mode(&self) -> PacketMode {
self.header.packet_type self.header.packet_mode
} }
pub fn into_inner(self) -> NymPacket { pub fn into_inner(self) -> SphinxPacket {
self.packet self.packet
} }
} }
@@ -69,23 +64,15 @@ pub struct Header {
/// ///
/// TODO: ask @AP whether this can be sent like this - could it introduce some anonymity issues? /// TODO: ask @AP whether this can be sent like this - could it introduce some anonymity issues?
/// (note: this will be behind some encryption, either something implemented by us or some SSL action) /// (note: this will be behind some encryption, either something implemented by us or some SSL action)
// Note: currently packet_type is deprecated but is still left as a concept behind to not break // Note: currently packet_mode is deprecated but is still left as a concept behind to not break
// compatibility with existing network // compatibility with existing network
pub(crate) packet_type: PacketType, pub(crate) packet_mode: PacketMode,
} }
impl Header { impl Header {
pub(crate) const LEGACY_SIZE: usize = 2; pub(crate) const LEGACY_SIZE: usize = 2;
pub(crate) const VERSIONED_SIZE: usize = 3; pub(crate) const VERSIONED_SIZE: usize = 3;
pub fn outfox() -> Header {
Header {
packet_version: PacketVersion::default(),
packet_size: PacketSize::OutfoxRegularPacket,
packet_type: PacketType::Outfox,
}
}
pub(crate) fn size(&self) -> usize { pub(crate) fn size(&self) -> usize {
if self.packet_version.is_legacy() { if self.packet_version.is_legacy() {
Self::LEGACY_SIZE Self::LEGACY_SIZE
@@ -103,12 +90,12 @@ impl Header {
} }
dst.put_u8(self.packet_size as u8); dst.put_u8(self.packet_size as u8);
dst.put_u8(self.packet_type as u8); dst.put_u8(self.packet_mode as u8);
// reserve bytes for the actual packet // reserve bytes for the actual packet
dst.reserve(self.packet_size.size()); dst.reserve(self.packet_size.size());
} }
pub(crate) fn decode(src: &mut BytesMut) -> Result<Option<Self>, NymCodecError> { pub(crate) fn decode(src: &mut BytesMut) -> Result<Option<Self>, SphinxCodecError> {
if src.len() < Self::LEGACY_SIZE { if src.len() < Self::LEGACY_SIZE {
// can't do anything if we don't have enough bytes - but reserve enough for the next call // can't do anything if we don't have enough bytes - but reserve enough for the next call
src.reserve(Self::LEGACY_SIZE); src.reserve(Self::LEGACY_SIZE);
@@ -120,7 +107,7 @@ impl Header {
Ok(Some(Header { Ok(Some(Header {
packet_version, packet_version,
packet_size: PacketSize::try_from(src[0])?, packet_size: PacketSize::try_from(src[0])?,
packet_type: PacketType::try_from(src[1])?, packet_mode: PacketMode::try_from(src[1])?,
})) }))
} else if src.len() < Self::VERSIONED_SIZE { } else if src.len() < Self::VERSIONED_SIZE {
// we're missing that 1 byte to read the full header... // we're missing that 1 byte to read the full header...
@@ -130,7 +117,7 @@ impl Header {
Ok(Some(Header { Ok(Some(Header {
packet_version, packet_version,
packet_size: PacketSize::try_from(src[1])?, packet_size: PacketSize::try_from(src[1])?,
packet_type: PacketType::try_from(src[2])?, packet_mode: PacketMode::try_from(src[2])?,
})) }))
} }
} }
@@ -161,7 +148,7 @@ mod header_encoding {
[ [
PacketVersion::new_versioned(123).as_u8().unwrap(), PacketVersion::new_versioned(123).as_u8().unwrap(),
unknown_packet_size, unknown_packet_size,
PacketType::default() as u8, PacketMode::default() as u8,
] ]
.as_ref(), .as_ref(),
); );
@@ -169,12 +156,12 @@ mod header_encoding {
} }
#[test] #[test]
fn decoding_will_fail_for_unknown_packet_type() { fn decoding_will_fail_for_unknown_packet_mode() {
let unknown_packet_type: u8 = 255; let unknown_packet_mode: u8 = 255;
// make sure this is still 'unknown' for if we make changes in the future // make sure this is still 'unknown' for if we make changes in the future
assert!(PacketType::try_from(unknown_packet_type).is_err()); assert!(PacketMode::try_from(unknown_packet_mode).is_err());
let mut bytes = BytesMut::from([PacketSize::default() as u8, unknown_packet_type].as_ref()); let mut bytes = BytesMut::from([PacketSize::default() as u8, unknown_packet_mode].as_ref());
assert!(Header::decode(&mut bytes).is_err()) assert!(Header::decode(&mut bytes).is_err())
} }
@@ -204,7 +191,7 @@ mod header_encoding {
let header = Header { let header = Header {
packet_version: PacketVersion::Legacy, packet_version: PacketVersion::Legacy,
packet_size, packet_size,
..Default::default() packet_mode: Default::default(),
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
header.encode(&mut bytes); header.encode(&mut bytes);
@@ -225,7 +212,7 @@ mod header_encoding {
let header = Header { let header = Header {
packet_version: PacketVersion::Versioned(123), packet_version: PacketVersion::Versioned(123),
packet_size, packet_size,
..Default::default() packet_mode: Default::default(),
}; };
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
header.encode(&mut bytes); header.encode(&mut bytes);
+3 -3
View File
@@ -8,11 +8,11 @@ use nym_crypto::ctr;
type Aes128Ctr = ctr::Ctr64BE<Aes128>; type Aes128Ctr = ctr::Ctr64BE<Aes128>;
// Re-export for ease of use // Re-export for ease of use
pub use packet_modes::PacketMode;
pub use packet_sizes::PacketSize; pub use packet_sizes::PacketSize;
pub use packet_types::PacketType;
pub mod packet_modes;
pub mod packet_sizes; pub mod packet_sizes;
pub mod packet_types;
pub mod packet_version; pub mod packet_version;
// If somebody can provide an argument why it might be reasonable to have more than 255 mix hops, // If somebody can provide an argument why it might be reasonable to have more than 255 mix hops,
@@ -29,7 +29,7 @@ pub type SerializedFragmentIdentifier = [u8; FRAG_ID_LEN];
// when packet header gets serialized, the following bytes (in that order) are put onto the wire: // when packet header gets serialized, the following bytes (in that order) are put onto the wire:
// - packet_version (starting with v1.1.0) // - packet_version (starting with v1.1.0)
// - packet_size indicator // - packet_size indicator
// - packet_type // - packet_mode
// it also just so happens that the only valid values for packet_size indicator include values 1-6 // it also just so happens that the only valid values for packet_size indicator include values 1-6
// therefore if we receive byte `7` (or larger than that) we'll know we received a versioned packet, // therefore if we receive byte `7` (or larger than that) we'll know we received a versioned packet,
// otherwise we should treat it as legacy // otherwise we should treat it as legacy
@@ -0,0 +1,46 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryFrom;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("{received} is not a valid packet mode tag")]
pub struct InvalidPacketMode {
received: u8,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum PacketMode {
/// Represents 'normal' packet sent through the network that should be delayed by an appropriate
/// value at each hop.
#[default]
Mix = 0,
/// Represents a VPN packet that should not be delayed and ideally cached pre-computed keys
/// should be used for unwrapping data. Note that it does not offer the same level of anonymity.
Vpn = 1,
}
impl PacketMode {
pub fn is_mix(self) -> bool {
self == PacketMode::Mix
}
pub fn is_old_vpn(self) -> bool {
self == PacketMode::Vpn
}
}
impl TryFrom<u8> for PacketMode {
type Error = InvalidPacketMode;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
_ if value == (PacketMode::Mix as u8) => Ok(Self::Mix),
_ if value == (PacketMode::Vpn as u8) => Ok(Self::Vpn),
v => Err(InvalidPacketMode { received: v }),
}
}
}
+13 -115
View File
@@ -3,7 +3,7 @@
use crate::FRAG_ID_LEN; use crate::FRAG_ID_LEN;
use nym_sphinx_types::header::HEADER_SIZE; use nym_sphinx_types::header::HEADER_SIZE;
use nym_sphinx_types::{MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD, PAYLOAD_OVERHEAD_SIZE}; use nym_sphinx_types::PAYLOAD_OVERHEAD_SIZE;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::convert::TryFrom; use std::convert::TryFrom;
@@ -12,27 +12,20 @@ use std::str::FromStr;
use thiserror::Error; use thiserror::Error;
// each sphinx packet contains mandatory header and payload padding + markers // each sphinx packet contains mandatory header and payload padding + markers
const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE; const PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE;
// it's up to the smart people to figure those values out : ) // it's up to the smart people to figure those values out : )
const REGULAR_PACKET_SIZE: usize = 2 * 1024 + PACKET_OVERHEAD;
// TODO: even though we have 16B IV, is having just 5B (FRAG_ID_LEN) of the ID possibly insecure? // TODO: even though we have 16B IV, is having just 5B (FRAG_ID_LEN) of the ID possibly insecure?
// TODO: I'm not entirely sure if we can easily extract `<AckEncryptionAlgorithm as NewStreamCipher>::NonceSize` // TODO: I'm not entirely sure if we can easily extract `<AckEncryptionAlgorithm as NewStreamCipher>::NonceSize`
// into a const usize before relevant stuff is stabilised in rust... // into a const usize before relevant stuff is stabilised in rust...
const ACK_IV_SIZE: usize = 16; const ACK_IV_SIZE: usize = 16;
const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD; const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + PACKET_OVERHEAD;
const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD; const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + SPHINX_PACKET_OVERHEAD; const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD; const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD;
const OUTFOX_ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + OUTFOX_PACKET_OVERHEAD;
const OUTFOX_REGULAR_PACKET_SIZE: usize = 2 * 1024 + OUTFOX_PACKET_OVERHEAD;
const OUTFOX_EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + OUTFOX_PACKET_OVERHEAD;
const OUTFOX_EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + OUTFOX_PACKET_OVERHEAD;
const OUTFOX_EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + OUTFOX_PACKET_OVERHEAD;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum InvalidPacketSize { pub enum InvalidPacketSize {
@@ -69,25 +62,6 @@ pub enum PacketSize {
// for example for streaming fast and furious in compressed XviD quality // for example for streaming fast and furious in compressed XviD quality
#[serde(rename = "extended16")] #[serde(rename = "extended16")]
ExtendedPacket16 = 5, ExtendedPacket16 = 5,
#[serde(rename = "outfox_regular")]
OutfoxRegularPacket = 6,
// for sending SURB-ACKs
#[serde(rename = "outfox_ack")]
OutfoxAckPacket = 7,
// for example for streaming fast and furious in uncompressed 10bit 4K HDR quality
#[serde(rename = "outfox_extended32")]
OutfoxExtendedPacket32 = 8,
// for example for streaming fast and furious in heavily compressed lossy RealPlayer quality
#[serde(rename = "outfox_extended8")]
OutfoxExtendedPacket8 = 9,
// for example for streaming fast and furious in compressed XviD quality
#[serde(rename = "outfox_extended16")]
OutfoxExtendedPacket16 = 10,
} }
impl PartialOrd for PacketSize { impl PartialOrd for PacketSize {
@@ -114,11 +88,6 @@ impl FromStr for PacketSize {
"extended8" => Ok(Self::ExtendedPacket8), "extended8" => Ok(Self::ExtendedPacket8),
"extended16" => Ok(Self::ExtendedPacket16), "extended16" => Ok(Self::ExtendedPacket16),
"extended32" => Ok(Self::ExtendedPacket32), "extended32" => Ok(Self::ExtendedPacket32),
"outfox_regular" => Ok(Self::OutfoxRegularPacket),
"outfox_ack" => Ok(Self::OutfoxAckPacket),
"outfox_extended8" => Ok(Self::OutfoxExtendedPacket8),
"outfox_extended16" => Ok(Self::OutfoxExtendedPacket16),
"outfox_extended32" => Ok(Self::OutfoxExtendedPacket32),
s => Err(InvalidPacketSize::UnknownExtendedPacketVariant { s => Err(InvalidPacketSize::UnknownExtendedPacketVariant {
received: s.to_string(), received: s.to_string(),
}), }),
@@ -134,11 +103,6 @@ impl Display for PacketSize {
PacketSize::ExtendedPacket32 => write!(f, "extended32"), PacketSize::ExtendedPacket32 => write!(f, "extended32"),
PacketSize::ExtendedPacket8 => write!(f, "extended8"), PacketSize::ExtendedPacket8 => write!(f, "extended8"),
PacketSize::ExtendedPacket16 => write!(f, "extended16"), PacketSize::ExtendedPacket16 => write!(f, "extended16"),
PacketSize::OutfoxRegularPacket => write!(f, "outfox_regular"),
PacketSize::OutfoxAckPacket => write!(f, "outfox_ack"),
PacketSize::OutfoxExtendedPacket32 => write!(f, "outfox_extended32"),
PacketSize::OutfoxExtendedPacket8 => write!(f, "outfox_extended8"),
PacketSize::OutfoxExtendedPacket16 => write!(f, "outfox_extended16"),
} }
} }
} }
@@ -163,17 +127,6 @@ impl TryFrom<u8> for PacketSize {
_ if value == (PacketSize::ExtendedPacket8 as u8) => Ok(Self::ExtendedPacket8), _ if value == (PacketSize::ExtendedPacket8 as u8) => Ok(Self::ExtendedPacket8),
_ if value == (PacketSize::ExtendedPacket16 as u8) => Ok(Self::ExtendedPacket16), _ if value == (PacketSize::ExtendedPacket16 as u8) => Ok(Self::ExtendedPacket16),
_ if value == (PacketSize::ExtendedPacket32 as u8) => Ok(Self::ExtendedPacket32), _ if value == (PacketSize::ExtendedPacket32 as u8) => Ok(Self::ExtendedPacket32),
_ if value == (PacketSize::OutfoxRegularPacket as u8) => Ok(Self::OutfoxRegularPacket),
_ if value == (PacketSize::OutfoxAckPacket as u8) => Ok(Self::OutfoxAckPacket),
_ if value == (PacketSize::OutfoxExtendedPacket8 as u8) => {
Ok(Self::OutfoxExtendedPacket8)
}
_ if value == (PacketSize::OutfoxExtendedPacket16 as u8) => {
Ok(Self::OutfoxExtendedPacket16)
}
_ if value == (PacketSize::OutfoxExtendedPacket32 as u8) => {
Ok(Self::OutfoxExtendedPacket32)
}
v => Err(InvalidPacketSize::UnknownPacketTag { received: v }), v => Err(InvalidPacketSize::UnknownPacketTag { received: v }),
} }
} }
@@ -187,50 +140,15 @@ impl PacketSize {
PacketSize::ExtendedPacket8 => EXTENDED_PACKET_SIZE_8, PacketSize::ExtendedPacket8 => EXTENDED_PACKET_SIZE_8,
PacketSize::ExtendedPacket16 => EXTENDED_PACKET_SIZE_16, PacketSize::ExtendedPacket16 => EXTENDED_PACKET_SIZE_16,
PacketSize::ExtendedPacket32 => EXTENDED_PACKET_SIZE_32, PacketSize::ExtendedPacket32 => EXTENDED_PACKET_SIZE_32,
PacketSize::OutfoxRegularPacket => OUTFOX_REGULAR_PACKET_SIZE,
PacketSize::OutfoxAckPacket => OUTFOX_ACK_PACKET_SIZE,
PacketSize::OutfoxExtendedPacket8 => OUTFOX_EXTENDED_PACKET_SIZE_8,
PacketSize::OutfoxExtendedPacket16 => OUTFOX_EXTENDED_PACKET_SIZE_16,
PacketSize::OutfoxExtendedPacket32 => OUTFOX_EXTENDED_PACKET_SIZE_32,
}
}
pub const fn header_size(&self) -> usize {
match self {
PacketSize::RegularPacket
| PacketSize::AckPacket
| PacketSize::ExtendedPacket8
| PacketSize::ExtendedPacket16
| PacketSize::ExtendedPacket32 => HEADER_SIZE,
PacketSize::OutfoxRegularPacket
| PacketSize::OutfoxAckPacket
| PacketSize::OutfoxExtendedPacket8
| PacketSize::OutfoxExtendedPacket16
| PacketSize::OutfoxExtendedPacket32 => MIX_PARAMS_LEN,
}
}
pub const fn payload_overhead(&self) -> usize {
match self {
PacketSize::RegularPacket
| PacketSize::AckPacket
| PacketSize::ExtendedPacket8
| PacketSize::ExtendedPacket16
| PacketSize::ExtendedPacket32 => PAYLOAD_OVERHEAD_SIZE,
PacketSize::OutfoxRegularPacket
| PacketSize::OutfoxAckPacket
| PacketSize::OutfoxExtendedPacket8
| PacketSize::OutfoxExtendedPacket16
| PacketSize::OutfoxExtendedPacket32 => OUTFOX_PACKET_OVERHEAD - MIX_PARAMS_LEN,
} }
} }
pub const fn plaintext_size(self) -> usize { pub const fn plaintext_size(self) -> usize {
self.size() - self.header_size() - self.payload_overhead() self.size() - HEADER_SIZE - PAYLOAD_OVERHEAD_SIZE
} }
pub const fn payload_size(self) -> usize { pub const fn payload_size(self) -> usize {
self.size() - self.header_size() self.size() - HEADER_SIZE
} }
pub fn get_type(size: usize) -> Result<Self, InvalidPacketSize> { pub fn get_type(size: usize) -> Result<Self, InvalidPacketSize> {
@@ -244,16 +162,6 @@ impl PacketSize {
Ok(PacketSize::ExtendedPacket16) Ok(PacketSize::ExtendedPacket16)
} else if PacketSize::ExtendedPacket32.size() == size { } else if PacketSize::ExtendedPacket32.size() == size {
Ok(PacketSize::ExtendedPacket32) Ok(PacketSize::ExtendedPacket32)
} else if PacketSize::OutfoxRegularPacket.size() == size {
Ok(PacketSize::OutfoxRegularPacket)
} else if PacketSize::OutfoxAckPacket.size() == size {
Ok(PacketSize::OutfoxAckPacket)
} else if PacketSize::OutfoxExtendedPacket8.size() == size {
Ok(PacketSize::OutfoxExtendedPacket8)
} else if PacketSize::OutfoxExtendedPacket16.size() == size {
Ok(PacketSize::OutfoxExtendedPacket16)
} else if PacketSize::OutfoxExtendedPacket32.size() == size {
Ok(PacketSize::OutfoxExtendedPacket32)
} else { } else {
Err(InvalidPacketSize::UnknownPacketSize { received: size }) Err(InvalidPacketSize::UnknownPacketSize { received: size })
} }
@@ -261,16 +169,10 @@ impl PacketSize {
pub fn is_extended_size(&self) -> bool { pub fn is_extended_size(&self) -> bool {
match self { match self {
PacketSize::RegularPacket PacketSize::RegularPacket | PacketSize::AckPacket => false,
| PacketSize::AckPacket
| PacketSize::OutfoxAckPacket
| PacketSize::OutfoxRegularPacket => false,
PacketSize::ExtendedPacket8 PacketSize::ExtendedPacket8
| PacketSize::ExtendedPacket16 | PacketSize::ExtendedPacket16
| PacketSize::ExtendedPacket32 | PacketSize::ExtendedPacket32 => true,
| PacketSize::OutfoxExtendedPacket8
| PacketSize::OutfoxExtendedPacket16
| PacketSize::OutfoxExtendedPacket32 => true,
} }
} }
@@ -283,12 +185,8 @@ impl PacketSize {
} }
pub fn get_type_from_plaintext(plaintext_size: usize) -> Result<Self, InvalidPacketSize> { pub fn get_type_from_plaintext(plaintext_size: usize) -> Result<Self, InvalidPacketSize> {
let sphinx_packet_size = plaintext_size + SPHINX_PACKET_OVERHEAD; let packet_size = plaintext_size + PACKET_OVERHEAD;
let outfox_packet_size = plaintext_size + OUTFOX_PACKET_OVERHEAD; Self::get_type(packet_size)
match Self::get_type(sphinx_packet_size) {
Ok(t) => Ok(t),
Err(_) => Self::get_type(outfox_packet_size),
}
} }
} }
@@ -1,49 +0,0 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("{received} is not a valid packet mode tag")]
pub struct InvalidPacketType {
received: u8,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum PacketType {
/// Represents 'normal' packet sent through the network that should be delayed by an appropriate
/// value at each hop.
#[default]
Mix = 0,
/// Represents a packet that should be sent through the network as fast as possible.
Vpn = 1,
/// Abusing this to add Outfox support
Outfox = 2,
}
impl PacketType {
pub fn is_mix(self) -> bool {
self == PacketType::Mix
}
pub fn is_outfox(self) -> bool {
self == PacketType::Outfox
}
}
impl TryFrom<u8> for PacketType {
type Error = InvalidPacketType;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
_ if value == (PacketType::Mix as u8) => Ok(Self::Mix),
_ if value == (PacketType::Outfox as u8) => Ok(Self::Outfox),
v => Err(InvalidPacketType { received: v }),
}
}
}
@@ -1,11 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use crate::{PacketSize, CURRENT_PACKET_VERSION_NUMBER}; use crate::{PacketSize, CURRENT_PACKET_VERSION_NUMBER};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PacketVersion { pub enum PacketVersion {
// this will allow updated mixnodes to still understand packets from before the update // this will allow updated mixnodes to still understand packets from before the update
Legacy, Legacy,
+10 -36
View File
@@ -5,7 +5,6 @@ use crate::message::{NymMessage, ACK_OVERHEAD};
use crate::NymsphinxPayloadBuilder; use crate::NymsphinxPayloadBuilder;
use nym_crypto::asymmetric::encryption; use nym_crypto::asymmetric::encryption;
use nym_crypto::Digest; use nym_crypto::Digest;
use nym_outfox::packet::OutfoxPacket;
use nym_sphinx_acknowledgements::surb_ack::SurbAck; use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_acknowledgements::AckKey; use nym_sphinx_acknowledgements::AckKey;
use nym_sphinx_addressing::clients::Recipient; use nym_sphinx_addressing::clients::Recipient;
@@ -14,9 +13,9 @@ use nym_sphinx_anonymous_replies::reply_surb::ReplySurb;
use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier}; use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx_forwarding::packet::MixPacket; use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::packet_sizes::PacketSize; use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS}; use nym_sphinx_params::{ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::builder::SphinxPacketBuilder; use nym_sphinx_types::builder::SphinxPacketBuilder;
use nym_sphinx_types::{delays, Delay, NymPacket}; use nym_sphinx_types::{delays, Delay};
use nym_topology::{NymTopology, NymTopologyError}; use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, Rng}; use rand::{CryptoRng, Rng};
use std::convert::TryFrom; use std::convert::TryFrom;
@@ -150,12 +149,8 @@ where
let surb_ack = self.generate_surb_ack(fragment_identifier, topology, ack_key)?; let surb_ack = self.generate_surb_ack(fragment_identifier, topology, ack_key)?;
let ack_delay = surb_ack.expected_total_delay(); let ack_delay = surb_ack.expected_total_delay();
let packet_payload = match NymsphinxPayloadBuilder::new(fragment, surb_ack) let packet_payload = NymsphinxPayloadBuilder::new(fragment, surb_ack)
.build_reply(reply_surb.encryption_key()) .build_reply(reply_surb.encryption_key());
{
Ok(payload) => payload,
Err(_e) => return Err(NymTopologyError::PayloadBuilder),
};
// the unwrap here is fine as the failures can only originate from attempting to use invalid payload lengths // the unwrap here is fine as the failures can only originate from attempting to use invalid payload lengths
// and we just very carefully constructed a (presumably) valid one // and we just very carefully constructed a (presumably) valid one
@@ -195,7 +190,6 @@ where
topology: &NymTopology, topology: &NymTopology,
ack_key: &AckKey, ack_key: &AckKey,
packet_recipient: &Recipient, packet_recipient: &Recipient,
packet_type: PacketType,
) -> Result<PreparedFragment, NymTopologyError> { ) -> Result<PreparedFragment, NymTopologyError> {
// each plain or repliable packet (i.e. not a reply) attaches an ephemeral public key so that the recipient // each plain or repliable packet (i.e. not a reply) attaches an ephemeral public key so that the recipient
// could perform diffie-hellman with its own keys followed by a kdf to re-derive // could perform diffie-hellman with its own keys followed by a kdf to re-derive
@@ -214,12 +208,8 @@ where
let surb_ack = self.generate_surb_ack(fragment_identifier, topology, ack_key)?; let surb_ack = self.generate_surb_ack(fragment_identifier, topology, ack_key)?;
let ack_delay = surb_ack.expected_total_delay(); let ack_delay = surb_ack.expected_total_delay();
let packet_payload = match NymsphinxPayloadBuilder::new(fragment, surb_ack) let packet_payload = NymsphinxPayloadBuilder::new(fragment, surb_ack)
.build_regular(&mut self.rng, packet_recipient.encryption_key()) .build_regular(&mut self.rng, packet_recipient.encryption_key());
{
Ok(payload) => payload,
Err(_e) => return Err(NymTopologyError::PayloadBuilder),
};
// generate pseudorandom route for the packet // generate pseudorandom route for the packet
let route = topology.random_route_to_gateway( let route = topology.random_route_to_gateway(
@@ -234,26 +224,10 @@ where
// create the actual sphinx packet here. With valid route and correct payload size, // create the actual sphinx packet here. With valid route and correct payload size,
// there's absolutely no reason for this call to fail. // there's absolutely no reason for this call to fail.
let sphinx_packet = SphinxPacketBuilder::new()
let sphinx_packet = match packet_type { .with_payload_size(packet_size.payload_size())
PacketType::Outfox => NymPacket::Outfox(OutfoxPacket::build( .build_packet(packet_payload, &route, &destination, &delays)
packet_payload, .unwrap();
route.as_slice().try_into()?,
Some(packet_size.payload_size()),
)?),
PacketType::Mix => NymPacket::Sphinx(
SphinxPacketBuilder::new()
.with_payload_size(packet_size.payload_size())
.build_packet(packet_payload, &route, &destination, &delays)
.unwrap(),
),
PacketType::Vpn => NymPacket::Sphinx(
SphinxPacketBuilder::new()
.with_payload_size(packet_size.payload_size())
.build_packet(packet_payload, &route, &destination, &delays)
.unwrap(),
),
};
// from the previously constructed route extract the first hop // from the previously constructed route extract the first hop
let first_hop_address = let first_hop_address =
+7 -10
View File
@@ -6,7 +6,7 @@ use nym_crypto::asymmetric::encryption;
use nym_crypto::shared_key::new_ephemeral_shared_key; use nym_crypto::shared_key::new_ephemeral_shared_key;
use nym_crypto::symmetric::stream_cipher; use nym_crypto::symmetric::stream_cipher;
use nym_crypto::symmetric::stream_cipher::CipherKey; use nym_crypto::symmetric::stream_cipher::CipherKey;
use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError}; use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_anonymous_replies::SurbEncryptionKey; use nym_sphinx_anonymous_replies::SurbEncryptionKey;
use nym_sphinx_chunking::fragment::Fragment; use nym_sphinx_chunking::fragment::Fragment;
use nym_sphinx_params::{ use nym_sphinx_params::{
@@ -28,11 +28,11 @@ impl NymsphinxPayloadBuilder {
self, self,
packet_encryption_key: &CipherKey<C>, packet_encryption_key: &CipherKey<C>,
variant_data: impl IntoIterator<Item = u8>, variant_data: impl IntoIterator<Item = u8>,
) -> Result<NymsphinxPayload, SurbAckRecoveryError> ) -> NymsphinxPayload
where where
C: StreamCipher + KeyIvInit, C: StreamCipher + KeyIvInit,
{ {
let (_, surb_ack_bytes) = self.surb_ack.prepare_for_sending()?; let (_, surb_ack_bytes) = self.surb_ack.prepare_for_sending();
let mut fragment_data = self.fragment.into_bytes(); let mut fragment_data = self.fragment.into_bytes();
stream_cipher::encrypt_in_place::<C>( stream_cipher::encrypt_in_place::<C>(
@@ -46,19 +46,16 @@ impl NymsphinxPayloadBuilder {
// where variant-specific data is as follows: // where variant-specific data is as follows:
// for replies it would be the digest of the encryption key used // for replies it would be the digest of the encryption key used
// for 'regular' messages it would be the public component used in DH later used in the KDF // for 'regular' messages it would be the public component used in DH later used in the KDF
Ok(NymsphinxPayload( NymsphinxPayload(
surb_ack_bytes surb_ack_bytes
.into_iter() .into_iter()
.chain(variant_data.into_iter()) .chain(variant_data.into_iter())
.chain(fragment_data.into_iter()) .chain(fragment_data.into_iter())
.collect(), .collect(),
)) )
} }
pub fn build_reply( pub fn build_reply(self, packet_encryption_key: &SurbEncryptionKey) -> NymsphinxPayload {
self,
packet_encryption_key: &SurbEncryptionKey,
) -> Result<NymsphinxPayload, SurbAckRecoveryError> {
let key_digest = packet_encryption_key.compute_digest(); let key_digest = packet_encryption_key.compute_digest();
self.build::<ReplySurbEncryptionAlgorithm>( self.build::<ReplySurbEncryptionAlgorithm>(
packet_encryption_key.inner(), packet_encryption_key.inner(),
@@ -70,7 +67,7 @@ impl NymsphinxPayloadBuilder {
self, self,
rng: &mut R, rng: &mut R,
recipient_encryption_key: &encryption::PublicKey, recipient_encryption_key: &encryption::PublicKey,
) -> Result<NymsphinxPayload, SurbAckRecoveryError> ) -> NymsphinxPayload
where where
R: RngCore + CryptoRng, R: RngCore + CryptoRng,
{ {
+45
View File
@@ -7,6 +7,8 @@ use nym_crypto::asymmetric::encryption;
use nym_crypto::shared_key::recompute_shared_key; use nym_crypto::shared_key::recompute_shared_key;
use nym_crypto::symmetric::stream_cipher; use nym_crypto::symmetric::stream_cipher;
use nym_crypto::symmetric::stream_cipher::CipherKey; use nym_crypto::symmetric::stream_cipher::CipherKey;
use nym_outfox::error::OutfoxError;
use nym_outfox::lion::lion_transform_decrypt;
use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx_anonymous_replies::SurbEncryptionKey; use nym_sphinx_anonymous_replies::SurbEncryptionKey;
use nym_sphinx_chunking::fragment::Fragment; use nym_sphinx_chunking::fragment::Fragment;
@@ -74,6 +76,49 @@ pub enum MessageRecoveryError {
#[error("Failed to recover message fragment - {0}")] #[error("Failed to recover message fragment - {0}")]
FragmentRecoveryError(#[from] ChunkingError), FragmentRecoveryError(#[from] ChunkingError),
#[error("Outfox: {source}")]
OutfoxRecoveryError {
#[from]
source: OutfoxError,
},
}
#[derive(Default)]
pub struct OutfoxMessageReceiver {
reconstructor: MessageReconstructor,
}
impl OutfoxMessageReceiver {
pub fn new() -> Self {
Default::default()
}
}
impl MessageReceiver for OutfoxMessageReceiver {
fn new() -> Self {
Self::default()
}
fn reconstructor(&mut self) -> &mut MessageReconstructor {
&mut self.reconstructor
}
fn num_mix_hops(&self) -> u8 {
DEFAULT_NUM_MIX_HOPS
}
fn decrypt_raw_message<C>(
&self,
message: &mut [u8],
key: &CipherKey<C>,
) -> Result<(), MessageRecoveryError>
where
C: StreamCipher + KeyIvInit,
{
lion_transform_decrypt(message, key)?;
Ok(())
}
} }
pub trait MessageReceiver { pub trait MessageReceiver {
+3 -2
View File
@@ -9,5 +9,6 @@ repository = { workspace = true }
[dependencies] [dependencies]
sphinx-packet = { version = "0.1.0" } sphinx-packet = { version = "0.1.0" }
nym-outfox = { path = "../../../nym-outfox" }
thiserror = "1" #[patch.crates-io]
#sphinx-packet = { path = "../../../../sphinx" }
+1 -99
View File
@@ -1,9 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
pub use nym_outfox::{error::OutfoxError, format::MIX_PARAMS_LEN, packet::OUTFOX_PACKET_OVERHEAD};
// re-exporting types and constants available in sphinx // re-exporting types and constants available in sphinx
use nym_outfox::packet::OutfoxPacket;
pub use sphinx_packet::{ pub use sphinx_packet::{
constants::{ constants::{
self, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, MAX_PATH_LENGTH, NODE_ADDRESS_LENGTH, self, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, MAX_PATH_LENGTH, NODE_ADDRESS_LENGTH,
@@ -15,101 +13,5 @@ pub use sphinx_packet::{
payload::{Payload, PAYLOAD_OVERHEAD_SIZE}, payload::{Payload, PAYLOAD_OVERHEAD_SIZE},
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier}, route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
surb::{SURBMaterial, SURB}, surb::{SURBMaterial, SURB},
Error as SphinxError, ProcessedPacket, Error, ProcessedPacket, Result, SphinxPacket,
}; };
use sphinx_packet::{SphinxPacket, SphinxPacketBuilder};
use std::{array::TryFromSliceError, fmt};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NymPacketError {
#[error("Sphinx error: {0}")]
Sphinx(#[from] sphinx_packet::Error),
#[error("Outfox error: {0}")]
Outfox(#[from] nym_outfox::error::OutfoxError),
#[error("{0}")]
FromSlice(#[from] TryFromSliceError),
}
#[allow(clippy::large_enum_variant)]
pub enum NymPacket {
Sphinx(SphinxPacket),
Outfox(OutfoxPacket),
}
impl fmt::Debug for NymPacket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
NymPacket::Sphinx(packet) => f
.debug_struct("NymPacket::Sphinx")
.field("len", &packet.len())
.finish(),
NymPacket::Outfox(packet) => f
.debug_struct("NymPacket::Outfox")
.field("len", &packet.len())
.finish(),
}
}
}
impl NymPacket {
pub fn sphinx_build<M: AsRef<[u8]>>(
size: usize,
message: M,
route: &[Node],
destination: &Destination,
delays: &[Delay],
) -> Result<NymPacket, NymPacketError> {
Ok(NymPacket::Sphinx(
SphinxPacketBuilder::new()
.with_payload_size(size)
.build_packet(message, route, destination, delays)?,
))
}
pub fn sphinx_from_bytes(bytes: &[u8]) -> Result<NymPacket, NymPacketError> {
Ok(NymPacket::Sphinx(SphinxPacket::from_bytes(bytes)?))
}
pub fn outfox_build<M: AsRef<[u8]>>(
payload: M,
route: &[Node],
size: Option<usize>,
) -> Result<NymPacket, NymPacketError> {
Ok(NymPacket::Outfox(OutfoxPacket::build(
payload,
route.try_into()?,
size,
)?))
}
pub fn outfox_from_bytes(bytes: &[u8]) -> Result<NymPacket, NymPacketError> {
Ok(NymPacket::Outfox(OutfoxPacket::try_from(bytes)?))
}
pub fn len(&self) -> usize {
match self {
NymPacket::Sphinx(packet) => packet.len(),
NymPacket::Outfox(packet) => packet.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_bytes(&self) -> Result<Vec<u8>, NymPacketError> {
match self {
NymPacket::Sphinx(packet) => Ok(packet.to_bytes()),
NymPacket::Outfox(packet) => Ok(packet.to_bytes()?),
}
}
pub fn process(self, node_secret_key: &PrivateKey) -> Result<ProcessedPacket, NymPacketError> {
match self {
NymPacket::Sphinx(packet) => Ok(packet.process(node_secret_key)?),
NymPacket::Outfox(_packet) => todo!(),
}
}
}
+1 -14
View File
@@ -17,7 +17,6 @@ use nym_socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request, ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
}; };
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
use nym_task::connections::{LaneQueueLengths, TransmissionLane}; use nym_task::connections::{LaneQueueLengths, TransmissionLane};
use nym_task::TaskClient; use nym_task::TaskClient;
use pin_project::pin_project; use pin_project::pin_project;
@@ -182,7 +181,6 @@ pub(crate) struct SocksClient {
started_proxy: bool, started_proxy: bool,
lane_queue_lengths: LaneQueueLengths, lane_queue_lengths: LaneQueueLengths,
shutdown_listener: TaskClient, shutdown_listener: TaskClient,
packet_type: Option<PacketType>,
} }
impl Drop for SocksClient { impl Drop for SocksClient {
@@ -211,7 +209,6 @@ impl SocksClient {
self_address: &Recipient, self_address: &Recipient,
lane_queue_lengths: LaneQueueLengths, lane_queue_lengths: LaneQueueLengths,
mut shutdown_listener: TaskClient, mut shutdown_listener: TaskClient,
packet_type: Option<PacketType>,
) -> Self { ) -> Self {
// If this task fails and exits, we don't want to send shutdown signal // If this task fails and exits, we don't want to send shutdown signal
shutdown_listener.mark_as_success(); shutdown_listener.mark_as_success();
@@ -232,7 +229,6 @@ impl SocksClient {
started_proxy: false, started_proxy: false,
lane_queue_lengths, lane_queue_lengths,
shutdown_listener, shutdown_listener,
packet_type,
} }
} }
@@ -349,7 +345,6 @@ impl SocksClient {
msg.into_bytes(), msg.into_bytes(),
self.config.connection_start_surbs, self.config.connection_start_surbs,
TransmissionLane::ConnectionId(self.connection_id), TransmissionLane::ConnectionId(self.connection_id),
self.packet_type,
); );
self.input_sender self.input_sender
.send(input_message) .send(input_message)
@@ -372,7 +367,6 @@ impl SocksClient {
self.service_provider, self.service_provider,
msg.into_bytes(), msg.into_bytes(),
TransmissionLane::ConnectionId(self.connection_id), TransmissionLane::ConnectionId(self.connection_id),
self.packet_type,
); );
self.input_sender self.input_sender
.send(input_message) .send(input_message)
@@ -410,7 +404,6 @@ impl SocksClient {
let request_version = self.config.request_version(); let request_version = self.config.request_version();
let recipient = self.service_provider; let recipient = self.service_provider;
let packet_type = self.packet_type;
let (stream, _) = ProxyRunner::new( let (stream, _) = ProxyRunner::new(
stream, stream,
local_stream_remote, local_stream_remote,
@@ -439,15 +432,9 @@ impl SocksClient {
provider_message.into_bytes(), provider_message.into_bytes(),
per_request_surbs, per_request_surbs,
lane, lane,
packet_type,
) )
} else { } else {
InputMessage::new_regular( InputMessage::new_regular(recipient, provider_message.into_bytes(), lane)
recipient,
provider_message.into_bytes(),
lane,
packet_type,
)
} }
}) })
.await .await
@@ -104,7 +104,6 @@ impl SphinxSocksServer {
&self.self_address, &self.self_address,
self.lane_queue_lengths.clone(), self.lane_queue_lengths.clone(),
self.shutdown.clone(), self.shutdown.clone(),
None
); );
tokio::spawn(async move { tokio::spawn(async move {
-10
View File
@@ -8,7 +8,6 @@ use nym_mixnet_contract_common::GatewayBond;
use nym_sphinx_addressing::nodes::NodeIdentity; use nym_sphinx_addressing::nodes::NodeIdentity;
use nym_sphinx_types::Node as SphinxNode; use nym_sphinx_types::Node as SphinxNode;
use rand::{CryptoRng, Rng}; use rand::{CryptoRng, Rng};
use std::array::TryFromSliceError;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
@@ -53,15 +52,6 @@ pub enum NymTopologyError {
total_nodes: usize, total_nodes: usize,
layer_distribution: Vec<(MixLayer, usize)>, layer_distribution: Vec<(MixLayer, usize)>,
}, },
// We can't import SurbAckRecoveryError due to cyclic dependency, this is a bit dirty
#[error("Could not build payload")]
PayloadBuilder,
#[error("Outfox: {0}")]
Outfox(#[from] nym_sphinx_types::OutfoxError),
#[error("{0}")]
FromSlice(#[from] TryFromSliceError),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
+11
View File
@@ -61,6 +61,10 @@ pub enum PendingEpochEventData {
mix_id: MixId, mix_id: MixId,
amount: DecCoin, amount: DecCoin,
}, },
DecreasePledge {
mix_id: MixId,
decrease_by: DecCoin,
},
UnbondMixnode { UnbondMixnode {
mix_id: MixId, mix_id: MixId,
}, },
@@ -101,6 +105,13 @@ impl PendingEpochEventData {
amount: reg.attempt_convert_to_display_dec_coin(amount.into())?, amount: reg.attempt_convert_to_display_dec_coin(amount.into())?,
}) })
} }
MixnetContractPendingEpochEventKind::DecreasePledge {
mix_id,
decrease_by,
} => Ok(PendingEpochEventData::DecreasePledge {
mix_id,
decrease_by: reg.attempt_convert_to_display_dec_coin(decrease_by.into())?,
}),
MixnetContractPendingEpochEventKind::UnbondMixnode { mix_id } => { MixnetContractPendingEpochEventKind::UnbondMixnode { mix_id } => {
Ok(PendingEpochEventData::UnbondMixnode { mix_id }) Ok(PendingEpochEventData::UnbondMixnode { mix_id })
} }
+6
View File
@@ -0,0 +1,6 @@
[alias]
wasm = "build --target wasm32-unknown-unknown"
[build]
rustflags = ["-C", "link-arg=-s"]
#target = "wasm32-unknown-unknown"
+22 -257
View File
@@ -2,16 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array 0.14.6",
]
[[package]] [[package]]
name = "aes" name = "aes"
version = "0.7.5" version = "0.7.5"
@@ -19,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cipher 0.3.0", "cipher",
"cpufeatures", "cpufeatures",
"ctr", "ctr",
"opaque-debug 0.3.0", "opaque-debug 0.3.0",
@@ -48,12 +38,6 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@@ -102,20 +86,6 @@ dependencies = [
"opaque-debug 0.2.3", "opaque-debug 0.2.3",
] ]
[[package]]
name = "blake3"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"digest 0.10.6",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.9.0" version = "0.9.0"
@@ -125,15 +95,6 @@ dependencies = [
"generic-array 0.14.6", "generic-array 0.14.6",
] ]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array 0.14.6",
]
[[package]] [[package]]
name = "bs58" name = "bs58"
version = "0.4.0" version = "0.4.0"
@@ -189,30 +150,6 @@ dependencies = [
"keystream", "keystream",
] ]
[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher 0.4.4",
"cpufeatures",
]
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead",
"chacha20",
"cipher 0.4.4",
"poly1305",
"zeroize",
]
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.3.0" version = "0.3.0"
@@ -222,17 +159,6 @@ dependencies = [
"generic-array 0.14.6", "generic-array 0.14.6",
] ]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
"zeroize",
]
[[package]] [[package]]
name = "coconut-test" name = "coconut-test"
version = "0.1.0" version = "0.1.0"
@@ -264,12 +190,6 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
[[package]]
name = "constant_time_eq"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b"
[[package]] [[package]]
name = "cosmwasm-crypto" name = "cosmwasm-crypto"
version = "1.0.0" version = "1.0.0"
@@ -338,49 +258,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crunchy" name = "crunchy"
version = "0.2.2" version = "0.2.2"
@@ -399,17 +276,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array 0.14.6",
"rand_core 0.6.4",
"typenum",
]
[[package]] [[package]]
name = "crypto-mac" name = "crypto-mac"
version = "0.7.0" version = "0.7.0"
@@ -436,14 +302,14 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [ dependencies = [
"cipher 0.3.0", "cipher",
] ]
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "3.2.0" version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"digest 0.9.0", "digest 0.9.0",
@@ -635,17 +501,6 @@ dependencies = [
"generic-array 0.14.6", "generic-array 0.14.6",
] ]
[[package]]
name = "digest"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer 0.10.4",
"crypto-common",
"subtle 2.4.1",
]
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.11" version = "1.0.11"
@@ -846,10 +701,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
] ]
[[package]] [[package]]
@@ -897,15 +750,6 @@ dependencies = [
"ahash", "ahash",
] ]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.1" version = "0.3.1"
@@ -964,15 +808,6 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array 0.14.6",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@@ -988,7 +823,7 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
dependencies = [ dependencies = [
"hermit-abi 0.3.1", "hermit-abi",
"libc", "libc",
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
@@ -1115,12 +950,19 @@ dependencies = [
] ]
[[package]] [[package]]
name = "memoffset" name = "mixnet-vesting-integration-tests"
version = "0.8.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
dependencies = [ dependencies = [
"autocfg", "cosmwasm-std",
"cosmwasm-storage",
"cw-multi-test",
"nym-contracts-common",
"nym-crypto",
"nym-mixnet-contract",
"nym-mixnet-contract-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"rand_chacha 0.2.2",
] ]
[[package]] [[package]]
@@ -1133,16 +975,6 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi 0.2.6",
"libc",
]
[[package]] [[package]]
name = "nym-coconut-bandwidth" name = "nym-coconut-bandwidth"
version = "0.1.0" version = "0.1.0"
@@ -1289,22 +1121,6 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "nym-outfox"
version = "0.1.0"
dependencies = [
"blake3",
"chacha20",
"chacha20poly1305",
"curve25519-dalek",
"getrandom 0.2.8",
"rand",
"rayon",
"sphinx-packet",
"thiserror",
"zeroize",
]
[[package]] [[package]]
name = "nym-pemstore" name = "nym-pemstore"
version = "0.2.0" version = "0.2.0"
@@ -1343,9 +1159,7 @@ dependencies = [
name = "nym-sphinx-types" name = "nym-sphinx-types"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"nym-outfox",
"sphinx-packet", "sphinx-packet",
"thiserror",
] ]
[[package]] [[package]]
@@ -1434,17 +1248,6 @@ version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
"cpufeatures",
"opaque-debug 0.3.0",
"universal-hash",
]
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@@ -1592,28 +1395,6 @@ dependencies = [
"rand_core 0.5.1", "rand_core 0.5.1",
] ]
[[package]]
name = "rayon"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.16" version = "0.2.16"
@@ -1720,12 +1501,6 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "sec1" name = "sec1"
version = "0.2.1" version = "0.2.1"
@@ -1813,7 +1588,7 @@ version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [ dependencies = [
"block-buffer 0.9.0", "block-buffer",
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
"digest 0.9.0", "digest 0.9.0",
@@ -2045,16 +1820,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "universal-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
dependencies = [
"crypto-common",
"subtle 2.4.1",
]
[[package]] [[package]]
name = "url" name = "url"
version = "2.3.1" version = "2.3.1"
@@ -2275,9 +2040,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "x25519-dalek" name = "x25519-dalek"
version = "1.1.1" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077"
dependencies = [ dependencies = [
"curve25519-dalek", "curve25519-dalek",
"rand_core 0.5.1", "rand_core 0.5.1",
@@ -2286,9 +2051,9 @@ dependencies = [
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.6.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
dependencies = [ dependencies = [
"zeroize_derive", "zeroize_derive",
] ]
+1
View File
@@ -4,6 +4,7 @@ members = [
"coconut-dkg", "coconut-dkg",
"coconut-test", "coconut-test",
"mixnet", "mixnet",
"mixnet-vesting-integration-tests",
"multisig/cw3-flex-multisig", "multisig/cw3-flex-multisig",
"multisig/cw4-group", "multisig/cw4-group",
"service-provider-directory", "service-provider-directory",
+2 -2
View File
@@ -86,9 +86,9 @@ mod tests {
assert!(res.is_none()); assert!(res.is_none());
let mut spend_credential = SpendCredential::new( let mut spend_credential = SpendCredential::new(
funds.clone(), funds,
blind_serial_number.to_string(), blind_serial_number.to_string(),
gateway_cosmos_address.clone(), gateway_cosmos_address,
); );
spend_credential.mark_as_spent(); spend_credential.mark_as_spent();
@@ -20,6 +20,6 @@ pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>
}; };
let env = mock_env(); let env = mock_env();
let info = mock_info("creator", &[]); let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); instantiate(deps.as_mut(), env, info, msg).unwrap();
deps deps
} }
@@ -173,7 +173,7 @@ mod tests {
); );
let info = mock_info("requester", &[coin]); let info = mock_info("requester", &[coin]);
let tx = deposit_funds(deps.as_mut(), env.clone(), info, data).unwrap(); let tx = deposit_funds(deps.as_mut(), env, info, data).unwrap();
let events: Vec<_> = tx let events: Vec<_> = tx
.events .events
@@ -246,13 +246,8 @@ mod tests {
deps.querier deps.querier
.update_balance(env.contract.address.clone(), vec![funds.clone()]); .update_balance(env.contract.address.clone(), vec![funds.clone()]);
let err = release_funds( let err =
deps.as_mut(), release_funds(deps.as_mut(), env, mock_info(invalid_admin, &[]), funds).unwrap_err();
env.clone(),
mock_info(invalid_admin, &[]),
funds.clone(),
)
.unwrap_err();
assert_eq!(err, ContractError::Admin(AdminError::NotAdmin {})); assert_eq!(err, ContractError::Admin(AdminError::NotAdmin {}));
} }
@@ -294,7 +289,7 @@ mod tests {
{ {
assert_eq!(contract_addr, MULTISIG_CONTRACT); assert_eq!(contract_addr, MULTISIG_CONTRACT);
assert!(funds.is_empty()); assert!(funds.is_empty());
let multisig_msg: MultisigExecuteMsg = from_binary(&msg).unwrap(); let multisig_msg: MultisigExecuteMsg = from_binary(msg).unwrap();
if let MultisigExecuteMsg::Propose { if let MultisigExecuteMsg::Propose {
title: _, title: _,
description, description,
@@ -312,7 +307,7 @@ mod tests {
{ {
assert_eq!(*contract_addr, env.contract.address.into_string()); assert_eq!(*contract_addr, env.contract.address.into_string());
assert!(funds.is_empty()); assert!(funds.is_empty());
let release_funds_req: ExecuteMsg = from_binary(&msg).unwrap(); let release_funds_req: ExecuteMsg = from_binary(msg).unwrap();
if let ExecuteMsg::ReleaseFunds { funds } = release_funds_req { if let ExecuteMsg::ReleaseFunds { funds } = release_funds_req {
assert_eq!(funds, *data.funds()); assert_eq!(funds, *data.funds());
} else { } else {
+5 -5
View File
@@ -216,7 +216,7 @@ mod tests {
}; };
let info = mock_info("creator", &[]); let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), env.clone(), info, msg); let res = instantiate(deps.as_mut(), env, info, msg);
assert!(res.is_ok()) assert!(res.is_ok())
} }
@@ -245,7 +245,7 @@ mod tests {
announce_address: "127.0.0.1:8000".to_string(), announce_address: "127.0.0.1:8000".to_string(),
resharing: false, resharing: false,
}, },
&vec![], &[],
) )
.unwrap(); .unwrap();
assert_eq!(parse_node_index(res), (idx + 1) as u64); assert_eq!(parse_node_index(res), (idx + 1) as u64);
@@ -259,7 +259,7 @@ mod tests {
announce_address: "127.0.0.1:8000".to_string(), announce_address: "127.0.0.1:8000".to_string(),
resharing: false, resharing: false,
}, },
&vec![], &[],
) )
.unwrap_err(); .unwrap_err();
assert_eq!(ContractError::AlreadyADealer, err.downcast().unwrap()); assert_eq!(ContractError::AlreadyADealer, err.downcast().unwrap());
@@ -269,13 +269,13 @@ mod tests {
let err = app let err = app
.execute_contract( .execute_contract(
unauthorized_member, unauthorized_member,
coconut_dkg_contract_addr.clone(), coconut_dkg_contract_addr,
&RegisterDealer { &RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(), bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(), announce_address: "127.0.0.1:8000".to_string(),
resharing: false, resharing: false,
}, },
&vec![], &[],
) )
.unwrap_err(); .unwrap_err();
assert_eq!(ContractError::Unauthorized, err.downcast().unwrap()); assert_eq!(ContractError::Unauthorized, err.downcast().unwrap());
@@ -153,9 +153,9 @@ pub(crate) mod tests {
let ret = try_add_dealer( let ret = try_add_dealer(
deps.as_mut(), deps.as_mut(),
info.clone(), info,
bte_key_with_proof.clone(), bte_key_with_proof,
announce_address.clone(), announce_address,
false, false,
) )
.unwrap_err(); .unwrap_err();
@@ -56,11 +56,11 @@ pub(crate) mod tests {
for n in 0..size { for n in 0..size {
let dealing_share = dealing_bytes_fixture(); let dealing_share = dealing_bytes_fixture();
let sender = Addr::unchecked(format!("owner{}", n)); let sender = Addr::unchecked(format!("owner{}", n));
for idx in 0..TOTAL_DEALINGS { (0..TOTAL_DEALINGS).for_each(|idx| {
DEALINGS_BYTES[idx] DEALINGS_BYTES[idx]
.save(deps.storage, &sender, &dealing_share) .save(deps.storage, &sender, &dealing_share)
.unwrap(); .unwrap();
} });
} }
} }
@@ -131,8 +131,7 @@ pub(crate) mod tests {
assert!(ret.is_ok()); assert!(ret.is_ok());
assert!(dealings.has(deps.as_mut().storage, &owner)); assert!(dealings.has(deps.as_mut().storage, &owner));
} }
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true) let ret = try_commit_dealings(deps.as_mut(), info, dealing_bytes, true).unwrap_err();
.unwrap_err();
assert_eq!( assert_eq!(
ret, ret,
ContractError::AlreadyCommitted { ContractError::AlreadyCommitted {
@@ -236,10 +236,10 @@ pub(crate) mod tests {
let limit = *limits.next().unwrap(); let limit = *limits.next().unwrap();
{ {
let mut group_members = GROUP_MEMBERS.lock().unwrap(); let mut group_members = GROUP_MEMBERS.lock().unwrap();
for i in 0..n as usize { for dealer in dealers.iter() {
group_members.push(( group_members.push((
Member { Member {
addr: dealers[i].address.to_string(), addr: dealer.address.to_string(),
weight: 10, weight: 10,
}, },
1, 1,
@@ -339,7 +339,7 @@ pub(crate) mod tests {
) )
.unwrap() .unwrap()
); );
for i in 0..3 as u64 { for i in 0..3_u64 {
let details = dealer_details_fixture(i + 1); let details = dealer_details_fixture(i + 1);
current_dealers() current_dealers()
.save(deps.as_mut().storage, &details.address, &details) .save(deps.as_mut().storage, &details.address, &details)
@@ -90,6 +90,6 @@ pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>
}; };
let env = mock_env(); let env = mock_env();
let info = mock_info(ADMIN_ADDRESS, &[]); let info = mock_info(ADMIN_ADDRESS, &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); instantiate(deps.as_mut(), env, info, msg).unwrap();
deps deps
} }
@@ -129,14 +129,8 @@ mod tests {
.save(deps.as_mut().storage, &dealer, &dealer_details) .save(deps.as_mut().storage, &dealer, &dealer_details)
.unwrap(); .unwrap();
try_commit_verification_key_share( try_commit_verification_key_share(deps.as_mut(), env, info.clone(), share.clone(), false)
deps.as_mut(), .unwrap();
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap(); let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap();
assert_eq!( assert_eq!(
vk_share, vk_share,
@@ -215,14 +209,8 @@ mod tests {
) )
.unwrap(); .unwrap();
let ret = try_commit_verification_key_share( let ret =
deps.as_mut(), try_commit_verification_key_share(deps.as_mut(), env, info, share, false).unwrap_err();
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap_err();
assert_eq!( assert_eq!(
ret, ret,
ContractError::AlreadyCommitted { ContractError::AlreadyCommitted {
@@ -318,14 +306,7 @@ mod tests {
dealers_storage::current_dealers() dealers_storage::current_dealers()
.save(deps.as_mut().storage, &owner, &dealer_details) .save(deps.as_mut().storage, &owner, &dealer_details)
.unwrap(); .unwrap();
try_commit_verification_key_share( try_commit_verification_key_share(deps.as_mut(), env.clone(), info, share, false).unwrap();
deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
env.block.time = env env.block.time = env
.block .block
@@ -338,7 +319,6 @@ mod tests {
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
advance_epoch_state(deps.as_mut(), env).unwrap(); advance_epoch_state(deps.as_mut(), env).unwrap();
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false) try_verify_verification_key_share(deps.as_mut(), multisig_info, owner, false).unwrap();
.unwrap();
} }
} }
@@ -89,13 +89,8 @@ fn deposit_and_release() {
let msg = ExecuteMsg::ReleaseFunds { let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(), funds: deposit_funds[0].clone(),
}; };
app.execute_contract( app.execute_contract(Addr::unchecked(multisig_addr), contract_addr, &msg, &[])
Addr::unchecked(multisig_addr), .unwrap();
contract_addr.clone(),
&msg,
&[],
)
.unwrap();
let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap(); let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap();
assert_eq!(pool_bal, deposit_funds[0]); assert_eq!(pool_bal, deposit_funds[0]);
} }
@@ -101,7 +101,7 @@ fn spend_credential_creates_proposal() {
Addr::unchecked(OWNER), Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(), coconut_bandwidth_contract_addr.clone(),
&msg, &msg,
&vec![], &[],
) )
.unwrap(); .unwrap();
let proposal_id = res let proposal_id = res
@@ -124,7 +124,7 @@ fn spend_credential_creates_proposal() {
Addr::unchecked(OWNER), Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(), coconut_bandwidth_contract_addr.clone(),
&msg, &msg,
&vec![], &[],
) )
.unwrap_err(); .unwrap_err();
assert_eq!( assert_eq!(
@@ -142,9 +142,9 @@ fn spend_credential_creates_proposal() {
let res = app let res = app
.execute_contract( .execute_contract(
Addr::unchecked(OWNER), Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(), coconut_bandwidth_contract_addr,
&msg, &msg,
&vec![], &[],
) )
.unwrap(); .unwrap();
let proposal_id = res let proposal_id = res
@@ -105,7 +105,7 @@ fn dkg_proposal() {
announce_address: "127.0.0.1:8000".to_string(), announce_address: "127.0.0.1:8000".to_string(),
resharing: false, resharing: false,
}, },
&vec![], &[],
) )
.unwrap(); .unwrap();
@@ -115,7 +115,7 @@ fn dkg_proposal() {
Addr::unchecked(OWNER), Addr::unchecked(OWNER),
coconut_dkg_contract_addr.clone(), coconut_dkg_contract_addr.clone(),
&AdvanceEpochState {}, &AdvanceEpochState {},
&vec![], &[],
) )
.unwrap(); .unwrap();
} }
@@ -132,7 +132,7 @@ fn dkg_proposal() {
Addr::unchecked(MEMBER1), Addr::unchecked(MEMBER1),
coconut_dkg_contract_addr.clone(), coconut_dkg_contract_addr.clone(),
&msg, &msg,
&vec![], &[],
) )
.unwrap(); .unwrap();
@@ -174,7 +174,7 @@ fn dkg_proposal() {
proposal_id, proposal_id,
vote: cw3::Vote::Yes, vote: cw3::Vote::Yes,
}, },
&vec![], &[],
) )
.unwrap(); .unwrap();
@@ -184,16 +184,16 @@ fn dkg_proposal() {
Addr::unchecked(OWNER), Addr::unchecked(OWNER),
coconut_dkg_contract_addr.clone(), coconut_dkg_contract_addr.clone(),
&AdvanceEpochState {}, &AdvanceEpochState {},
&vec![], &[],
) )
.unwrap(); .unwrap();
} }
app.execute_contract( app.execute_contract(
Addr::unchecked(MEMBER1), Addr::unchecked(MEMBER1),
multisig_contract_addr.clone(), multisig_contract_addr,
&Execute { proposal_id }, &Execute { proposal_id },
&vec![], &[],
) )
.unwrap(); .unwrap();
@@ -0,0 +1,32 @@
[package]
name = "mixnet-vesting-integration-tests"
version = "0.1.0"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# cosmwasm dependencies
cosmwasm-std = { workspace = true }
cosmwasm-storage = { workspace = true }
cw-multi-test = { workspace = true }
# contracts dependencies
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
nym-mixnet-contract = { path = "../mixnet" }
nym-vesting-contract = { path = "../vesting" }
# other local dependencies
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
# external dependencies
rand_chacha = "0.2"
[[test]]
name = "mixnet-vesting-test"
path = "src/tests.rs"
@@ -0,0 +1,240 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::helpers::{mix_coin, mix_coins, vesting_owner};
use crate::support::setup::{TestSetup, MIX_DENOM};
use cosmwasm_std::Addr;
use cw_multi_test::Executor;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::error::MixnetContractError;
use nym_mixnet_contract_common::{ContractStateParams, MixNodeCostParams};
use nym_mixnet_contract_common::{MixOwnershipResponse, QueryMsg as MixnetQueryMsg};
use nym_vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
use vesting_contract::errors::ContractError as VestingContractError;
#[test]
fn decrease_mixnode_pledge_from_vesting_account_with_minimum_pledge() {
let mut test = TestSetup::new_simple();
let vesting_account = "vesting-account";
// 0. get the minimum pledge amount
let state_params: ContractStateParams = test
.app
.wrap()
.query_wasm_smart(test.mixnet_contract(), &MixnetQueryMsg::GetStateParams {})
.unwrap();
let minimum_pledge = state_params.minimum_mixnode_pledge;
// 1. create vesting account
let create_msg = VestingExecuteMsg::CreateAccount {
owner_address: vesting_account.to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
test.app
.execute_contract(
vesting_owner(),
test.vesting_contract(),
&create_msg,
&mix_coins(1_000_000_000),
)
.unwrap();
// 2. bond mixnode with the vesting account
let pledge = minimum_pledge.clone();
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: mix_coin(40_000_000),
};
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
vesting_account,
Some(test.vesting_contract()),
cost_params.clone(),
pledge.clone(),
);
let bond_msg = VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount: pledge.clone(),
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&bond_msg,
&[],
)
.unwrap();
// 3. try to decrease the pledge
// trying to decrease by a zero amount - not valid
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: mix_coin(0),
};
let res_zero = test
.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap_err();
assert_eq!(
VestingContractError::EmptyFunds,
res_zero.downcast().unwrap()
);
// trying to go below the cap - also not valid
let amount = mix_coin(50_000);
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: amount.clone(),
};
let res_below = test
.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap_err();
assert_eq!(
MixnetContractError::InvalidPledgeReduction {
current: pledge.amount,
decrease_by: amount.amount,
minimum: minimum_pledge.amount,
denom: minimum_pledge.denom
},
res_below.downcast().unwrap()
)
}
#[test]
fn decrease_mixnode_pledge_from_vesting_account_with_sufficient_pledge() {
let mut test = TestSetup::new_simple();
let vesting_account = "vesting-account";
// 1. create vesting account
let create_msg = VestingExecuteMsg::CreateAccount {
owner_address: vesting_account.to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
test.app
.execute_contract(
vesting_owner(),
test.vesting_contract(),
&create_msg,
&mix_coins(10_000_000_000),
)
.unwrap();
// 2. bond mixnode with the vesting account
let pledge = mix_coin(150_000_000);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: mix_coin(40_000_000),
};
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
vesting_account,
Some(test.vesting_contract()),
cost_params.clone(),
pledge.clone(),
);
let bond_msg = VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount: pledge,
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&bond_msg,
&[],
)
.unwrap();
// 3. try to decrease the pledge
let before: MixOwnershipResponse = test
.app
.wrap()
.query_wasm_smart(
test.mixnet_contract(),
&MixnetQueryMsg::GetOwnedMixnode {
address: vesting_account.to_string(),
},
)
.unwrap();
let balance_before = test
.app
.wrap()
.query_balance(test.vesting_contract(), MIX_DENOM)
.unwrap();
assert_eq!(balance_before.amount.u128(), 9_850_000_000);
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: mix_coin(50_000_000),
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap();
let after_decrease: MixOwnershipResponse = test
.app
.wrap()
.query_wasm_smart(
test.mixnet_contract(),
&MixnetQueryMsg::GetOwnedMixnode {
address: vesting_account.to_string(),
},
)
.unwrap();
// note: nothing has changed with the pledge because the event hasn't been resolved yet!
assert_eq!(before.address, after_decrease.address);
let before_details = before.mixnode_details.unwrap();
let after_details = after_decrease.mixnode_details.unwrap();
assert_eq!(
before_details.rewarding_details,
after_details.rewarding_details
);
assert_eq!(
before_details.bond_information,
after_details.bond_information
);
// but we have the pending change saved now!
assert!(before_details.pending_changes.pledge_change.is_none());
assert_eq!(Some(1), after_details.pending_changes.pledge_change);
// 4. resolve events
test.advance_mixnet_epoch();
let balance_after = test
.app
.wrap()
.query_balance(test.vesting_contract(), MIX_DENOM)
.unwrap();
assert_eq!(balance_after.amount.u128(), 9_900_000_000);
}
@@ -0,0 +1,28 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::setup::{MIX_DENOM, REWARDING_VALIDATOR};
use cosmwasm_std::Decimal;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::InitialRewardingParams;
use std::time::Duration;
pub fn default_mixnet_init_msg() -> nym_mixnet_contract_common::InstantiateMsg {
nym_mixnet_contract_common::InstantiateMsg {
rewarding_validator_address: REWARDING_VALIDATOR.to_string(),
vesting_contract_address: "placeholder".to_string(),
rewarding_denom: MIX_DENOM.to_string(),
epochs_in_interval: 720,
epoch_duration: Duration::from_secs(60 * 60),
initial_rewarding_params: InitialRewardingParams {
initial_reward_pool: Decimal::from_atomics(250_000_000_000_000u128, 0).unwrap(),
initial_staking_supply: Decimal::from_atomics(223_000_000_000_000u128, 0).unwrap(),
staking_supply_scale_factor: Percent::hundred(),
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
interval_pool_emission: Percent::from_percentage_value(2).unwrap(),
rewarded_set_size: 240,
active_set_size: 100,
},
}
}
@@ -0,0 +1,56 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::setup::{MIXNET_OWNER, MIX_DENOM, REWARDING_VALIDATOR, VESTING_OWNER};
use cosmwasm_std::{coin, coins, Addr, Coin, Empty};
use cw_multi_test::{Contract, ContractWrapper};
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha20Rng;
#[allow(unused)]
pub fn mixnet_owner() -> Addr {
Addr::unchecked(MIXNET_OWNER)
}
pub fn vesting_owner() -> Addr {
Addr::unchecked(VESTING_OWNER)
}
pub fn rewarding_validator() -> Addr {
Addr::unchecked(REWARDING_VALIDATOR)
}
pub fn mix_coins(amount: u128) -> Vec<Coin> {
coins(amount, MIX_DENOM)
}
pub fn mix_coin(amount: u128) -> Coin {
coin(amount, MIX_DENOM)
}
pub fn test_rng() -> ChaCha20Rng {
let dummy_seed = [42u8; 32];
ChaCha20Rng::from_seed(dummy_seed)
}
pub fn mixnet_contract_wrapper() -> Box<dyn Contract<Empty>> {
Box::new(
ContractWrapper::new(
mixnet_contract::contract::execute,
mixnet_contract::contract::instantiate,
mixnet_contract::contract::query,
)
.with_migrate(mixnet_contract::contract::migrate),
)
}
pub fn vesting_contract_wrapper() -> Box<dyn Contract<Empty>> {
Box::new(
ContractWrapper::new(
vesting_contract::contract::execute,
vesting_contract::contract::instantiate,
vesting_contract::contract::query,
)
.with_migrate(vesting_contract::contract::migrate),
)
}
@@ -0,0 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod fixtures;
pub mod helpers;
pub mod setup;
@@ -0,0 +1,328 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::fixtures;
use crate::support::helpers::{
mixnet_contract_wrapper, rewarding_validator, test_rng, vesting_contract_wrapper,
};
use cosmwasm_std::{coins, Addr, Coin, Timestamp};
use cw_multi_test::{App, AppBuilder, Executor};
use nym_contracts_common::signing::{ContractMessageContent, MessageSignature, Nonce};
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::{
CurrentIntervalResponse, LayerAssignment, MixNodeCostParams, MixnodeBondingPayload,
PagedRewardedSetResponse, RewardingParams, SignableMixNodeBondingMsg,
};
use nym_mixnet_contract_common::{
ExecuteMsg as MixnetExecuteMsg, MixNode, QueryMsg as MixnetQueryMsg,
};
use rand_chacha::ChaCha20Rng;
use std::collections::HashMap;
// our global accounts that should always get some coins at the start
pub const MIXNET_OWNER: &str = "mixnet-owner";
pub const VESTING_OWNER: &str = "vesting-owner";
pub const REWARDING_VALIDATOR: &str = "rewarding-validator";
pub const MIX_DENOM: &str = "unym";
pub struct ContractInstantiationResult {
mixnet_contract_address: Addr,
vesting_contract_address: Addr,
}
#[allow(unused)]
pub struct TestSetupBuilder {
mixnet_init_msg: nym_mixnet_contract_common::InstantiateMsg,
initial_balances: HashMap<Addr, Vec<Coin>>,
}
#[allow(unused)]
impl TestSetupBuilder {
pub fn new() -> Self {
TestSetupBuilder {
mixnet_init_msg: fixtures::default_mixnet_init_msg(),
initial_balances: Default::default(),
}
}
pub fn with_mixnet_init_msg(
mut self,
mixnet_init_msg: nym_mixnet_contract_common::InstantiateMsg,
) -> Self {
self.mixnet_init_msg = mixnet_init_msg;
self
}
pub fn with_initial_balances(mut self, initial_balances: HashMap<Addr, Vec<Coin>>) -> Self {
self.initial_balances = initial_balances;
self
}
pub fn with_initial_balance(mut self, addr: impl Into<String>, balance: Vec<Coin>) -> Self {
self.initial_balances.insert(Addr::unchecked(addr), balance);
self
}
pub fn build(self) -> TestSetup {
TestSetup::new(self.initial_balances, self.mixnet_init_msg)
}
}
pub struct TestSetup {
pub app: App,
pub rng: ChaCha20Rng,
pub mixnet_contract: Addr,
pub vesting_contract: Addr,
}
impl TestSetup {
pub fn new_simple() -> Self {
TestSetup::new(Default::default(), fixtures::default_mixnet_init_msg())
}
pub fn new(
initial_balances: HashMap<Addr, Vec<Coin>>,
custom_mixnet_init: nym_mixnet_contract_common::InstantiateMsg,
) -> Self {
let (app, contracts) = instantiate_contracts(initial_balances, Some(custom_mixnet_init));
TestSetup {
app,
rng: test_rng(),
mixnet_contract: contracts.mixnet_contract_address,
vesting_contract: contracts.vesting_contract_address,
}
}
pub fn mixnet_contract(&self) -> Addr {
self.mixnet_contract.clone()
}
pub fn vesting_contract(&self) -> Addr {
self.vesting_contract.clone()
}
pub fn skip_to_current_epoch_end(&mut self) {
let current_interval: CurrentIntervalResponse = self
.app
.wrap()
.query_wasm_smart(
self.mixnet_contract(),
&MixnetQueryMsg::GetCurrentIntervalDetails {},
)
.unwrap();
let epoch_end = current_interval.interval.current_epoch_end_unix_timestamp();
self.app.update_block(|current_block| {
// skip few blocks just in case
current_block.height += 10;
current_block.time = Timestamp::from_seconds(epoch_end as u64)
})
}
pub fn full_mixnet_epoch_operations(&mut self) {
let current_rewarded_set: PagedRewardedSetResponse = self
.app
.wrap()
.query_wasm_smart(
self.mixnet_contract(),
&MixnetQueryMsg::GetRewardedSet {
limit: Some(9999),
start_after: None,
},
)
.unwrap();
let current_params: RewardingParams = self
.app
.wrap()
.query_wasm_smart(
self.mixnet_contract(),
&MixnetQueryMsg::GetRewardingParams {},
)
.unwrap();
// TODO: handle paging
// begin epoch transition
self.app
.execute_contract(
rewarding_validator(),
self.mixnet_contract(),
&MixnetExecuteMsg::BeginEpochTransition {},
&[],
)
.unwrap();
// reward
for (mix_id, _status) in &current_rewarded_set.nodes {
self.app
.execute_contract(
rewarding_validator(),
self.mixnet_contract(),
&MixnetExecuteMsg::RewardMixnode {
mix_id: *mix_id,
performance: Performance::hundred(),
},
&[],
)
.unwrap();
}
// events
self.app
.execute_contract(
rewarding_validator(),
self.mixnet_contract(),
&MixnetExecuteMsg::ReconcileEpochEvents { limit: None },
&[],
)
.unwrap();
// don't bother changing the active set, use the same node for update and advance
let new_rewarded_set = current_rewarded_set
.nodes
.into_iter()
.enumerate()
.map(|(i, (node, _))| {
LayerAssignment::new(node, ((i as u8 % 3) + 1).try_into().unwrap())
})
.collect();
self.app
.execute_contract(
rewarding_validator(),
self.mixnet_contract(),
&MixnetExecuteMsg::AdvanceCurrentEpoch {
new_rewarded_set,
expected_active_set_size: current_params.active_set_size,
},
&[],
)
.unwrap();
}
pub fn advance_mixnet_epoch(&mut self) {
self.skip_to_current_epoch_end();
self.full_mixnet_epoch_operations();
}
pub fn valid_mixnode_with_sig(
&mut self,
owner: &str,
proxy: Option<Addr>,
cost_params: MixNodeCostParams,
stake: Coin,
) -> (MixNode, MessageSignature) {
let signing_nonce: Nonce = self
.app
.wrap()
.query_wasm_smart(
self.mixnet_contract(),
&MixnetQueryMsg::GetSigningNonce {
address: owner.to_string(),
},
)
.unwrap();
let keypair = identity::KeyPair::new(&mut self.rng);
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let mixnode = MixNode {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
host: "mix.node.org".to_string(),
mix_port: 1789,
verloc_port: 1790,
http_api_port: 8000,
version: "1.1.14".to_string(),
};
let payload = MixnodeBondingPayload::new(mixnode.clone(), cost_params);
let content =
ContractMessageContent::new(Addr::unchecked(owner), proxy, vec![stake], payload);
let sign_payload = SignableMixNodeBondingMsg::new(signing_nonce, content);
let plaintext = sign_payload.to_plaintext().unwrap();
let signature = keypair.private_key().sign(&plaintext);
let msg_signature = MessageSignature::from(signature.to_bytes().as_ref());
(mixnode, msg_signature)
}
}
pub fn instantiate_contracts(
mut initial_funds: HashMap<Addr, Vec<Coin>>,
custom_mixnet_init: Option<nym_mixnet_contract_common::InstantiateMsg>,
) -> (App, ContractInstantiationResult) {
// add our global addresses to the map
initial_funds.insert(
Addr::unchecked(MIXNET_OWNER),
coins(100_000_000_000, MIX_DENOM),
);
initial_funds.insert(
Addr::unchecked(VESTING_OWNER),
coins(100_000_000_000, MIX_DENOM),
);
initial_funds.insert(
Addr::unchecked(REWARDING_VALIDATOR),
coins(1_000_000_000_000, MIX_DENOM),
);
let mut app = AppBuilder::new().build(|router, _api, storage| {
for (addr, funds) in initial_funds {
router
.bank
.init_balance(storage, &addr, funds.clone())
.unwrap()
}
});
let mixnet_code_id = app.store_code(mixnet_contract_wrapper());
let vesting_code_id = app.store_code(vesting_contract_wrapper());
let mixnet_contract_address = app
.instantiate_contract(
mixnet_code_id,
Addr::unchecked(MIXNET_OWNER),
&custom_mixnet_init.unwrap_or(fixtures::default_mixnet_init_msg()),
&[],
"mixnet-contract",
Some(MIXNET_OWNER.to_string()),
)
.unwrap();
let vesting_contract_address = app
.instantiate_contract(
vesting_code_id,
Addr::unchecked(VESTING_OWNER),
&nym_vesting_contract_common::InitMsg {
mixnet_contract_address: mixnet_contract_address.to_string(),
mix_denom: MIX_DENOM.to_string(),
},
&[],
"vesting-contract",
Some(VESTING_OWNER.to_string()),
)
.unwrap();
// now fix up vesting contract address...
app.migrate_contract(
Addr::unchecked(MIXNET_OWNER),
mixnet_contract_address.clone(),
&nym_mixnet_contract_common::MigrateMsg {
vesting_contract_address: Some(vesting_contract_address.to_string()),
},
mixnet_code_id,
)
.unwrap();
(
app,
ContractInstantiationResult {
mixnet_contract_address,
vesting_contract_address,
},
)
}
@@ -0,0 +1,5 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod decrease_mixnode_pledge;
mod support;
+2
View File
@@ -0,0 +1,2 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
+2 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Uint128; use cosmwasm_std::Uint128;
@@ -61,6 +61,7 @@ pub const CONTRACT_STATE_KEY: &str = "state";
pub const LAYER_DISTRIBUTION_KEY: &str = "layers"; pub const LAYER_DISTRIBUTION_KEY: &str = "layers";
pub const NODE_ID_COUNTER_KEY: &str = "nic"; pub const NODE_ID_COUNTER_KEY: &str = "nic";
pub const PENDING_MIXNODE_CHANGES_NAMESPACE: &str = "pmc";
pub const MIXNODES_PK_NAMESPACE: &str = "mnn"; pub const MIXNODES_PK_NAMESPACE: &str = "mnn";
pub const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno"; pub const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
pub const MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "mni"; pub const MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "mni";
+20 -1
View File
@@ -251,6 +251,18 @@ pub fn execute(
ExecuteMsg::PledgeMoreOnBehalf { owner } => { ExecuteMsg::PledgeMoreOnBehalf { owner } => {
crate::mixnodes::transactions::try_increase_pledge_on_behalf(deps, env, info, owner) crate::mixnodes::transactions::try_increase_pledge_on_behalf(deps, env, info, owner)
} }
ExecuteMsg::DecreasePledge { decrease_by } => {
crate::mixnodes::transactions::try_decrease_pledge(deps, env, info, decrease_by)
}
ExecuteMsg::DecreasePledgeOnBehalf { owner, decrease_by } => {
crate::mixnodes::transactions::try_decrease_pledge_on_behalf(
deps,
env,
info,
decrease_by,
owner,
)
}
ExecuteMsg::UnbondMixnode {} => { ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info) crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
} }
@@ -573,6 +585,12 @@ pub fn query(
limit, limit,
)?, )?,
), ),
QueryMsg::GetPendingEpochEvent { event_id } => to_binary(
&crate::interval::queries::query_pending_epoch_event(deps, event_id)?,
),
QueryMsg::GetPendingIntervalEvent { event_id } => to_binary(
&crate::interval::queries::query_pending_interval_event(deps, event_id)?,
),
QueryMsg::GetNumberOfPendingEvents {} => to_binary( QueryMsg::GetNumberOfPendingEvents {} => to_binary(
&crate::interval::queries::query_number_of_pending_events(deps)?, &crate::interval::queries::query_number_of_pending_events(deps)?,
), ),
@@ -586,7 +604,7 @@ pub fn query(
#[entry_point] #[entry_point]
pub fn migrate( pub fn migrate(
deps: DepsMut<'_>, mut deps: DepsMut<'_>,
_env: Env, _env: Env,
msg: MigrateMsg, msg: MigrateMsg,
) -> Result<Response, MixnetContractError> { ) -> Result<Response, MixnetContractError> {
@@ -612,6 +630,7 @@ pub fn migrate(
// If state structure changed in any contract version in the way migration is needed, it // If state structure changed in any contract version in the way migration is needed, it
// should occur here, for example anything from `crate::queued_migrations::` // should occur here, for example anything from `crate::queued_migrations::`
crate::queued_migrations::insert_pending_pledge_changes(deps.branch())?;
} }
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address // due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
@@ -489,7 +489,7 @@ mod test {
test.deps_mut(), test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]), mock_info(illegal_proxy.as_ref(), &[]),
new_member.to_string(), new_member.to_string(),
family_head.clone(), family_head,
) )
.unwrap_err(); .unwrap_err();
@@ -362,7 +362,7 @@ pub mod tests {
let res = try_add_gateway( let res = try_add_gateway(
test.deps_mut(), test.deps_mut(),
env.clone(), env.clone(),
info.clone(), info,
gateway.clone(), gateway.clone(),
signature.clone(), signature.clone(),
); );
+598 -51
View File
@@ -1,6 +1,22 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Addr, Coin, DepsMut, Env, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_active_set_update_event, new_delegation_event, new_delegation_on_unbonded_node_event,
new_mixnode_cost_params_update_event, new_mixnode_unbonding_event, new_pledge_decrease_event,
new_pledge_increase_event, new_rewarding_params_update_event, new_undelegation_event,
};
use mixnet_contract_common::mixnode::MixNodeCostParams;
use mixnet_contract_common::pending_events::{
PendingEpochEventData, PendingEpochEventKind, PendingIntervalEventData,
PendingIntervalEventKind,
};
use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate;
use mixnet_contract_common::{BlockHeight, Delegation, MixId};
use crate::delegations; use crate::delegations;
use crate::delegations::storage as delegations_storage; use crate::delegations::storage as delegations_storage;
use crate::interval::helpers::change_interval_config; use crate::interval::helpers::change_interval_config;
@@ -9,20 +25,6 @@ use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_
use crate::mixnodes::storage as mixnodes_storage; use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::storage as rewards_storage; use crate::rewards::storage as rewards_storage;
use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking}; use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking};
use cosmwasm_std::{Addr, Coin, DepsMut, Env, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_active_set_update_event, new_delegation_event, new_delegation_on_unbonded_node_event,
new_mixnode_cost_params_update_event, new_mixnode_unbonding_event, new_pledge_increase_event,
new_rewarding_params_update_event, new_undelegation_event,
};
use mixnet_contract_common::mixnode::MixNodeCostParams;
use mixnet_contract_common::pending_events::{
PendingEpochEventData, PendingEpochEventKind, PendingIntervalEventData,
PendingIntervalEventKind,
};
use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate;
use mixnet_contract_common::{BlockHeight, Delegation, MixId};
pub(crate) trait ContractExecutableEvent { pub(crate) trait ContractExecutableEvent {
// note: the error only means a HARD error like we failed to read from storage. // note: the error only means a HARD error like we failed to read from storage.
@@ -146,10 +148,9 @@ pub(crate) fn undelegate(
Some(delegation) => delegation, Some(delegation) => delegation,
}; };
let mix_rewarding = let mix_rewarding =
rewards_storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::InconsistentState { rewards_storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::inconsistent_state(
comment: "mixnode rewarding got removed from the storage whilst there's still an existing delegation" "mixnode rewarding got removed from the storage whilst there's still an existing delegation",
.into(), ))?;
})?;
// this also appropriately adjusts the storage // this also appropriately adjusts the storage
let tokens_to_return = let tokens_to_return =
delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?; delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?;
@@ -180,11 +181,15 @@ pub(crate) fn unbond_mixnode(
// in unbonding state and thus nothing could have been done to it (such as attempting to double unbond it) // in unbonding state and thus nothing could have been done to it (such as attempting to double unbond it)
// thus the node with all its associated information MUST exist in the storage. // thus the node with all its associated information MUST exist in the storage.
let node_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or( let node_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
MixnetContractError::InconsistentState { MixnetContractError::inconsistent_state(
comment: "mixnode getting processed to get unbonded doesn't exist in the storage" "mixnode getting processed to get unbonded doesn't exist in the storage",
.into(), ),
},
)?; )?;
if node_details.pending_changes.pledge_change.is_some() {
return Err(MixnetContractError::inconsistent_state(
"attempted to unbond mixnode while there are associated pending pledge changes",
));
}
// the denom on the original pledge was validated at the time of bonding so we can safely reuse it here // the denom on the original pledge was validated at the time of bonding so we can safely reuse it here
let rewarding_denom = &node_details.bond_information.original_pledge.denom; let rewarding_denom = &node_details.bond_information.original_pledge.denom;
@@ -244,12 +249,15 @@ pub(crate) fn increase_pledge(
// the target node MUST exist - we have checked it at the time of putting this event onto the queue // the target node MUST exist - we have checked it at the time of putting this event onto the queue
// we have also verified there were no preceding unbond events // we have also verified there were no preceding unbond events
let mix_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or( let mix_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
MixnetContractError::InconsistentState { MixnetContractError::inconsistent_state(
comment: "mixnode getting processed to increase its pledge doesn't exist in the storage",
"mixnode getting processed to increase its pledge doesn't exist in the storage" ),
.into(),
},
)?; )?;
if mix_details.pending_changes.pledge_change.is_none() {
return Err(MixnetContractError::inconsistent_state(
"attempted to increase mixnode pledge while there are no associated pending changes",
));
}
let mut updated_bond = mix_details.bond_information.clone(); let mut updated_bond = mix_details.bond_information.clone();
let mut updated_rewarding = mix_details.rewarding_details; let mut updated_rewarding = mix_details.rewarding_details;
@@ -257,7 +265,10 @@ pub(crate) fn increase_pledge(
updated_bond.original_pledge.amount += increase.amount; updated_bond.original_pledge.amount += increase.amount;
updated_rewarding.increase_operator_uint128(increase.amount)?; updated_rewarding.increase_operator_uint128(increase.amount)?;
// update both, bond information and rewarding details let mut pending_changes = mix_details.pending_changes;
pending_changes.pledge_change = None;
// update all: bond information, rewarding details and pending pledge changes
mixnodes_storage::mixnode_bonds().replace( mixnodes_storage::mixnode_bonds().replace(
deps.storage, deps.storage,
mix_id, mix_id,
@@ -265,10 +276,70 @@ pub(crate) fn increase_pledge(
Some(&mix_details.bond_information), Some(&mix_details.bond_information),
)?; )?;
rewards_storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &updated_rewarding)?; rewards_storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &updated_rewarding)?;
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
Ok(Response::new().add_event(new_pledge_increase_event(created_at, mix_id, &increase))) Ok(Response::new().add_event(new_pledge_increase_event(created_at, mix_id, &increase)))
} }
pub(crate) fn decrease_pledge(
deps: DepsMut<'_>,
created_at: BlockHeight,
mix_id: MixId,
decrease_by: Coin,
) -> Result<Response, MixnetContractError> {
// the target node MUST exist - we have checked it at the time of putting this event onto the queue
// we have also verified there were no preceding unbond events
let mix_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
MixnetContractError::inconsistent_state(
"mixnode getting processed to increase its pledge doesn't exist in the storage",
),
)?;
if mix_details.pending_changes.pledge_change.is_none() {
return Err(MixnetContractError::inconsistent_state(
"attempted to decrease mixnode pledge while there are no associated pending changes",
));
}
let mut updated_bond = mix_details.bond_information.clone();
let mut updated_rewarding = mix_details.rewarding_details;
let mut pending_changes = mix_details.pending_changes;
pending_changes.pledge_change = None;
// SAFETY: the subtraction here can't overflow as before the event was pushed into the queue,
// we checked that the new value will be higher than minimum pledge (which is also strictly positive)
updated_bond.original_pledge.amount -= decrease_by.amount;
updated_rewarding.decrease_operator_uint128(decrease_by.amount)?;
let proxy = &mix_details.bond_information.proxy;
let owner = &mix_details.bond_information.owner;
// send the removed tokens back to the operator
let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![decrease_by.clone()]);
// update all: bond information, rewarding details and pending pledge changes
mixnodes_storage::mixnode_bonds().replace(
deps.storage,
mix_id,
Some(&updated_bond),
Some(&mix_details.bond_information),
)?;
rewards_storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &updated_rewarding)?;
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
let response = Response::new()
.add_message(return_tokens)
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by))
.maybe_add_track_vesting_decrease_mixnode_pledge(
deps.storage,
proxy.clone(),
owner.clone().to_string(),
decrease_by,
)?;
Ok(response)
}
impl ContractExecutableEvent for PendingEpochEventData { impl ContractExecutableEvent for PendingEpochEventData {
fn execute(self, deps: DepsMut<'_>, env: &Env) -> Result<Response, MixnetContractError> { fn execute(self, deps: DepsMut<'_>, env: &Env) -> Result<Response, MixnetContractError> {
// note that the basic validation on all those events was already performed before // note that the basic validation on all those events was already performed before
@@ -288,6 +359,10 @@ impl ContractExecutableEvent for PendingEpochEventData {
PendingEpochEventKind::PledgeMore { mix_id, amount } => { PendingEpochEventKind::PledgeMore { mix_id, amount } => {
increase_pledge(deps, self.created_at, mix_id, amount) increase_pledge(deps, self.created_at, mix_id, amount)
} }
PendingEpochEventKind::DecreasePledge {
mix_id,
decrease_by,
} => decrease_pledge(deps, self.created_at, mix_id, decrease_by),
PendingEpochEventKind::UnbondMixnode { mix_id } => { PendingEpochEventKind::UnbondMixnode { mix_id } => {
unbond_mixnode(deps, env, self.created_at, mix_id) unbond_mixnode(deps, env, self.created_at, mix_id)
} }
@@ -397,26 +472,33 @@ impl ContractExecutableEvent for PendingIntervalEventData {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use std::time::Duration;
use cosmwasm_std::Decimal;
use mixnet_contract_common::Percent;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use crate::support::tests::test_helpers; use crate::support::tests::test_helpers;
use crate::support::tests::test_helpers::{assert_decimals, TestSetup}; use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
use cosmwasm_std::Decimal;
use mixnet_contract_common::Percent; use super::*;
use std::time::Duration;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
// note that authorization and basic validation has already been performed for all of those // note that authorization and basic validation has already been performed for all of those
// before being pushed onto the event queues // before being pushed onto the event queues
#[cfg(test)] #[cfg(test)]
mod delegating { mod delegating {
use super::*; use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, to_binary, CosmosMsg, Decimal, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::mixnodes::transactions::try_remove_mixnode; use crate::mixnodes::transactions::try_remove_mixnode;
use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg; use crate::support::tests::test_helpers::get_bank_send_msg;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, to_binary, CosmosMsg, Decimal, WasmMsg}; use super::*;
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test] #[test]
fn returns_the_tokens_if_mixnode_has_unbonded() { fn returns_the_tokens_if_mixnode_has_unbonded() {
@@ -829,7 +911,7 @@ mod tests {
res_other_proxy, res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract { MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy, received: dummy_proxy,
vesting_contract vesting_contract,
} }
); );
} }
@@ -837,11 +919,14 @@ mod tests {
#[cfg(test)] #[cfg(test)]
mod undelegating { mod undelegating {
use super::*; use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg; use crate::support::tests::test_helpers::get_bank_send_msg;
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; use super::*;
#[test] #[test]
fn doesnt_return_any_tokens_if_it_doesnt_exist() { fn doesnt_return_any_tokens_if_it_doesnt_exist() {
@@ -1021,7 +1106,7 @@ mod tests {
res_other_proxy, res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract { MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy, received: dummy_proxy,
vesting_contract vesting_contract,
} }
); );
} }
@@ -1029,13 +1114,17 @@ mod tests {
#[cfg(test)] #[cfg(test)]
mod mixnode_unbonding { mod mixnode_unbonding {
use super::*; use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::mixnodes::storage as mixnodes_storage; use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::transactions::{_try_decrease_pledge, _try_increase_pledge};
use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg; use crate::support::tests::test_helpers::get_bank_send_msg;
use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
use mixnet_contract_common::mixnode::UnbondedMixnode; use super::*;
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test] #[test]
fn returns_hard_error_if_mixnode_doesnt_exist() { fn returns_hard_error_if_mixnode_doesnt_exist() {
@@ -1050,6 +1139,71 @@ mod tests {
)); ));
} }
#[test]
fn returns_hard_error_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
let env = test.env();
let change = test.coins(1234);
// increase
let owner = "mix-owner1";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
_try_increase_pledge(
test.deps_mut(),
env.clone(),
change.clone(),
Addr::unchecked(owner),
None,
)
.unwrap();
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
// decrease
let owner = "mix-owner2";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
_try_decrease_pledge(
test.deps_mut(),
env.clone(),
change[0].clone(),
Addr::unchecked(owner),
None,
)
.unwrap();
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
// artificial
let owner = "mix-owner3";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
let changes = PendingMixNodeChanges {
pledge_change: Some(1234),
};
mixnodes_storage::PENDING_MIXNODE_CHANGES
.save(test.deps_mut().storage, mix_id, &changes)
.unwrap();
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
}
#[test] #[test]
fn returns_original_pledge_alongside_any_earned_rewards() { fn returns_original_pledge_alongside_any_earned_rewards() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
@@ -1178,7 +1332,7 @@ mod tests {
res_other_proxy, res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract { MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy, received: dummy_proxy,
vesting_contract vesting_contract,
} }
); );
} }
@@ -1186,10 +1340,12 @@ mod tests {
#[cfg(test)] #[cfg(test)]
mod increasing_pledge { mod increasing_pledge {
use super::*;
use cosmwasm_std::Uint128; use cosmwasm_std::Uint128;
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
#[test] #[test]
fn returns_hard_error_if_mixnode_doesnt_exist() { fn returns_hard_error_if_mixnode_doesnt_exist() {
// this should have never happened so hard error MUST be thrown here // this should have never happened so hard error MUST be thrown here
@@ -1203,10 +1359,27 @@ mod tests {
)); ));
} }
#[test]
fn returns_hard_error_if_there_are_no_pending_pledge_changes() {
let mut test = TestSetup::new();
let change = test.coin(1234);
let owner = "mix-owner";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
let res = increase_pledge(test.deps_mut(), 123, mix_id, change);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
}
#[test] #[test]
fn updates_stored_bond_information_and_rewarding_details() { fn updates_stored_bond_information_and_rewarding_details() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None); let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.set_pending_pledge_change(mix_id, None);
let old_details = get_mixnode_details_by_id(test.deps().storage, mix_id) let old_details = get_mixnode_details_by_id(test.deps().storage, mix_id)
.unwrap() .unwrap()
@@ -1239,6 +1412,8 @@ mod tests {
let pledge3 = Uint128::new(200_000_000); let pledge3 = Uint128::new(200_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1)); let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
let increase = test.coin(pledge2.u128()); let increase = test.coin(pledge2.u128());
increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap(); increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap();
@@ -1274,6 +1449,7 @@ mod tests {
let pledge2 = Uint128::new(50_000_000_000); let pledge2 = Uint128::new(50_000_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1)); let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge); test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge); test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
@@ -1346,6 +1522,7 @@ mod tests {
let pledge2 = Uint128::new(50_000_000_000); let pledge2 = Uint128::new(50_000_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1)); let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge); test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge); test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
@@ -1420,6 +1597,373 @@ mod tests {
assert_eq!(dist1, dist2) assert_eq!(dist1, dist2)
} }
} }
#[test]
fn updates_the_pending_pledge_changes_field() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.set_pending_pledge_change(mix_id, None);
let amount = test.coin(12345);
increase_pledge(test.deps_mut(), 123, mix_id, amount).unwrap();
let pending = mixnodes_storage::PENDING_MIXNODE_CHANGES
.load(test.deps().storage, mix_id)
.unwrap();
assert!(pending.pledge_change.is_none())
}
}
#[cfg(test)]
mod decreasing_pledge {
use cosmwasm_std::{to_binary, BankMsg, CosmosMsg, Uint128, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
#[test]
fn returns_hard_error_if_mixnode_doesnt_exist() {
// this should have never happened so hard error MUST be thrown here
let mut test = TestSetup::new();
let amount = test.coin(123);
let res = decrease_pledge(test.deps_mut(), 123, 1, amount);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
}
#[test]
fn returns_hard_error_if_there_are_no_pending_pledge_changes() {
let mut test = TestSetup::new();
let change = test.coin(1234);
let owner = "mix-owner";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
let res = decrease_pledge(test.deps_mut(), 123, mix_id, change);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
}
#[test]
fn updates_stored_bond_information_and_rewarding_details() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.set_pending_pledge_change(mix_id, None);
let old_details = get_mixnode_details_by_id(test.deps().storage, mix_id)
.unwrap()
.unwrap();
let amount = test.coin(12345);
decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
let updated_details = get_mixnode_details_by_id(test.deps().storage, mix_id)
.unwrap()
.unwrap();
assert_eq!(
updated_details.bond_information.original_pledge.amount,
old_details.bond_information.original_pledge.amount - amount.amount
);
assert_eq!(
updated_details.rewarding_details.operator,
old_details.rewarding_details.operator
- Decimal::from_atomics(amount.amount, 0).unwrap()
);
}
#[test]
fn returns_tokens_back_to_the_owner() {
let mut test = TestSetup::new();
let owner = "mix-owner";
let mix_id = test.add_dummy_mixnode(owner, None);
test.set_pending_pledge_change(mix_id, None);
let amount = test.coin(12345);
let res = decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
assert_eq!(res.messages.len(), 1);
assert_eq!(
res.messages[0].msg,
CosmosMsg::Bank(BankMsg::Send {
to_address: owner.to_string(),
amount: vec![amount],
})
)
}
#[test]
fn returns_tokens_back_to_the_proxy_if_bonded_with_vesting() {
let mut test = TestSetup::new();
let owner = "mix-owner";
let mix_id = test.add_dummy_mixnode_with_legal_proxy(owner, None);
test.set_pending_pledge_change(mix_id, None);
let vesting_contract = test.vesting_contract();
let amount = test.coin(12345);
let res = decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
assert_eq!(res.messages.len(), 2);
assert_eq!(
res.messages[0].msg,
CosmosMsg::Bank(BankMsg::Send {
to_address: vesting_contract.to_string(),
amount: vec![amount],
})
)
}
#[test]
fn attaches_vesting_track_message() {
let mut test = TestSetup::new();
let mix_id_no_proxy = test.add_dummy_mixnode("mix-owner1", None);
test.set_pending_pledge_change(mix_id_no_proxy, None);
let mix_id_proxy = test.add_dummy_mixnode_with_legal_proxy("mix-owner2", None);
test.set_pending_pledge_change(mix_id_proxy, None);
let vesting_contract = test.vesting_contract();
let amount = test.coin(12345);
let res_no_proxy =
decrease_pledge(test.deps_mut(), 123, mix_id_no_proxy, amount.clone()).unwrap();
// nothing was attached (apart from bank message tested in `returns_tokens_back_to_the_owner`)
// because it wasn't done with proxy!
assert_eq!(res_no_proxy.messages.len(), 1);
let res_proxy =
decrease_pledge(test.deps_mut(), 123, mix_id_proxy, amount.clone()).unwrap();
assert_eq!(res_proxy.messages.len(), 2);
assert_eq!(
res_proxy.messages[1].msg,
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: vesting_contract.to_string(),
msg: to_binary(&VestingContractExecuteMsg::TrackDecreasePledge {
owner: "mix-owner2".to_string(),
amount,
})
.unwrap(),
funds: vec![],
})
);
}
#[test]
fn without_any_events_in_between_is_equivalent_to_pledging_the_same_amount_immediately() {
let mut test = TestSetup::new();
let pledge1 = Uint128::new(200_000_000);
let pledge_change = Uint128::new(50_000_000);
let pledge3 = Uint128::new(150_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
let decrease = test.coin(pledge_change.u128());
decrease_pledge(test.deps_mut(), 123, mix_id_repledge, decrease).unwrap();
let mix_id_full_pledge = test.add_dummy_mixnode("mix-owner2", Some(pledge3));
test.add_immediate_delegation("alice", 123_456_789u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000u128, mix_id_repledge);
test.add_immediate_delegation("carol", 111_111_111u128, mix_id_repledge);
test.add_immediate_delegation("alice", 123_456_789u128, mix_id_full_pledge);
test.add_immediate_delegation("bob", 500_000_000u128, mix_id_full_pledge);
test.add_immediate_delegation("carol", 111_111_111u128, mix_id_full_pledge);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]);
let dist1 = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
let dist2 = test.reward_with_distribution_with_state_bypass(
mix_id_full_pledge,
test_helpers::performance(100.0),
);
assert_eq!(dist1, dist2)
}
#[test]
fn correctly_decreases_future_rewards() {
let mut test = TestSetup::new();
let pledge1 = Uint128::new(200_000_000_000);
let pledge_change = Uint128::new(50_000_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge]);
let dist = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
let decrease = test.coin(pledge_change.u128());
decrease_pledge(test.deps_mut(), 123, mix_id_repledge, decrease).unwrap();
let pledge3 = Uint128::new(150_000_000_000) + truncate_reward_amount(dist.operator);
let mix_id_full_pledge = test.add_dummy_mixnode("mix-owner2", Some(pledge3));
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_full_pledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_full_pledge);
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_full_pledge);
let lost_operator = dist.operator
- Decimal::from_atomics(truncate_reward_amount(dist.operator), 0).unwrap();
let lost_delegates = dist.delegates
- Decimal::from_atomics(truncate_reward_amount(dist.delegates), 0).unwrap();
// add the tiny bit of lost precision manually
let mut mix_rewarding_full = test.mix_rewarding(mix_id_full_pledge);
mix_rewarding_full.delegates += lost_delegates;
mix_rewarding_full.operator += lost_operator;
rewards_storage::MIXNODE_REWARDING
.save(
test.deps_mut().storage,
mix_id_full_pledge,
&mix_rewarding_full,
)
.unwrap();
test.add_immediate_delegation(
"dave",
truncate_reward_amount(dist.delegates).u128(),
mix_id_full_pledge,
);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]);
// go through few epochs of rewarding
for _ in 0..500 {
test.skip_to_next_epoch_end();
let dist1 = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
let dist2 = test.reward_with_distribution_with_state_bypass(
mix_id_full_pledge,
test_helpers::performance(100.0),
);
assert_eq!(dist1, dist2)
}
}
#[test]
fn correctly_decreases_future_rewards_with_more_passed_epochs() {
let mut test = TestSetup::new();
let pledge1 = Uint128::new(200_000_000_000);
let pledge_change = Uint128::new(50_000_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge]);
let mut cumulative_op_reward = Decimal::zero();
let mut cumulative_del_reward = Decimal::zero();
// go few epochs of rewarding before decreasing pledge
for _ in 0..500 {
test.skip_to_next_epoch_end();
let dist = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
cumulative_op_reward += dist.operator;
cumulative_del_reward += dist.delegates;
}
let decrease = test.coin(pledge_change.u128());
decrease_pledge(test.deps_mut(), 123, mix_id_repledge, decrease).unwrap();
let pledge3 =
Uint128::new(150_000_000_000) + truncate_reward_amount(cumulative_op_reward);
let mix_id_full_pledge = test.add_dummy_mixnode("mix-owner2", Some(pledge3));
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_full_pledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_full_pledge);
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_full_pledge);
let lost_operator = cumulative_op_reward
- Decimal::from_atomics(truncate_reward_amount(cumulative_op_reward), 0).unwrap();
let lost_delegates = cumulative_del_reward
- Decimal::from_atomics(truncate_reward_amount(cumulative_del_reward), 0).unwrap();
// add the tiny bit of lost precision manually
let mut mix_rewarding_full = test.mix_rewarding(mix_id_full_pledge);
mix_rewarding_full.delegates += lost_delegates;
mix_rewarding_full.operator += lost_operator;
rewards_storage::MIXNODE_REWARDING
.save(
test.deps_mut().storage,
mix_id_full_pledge,
&mix_rewarding_full,
)
.unwrap();
test.add_immediate_delegation(
"dave",
truncate_reward_amount(cumulative_del_reward).u128(),
mix_id_full_pledge,
);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]);
// go through few more epochs of rewarding
for _ in 0..500 {
test.skip_to_next_epoch_end();
let dist1 = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
let dist2 = test.reward_with_distribution_with_state_bypass(
mix_id_full_pledge,
test_helpers::performance(100.0),
);
assert_eq!(dist1, dist2)
}
}
#[test]
fn updates_the_pending_pledge_changes_field() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.set_pending_pledge_change(mix_id, None);
let amount = test.coin(12345);
decrease_pledge(test.deps_mut(), 123, mix_id, amount).unwrap();
let pending = mixnodes_storage::PENDING_MIXNODE_CHANGES
.load(test.deps().storage, mix_id)
.unwrap();
assert!(pending.pledge_change.is_none())
}
} }
#[test] #[test]
@@ -1439,11 +1983,14 @@ mod tests {
#[cfg(test)] #[cfg(test)]
mod changing_mix_cost_params { mod changing_mix_cost_params {
use super::*;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use cosmwasm_std::coin; use cosmwasm_std::coin;
use mixnet_contract_common::Percent; use mixnet_contract_common::Percent;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use super::*;
#[test] #[test]
fn doesnt_do_anything_if_mixnode_has_unbonded() { fn doesnt_do_anything_if_mixnode_has_unbonded() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
@@ -1480,7 +2027,7 @@ mod tests {
Response::new().add_event(new_mixnode_cost_params_update_event( Response::new().add_event(new_mixnode_cost_params_update_event(
123, 123,
mix_id, mix_id,
&new_params &new_params,
)) ))
) )
); );
+100 -2
View File
@@ -13,8 +13,8 @@ use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::pending_events::{PendingEpochEvent, PendingIntervalEvent}; use mixnet_contract_common::pending_events::{PendingEpochEvent, PendingIntervalEvent};
use mixnet_contract_common::{ use mixnet_contract_common::{
CurrentIntervalResponse, EpochEventId, EpochStatus, IntervalEventId, MixId, CurrentIntervalResponse, EpochEventId, EpochStatus, IntervalEventId, MixId,
NumberOfPendingEventsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse, NumberOfPendingEventsResponse, PagedRewardedSetResponse, PendingEpochEventResponse,
PendingIntervalEventsResponse, PendingEpochEventsResponse, PendingIntervalEventResponse, PendingIntervalEventsResponse,
}; };
pub fn query_epoch_status(deps: Deps<'_>) -> StdResult<EpochStatus> { pub fn query_epoch_status(deps: Deps<'_>) -> StdResult<EpochStatus> {
@@ -112,6 +112,22 @@ pub fn query_pending_interval_events_paged(
}) })
} }
pub fn query_pending_epoch_event(
deps: Deps<'_>,
event_id: EpochEventId,
) -> Result<PendingEpochEventResponse, MixnetContractError> {
let event = storage::PENDING_EPOCH_EVENTS.may_load(deps.storage, event_id)?;
Ok(PendingEpochEventResponse { event_id, event })
}
pub fn query_pending_interval_event(
deps: Deps<'_>,
event_id: IntervalEventId,
) -> Result<PendingIntervalEventResponse, MixnetContractError> {
let event = storage::PENDING_INTERVAL_EVENTS.may_load(deps.storage, event_id)?;
Ok(PendingIntervalEventResponse { event_id, event })
}
pub fn query_number_of_pending_events( pub fn query_number_of_pending_events(
deps: Deps<'_>, deps: Deps<'_>,
) -> Result<NumberOfPendingEventsResponse, MixnetContractError> { ) -> Result<NumberOfPendingEventsResponse, MixnetContractError> {
@@ -540,6 +556,88 @@ mod tests {
} }
} }
#[test]
fn query_for_pending_epoch_event() {
let mut test = TestSetup::new();
// it doesn't exist
let expected = PendingEpochEventResponse {
event_id: 123,
event: None,
};
assert_eq!(
expected,
query_pending_epoch_event(test.deps(), 123).unwrap()
);
// it exists
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let env = test.env();
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action.clone()).unwrap();
let expected = PendingEpochEventResponse {
event_id: 1,
event: Some(dummy_action.attach_source_height(env.block.height)),
};
assert_eq!(expected, query_pending_epoch_event(test.deps(), 1).unwrap());
// it no longer exist (but used to)
test.execute_all_pending_events();
let expected = PendingEpochEventResponse {
event_id: 1,
event: None,
};
assert_eq!(expected, query_pending_epoch_event(test.deps(), 1).unwrap());
}
#[test]
fn query_for_pending_interval_event() {
let mut test = TestSetup::new();
// it doesn't exist
let expected = PendingIntervalEventResponse {
event_id: 123,
event: None,
};
assert_eq!(
expected,
query_pending_interval_event(test.deps(), 123).unwrap()
);
// it exists
let dummy_action = PendingIntervalEventKind::ChangeMixCostParams {
mix_id: test.rng.next_u32(),
new_costs: fixtures::mix_node_cost_params_fixture(),
};
let env = test.env();
storage::push_new_interval_event(test.deps_mut().storage, &env, dummy_action.clone())
.unwrap();
let expected = PendingIntervalEventResponse {
event_id: 1,
event: Some(dummy_action.attach_source_height(env.block.height)),
};
assert_eq!(
expected,
query_pending_interval_event(test.deps(), 1).unwrap()
);
// it no longer exist (but used to)
test.execute_all_pending_events();
let expected = PendingIntervalEventResponse {
event_id: 1,
event: None,
};
assert_eq!(
expected,
query_pending_interval_event(test.deps(), 1).unwrap()
);
}
#[test] #[test]
fn querying_for_number_of_pending_events() { fn querying_for_number_of_pending_events() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
+67 -4
View File
@@ -81,7 +81,7 @@ pub(crate) fn push_new_epoch_event(
storage: &mut dyn Storage, storage: &mut dyn Storage,
env: &Env, env: &Env,
event: PendingEpochEventKind, event: PendingEpochEventKind,
) -> StdResult<()> { ) -> StdResult<EpochEventId> {
// not included in non-test code as it messes with our return types as we expected `StdResult` // not included in non-test code as it messes with our return types as we expected `StdResult`
// from all storage-related operations. // from all storage-related operations.
// However, the callers MUST HAVE ensured the below invariant // However, the callers MUST HAVE ensured the below invariant
@@ -90,14 +90,15 @@ pub(crate) fn push_new_epoch_event(
let event_id = next_epoch_event_id_counter(storage)?; let event_id = next_epoch_event_id_counter(storage)?;
let event_data = event.attach_source_height(env.block.height); let event_data = event.attach_source_height(env.block.height);
PENDING_EPOCH_EVENTS.save(storage, event_id, &event_data) PENDING_EPOCH_EVENTS.save(storage, event_id, &event_data)?;
Ok(event_id)
} }
pub(crate) fn push_new_interval_event( pub(crate) fn push_new_interval_event(
storage: &mut dyn Storage, storage: &mut dyn Storage,
env: &Env, env: &Env,
event: PendingIntervalEventKind, event: PendingIntervalEventKind,
) -> StdResult<()> { ) -> StdResult<IntervalEventId> {
// not included in non-test code as it messes with our return types as we expected `StdResult` // not included in non-test code as it messes with our return types as we expected `StdResult`
// from all storage-related operations. // from all storage-related operations.
// However, the callers MUST HAVE ensured the below invariant // However, the callers MUST HAVE ensured the below invariant
@@ -106,7 +107,8 @@ pub(crate) fn push_new_interval_event(
let event_id = next_interval_event_id_counter(storage)?; let event_id = next_interval_event_id_counter(storage)?;
let event_data = event.attach_source_height(env.block.height); let event_data = event.attach_source_height(env.block.height);
PENDING_INTERVAL_EVENTS.save(storage, event_id, &event_data) PENDING_INTERVAL_EVENTS.save(storage, event_id, &event_data)?;
Ok(event_id)
} }
pub(crate) fn update_rewarded_set( pub(crate) fn update_rewarded_set(
@@ -168,8 +170,11 @@ pub(crate) fn initialise_storage(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::support::tests::fixtures;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::testing::mock_dependencies; use cosmwasm_std::testing::mock_dependencies;
use cosmwasm_std::Order; use cosmwasm_std::Order;
use rand_chacha::rand_core::RngCore;
fn read_entire_set(storage: &mut dyn Storage) -> HashMap<MixId, RewardedSetNodeStatus> { fn read_entire_set(storage: &mut dyn Storage) -> HashMap<MixId, RewardedSetNodeStatus> {
REWARDED_SET REWARDED_SET
@@ -211,4 +216,62 @@ mod tests {
assert!(current_set.get(&7).is_none()); assert!(current_set.get(&7).is_none());
assert!(current_set.get(&1).is_none()); assert!(current_set.get(&1).is_none());
} }
#[test]
fn pushing_new_epoch_event_returns_its_id() {
let mut test = TestSetup::new();
let env = test.env();
for _ in 0..500 {
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
}
test.execute_all_pending_events();
for _ in 0..10 {
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
}
}
#[test]
fn pushing_new_interval_event_returns_its_id() {
let mut test = TestSetup::new();
let env = test.env();
for _ in 0..500 {
let dummy_action = PendingIntervalEventKind::ChangeMixCostParams {
mix_id: test.rng.next_u32(),
new_costs: fixtures::mix_node_cost_params_fixture(),
};
let id = push_new_interval_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = INTERVAL_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
}
test.execute_all_pending_events();
for _ in 0..10 {
let dummy_action = PendingIntervalEventKind::ChangeMixCostParams {
mix_id: test.rng.next_u32(),
new_costs: fixtures::mix_node_cost_params_fixture(),
};
let id = push_new_interval_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = INTERVAL_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
}
}
} }
+164 -71
View File
@@ -10,7 +10,7 @@ use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::{ use mixnet_contract_common::mixnode::{
MixNodeCostParams, MixNodeDetails, MixNodeRewarding, UnbondedMixnode, MixNodeCostParams, MixNodeDetails, MixNodeRewarding, UnbondedMixnode,
}; };
use mixnet_contract_common::{Layer, MixId, MixNode, MixNodeBond}; use mixnet_contract_common::{IdentityKey, Layer, MixId, MixNode, MixNodeBond};
pub(crate) fn must_get_mixnode_bond_by_owner( pub(crate) fn must_get_mixnode_bond_by_owner(
store: &dyn Storage, store: &dyn Storage,
@@ -26,18 +26,34 @@ pub(crate) fn must_get_mixnode_bond_by_owner(
.1) .1)
} }
pub(crate) fn attach_mix_details(
store: &dyn Storage,
bond_information: MixNodeBond,
) -> StdResult<MixNodeDetails> {
// if bond exists, rewarding details MUST also exist
let rewarding_details =
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.mix_id)?;
// since this `Map` hasn't existed when contract was instantiated, some mixnodes might not
// have an entry here. But that's fine, because it means they have no pending changes
// (if there were supposed to be any changes, they would have been added during migration)
let pending_changes = storage::PENDING_MIXNODE_CHANGES
.may_load(store, bond_information.mix_id)?
.unwrap_or_default();
Ok(MixNodeDetails::new(
bond_information,
rewarding_details,
pending_changes,
))
}
pub(crate) fn get_mixnode_details_by_id( pub(crate) fn get_mixnode_details_by_id(
store: &dyn Storage, store: &dyn Storage,
mix_id: MixId, mix_id: MixId,
) -> StdResult<Option<MixNodeDetails>> { ) -> StdResult<Option<MixNodeDetails>> {
if let Some(bond_information) = storage::mixnode_bonds().may_load(store, mix_id)? { if let Some(bond_information) = storage::mixnode_bonds().may_load(store, mix_id)? {
// if bond exists, rewarding details MUST also exist attach_mix_details(store, bond_information).map(Some)
let rewarding_details =
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.mix_id)?;
Ok(Some(MixNodeDetails::new(
bond_information,
rewarding_details,
)))
} else { } else {
Ok(None) Ok(None)
} }
@@ -53,13 +69,23 @@ pub(crate) fn get_mixnode_details_by_owner(
.item(store, address)? .item(store, address)?
.map(|record| record.1) .map(|record| record.1)
{ {
// if bond exists, rewarding details MUST also exist attach_mix_details(store, bond_information).map(Some)
let rewarding_details = } else {
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.mix_id)?; Ok(None)
Ok(Some(MixNodeDetails::new( }
bond_information, }
rewarding_details,
))) pub(crate) fn get_mixnode_details_by_identity(
store: &dyn Storage,
mix_identity: IdentityKey,
) -> StdResult<Option<MixNodeDetails>> {
if let Some(bond_information) = storage::mixnode_bonds()
.idx
.identity_key
.item(store, mix_identity)?
.map(|record| record.1)
{
attach_mix_details(store, bond_information).map(Some)
} else { } else {
Ok(None) Ok(None)
} }
@@ -153,7 +179,13 @@ pub(crate) mod tests {
mix_node_cost_params_fixture, mix_node_fixture, TEST_COIN_DENOM, mix_node_cost_params_fixture, mix_node_fixture, TEST_COIN_DENOM,
}; };
use crate::support::tests::test_helpers::TestSetup; use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::coin; use cosmwasm_std::{coin, Uint128};
pub(crate) struct DummyMixnode {
pub mix_id: MixId,
pub owner: Addr,
pub identity: IdentityKey,
}
pub(crate) const OWNER_EXISTS: &str = "mix-owner-existing"; pub(crate) const OWNER_EXISTS: &str = "mix-owner-existing";
pub(crate) const OWNER_UNBONDING: &str = "mix-owner-unbonding"; pub(crate) const OWNER_UNBONDING: &str = "mix-owner-unbonding";
@@ -161,33 +193,59 @@ pub(crate) mod tests {
pub(crate) const OWNER_UNBONDED_LEFTOVER: &str = "mix-owner-unbonded-leftover"; pub(crate) const OWNER_UNBONDED_LEFTOVER: &str = "mix-owner-unbonded-leftover";
// create a mixnode that is bonded, unbonded, in the process of unbonding and unbonded with leftover mix rewarding details // create a mixnode that is bonded, unbonded, in the process of unbonding and unbonded with leftover mix rewarding details
pub(crate) fn setup_mix_combinations(test: &mut TestSetup) -> Vec<MixId> { pub(crate) fn setup_mix_combinations(
let mix_id_exists = test.add_dummy_mixnode(OWNER_EXISTS, None); test: &mut TestSetup,
let mix_id_unbonding = test.add_dummy_mixnode(OWNER_UNBONDING, None); stake: Option<Uint128>,
let mix_id_unbonded = test.add_dummy_mixnode(OWNER_UNBONDED, None); ) -> Vec<DummyMixnode> {
let mix_id_unbonded_leftover = test.add_dummy_mixnode(OWNER_UNBONDED_LEFTOVER, None); let (mix_id, keypair) = test.add_dummy_mixnode_with_keypair(OWNER_EXISTS, stake);
let mix_exists = DummyMixnode {
mix_id,
owner: Addr::unchecked(OWNER_EXISTS),
identity: keypair.public_key().to_base58_string(),
};
let (mix_id, keypair) = test.add_dummy_mixnode_with_keypair(OWNER_UNBONDING, stake);
let mix_unbonding = DummyMixnode {
mix_id,
owner: Addr::unchecked(OWNER_UNBONDING),
identity: keypair.public_key().to_base58_string(),
};
let (mix_id, keypair) = test.add_dummy_mixnode_with_keypair(OWNER_UNBONDED, stake);
let mix_unbonded = DummyMixnode {
mix_id,
owner: Addr::unchecked(OWNER_UNBONDED),
identity: keypair.public_key().to_base58_string(),
};
let (mix_id, keypair) = test.add_dummy_mixnode_with_keypair(OWNER_UNBONDED_LEFTOVER, stake);
let mix_unbonded_leftover = DummyMixnode {
mix_id,
owner: Addr::unchecked(OWNER_UNBONDED_LEFTOVER),
identity: keypair.public_key().to_base58_string(),
};
// manually adjust delegation info as to indicate the rewarding information shouldnt get removed // manually adjust delegation info as to indicate the rewarding information shouldnt get removed
let mut rewarding_details = test.mix_rewarding(mix_id_unbonded_leftover); let mut rewarding_details = test.mix_rewarding(mix_unbonded_leftover.mix_id);
rewarding_details.delegates = Decimal::raw(12345); rewarding_details.delegates = Decimal::raw(12345);
rewarding_details.unique_delegations = 1; rewarding_details.unique_delegations = 1;
rewards_storage::MIXNODE_REWARDING rewards_storage::MIXNODE_REWARDING
.save( .save(
test.deps_mut().storage, test.deps_mut().storage,
mix_id_unbonded_leftover, mix_unbonded_leftover.mix_id,
&rewarding_details, &rewarding_details,
) )
.unwrap(); .unwrap();
test.immediately_unbond_mixnode(mix_id_unbonded); test.immediately_unbond_mixnode(mix_unbonded.mix_id);
test.immediately_unbond_mixnode(mix_id_unbonded_leftover); test.immediately_unbond_mixnode(mix_unbonded_leftover.mix_id);
test.start_unbonding_mixnode(mix_id_unbonding); test.start_unbonding_mixnode(mix_unbonding.mix_id);
vec![ vec![
mix_id_exists, mix_exists,
mix_id_unbonding, mix_unbonding,
mix_id_unbonded, mix_unbonded,
mix_id_unbonded_leftover, mix_unbonded_leftover,
] ]
} }
@@ -195,37 +253,35 @@ pub(crate) mod tests {
fn getting_mixnode_bond_by_owner() { fn getting_mixnode_bond_by_owner() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
let owner_exists = Addr::unchecked(OWNER_EXISTS); let nodes = setup_mix_combinations(&mut test, None);
let owner_unbonding = Addr::unchecked(OWNER_UNBONDING); let mix_exists = &nodes[0];
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED); let mix_unbonding = &nodes[1];
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER); let mix_unbonded = &nodes[2];
let mix_unbonded_leftover = &nodes[3];
let ids = setup_mix_combinations(&mut test);
let mix_id_exists = ids[0];
let mix_id_unbonding = ids[1];
// if this is a normally bonded mixnode, all should be fine // if this is a normally bonded mixnode, all should be fine
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_exists).unwrap(); let res = must_get_mixnode_bond_by_owner(test.deps().storage, &mix_exists.owner).unwrap();
assert_eq!(res.mix_id, mix_id_exists); assert_eq!(res.mix_id, mix_exists.mix_id);
// if node is in the process of unbonding, we still should be capable of retrieving its details // if node is in the process of unbonding, we still should be capable of retrieving its details
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_unbonding).unwrap(); let res =
assert_eq!(res.mix_id, mix_id_unbonding); must_get_mixnode_bond_by_owner(test.deps().storage, &mix_unbonding.owner).unwrap();
assert_eq!(res.mix_id, mix_unbonding.mix_id);
// but if node has unbonded, the information is purged and query fails // but if node has unbonded, the information is purged and query fails
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_unbonded); let res = must_get_mixnode_bond_by_owner(test.deps().storage, &mix_unbonded.owner);
assert_eq!( assert_eq!(
res, res,
Err(MixnetContractError::NoAssociatedMixNodeBond { Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: owner_unbonded owner: mix_unbonded.owner.clone()
}) })
); );
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_unbonded_leftover); let res = must_get_mixnode_bond_by_owner(test.deps().storage, &mix_unbonded_leftover.owner);
assert_eq!( assert_eq!(
res, res,
Err(MixnetContractError::NoAssociatedMixNodeBond { Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: owner_unbonded_leftover owner: mix_unbonded_leftover.owner.clone()
}) })
); );
} }
@@ -234,26 +290,27 @@ pub(crate) mod tests {
fn getting_mixnode_details_by_id() { fn getting_mixnode_details_by_id() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
let ids = setup_mix_combinations(&mut test); let nodes = setup_mix_combinations(&mut test, None);
let mix_id_exists = ids[0]; let mix_exists = &nodes[0];
let mix_id_unbonding = ids[1]; let mix_unbonding = &nodes[1];
let mix_id_unbonded = ids[2]; let mix_unbonded = &nodes[2];
let mix_id_unbonded_leftover = ids[3]; let mix_unbonded_leftover = &nodes[3];
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_exists) let res = get_mixnode_details_by_id(test.deps().storage, mix_exists.mix_id)
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(res.bond_information.mix_id, mix_id_exists); assert_eq!(res.bond_information.mix_id, mix_exists.mix_id);
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_unbonding) let res = get_mixnode_details_by_id(test.deps().storage, mix_unbonding.mix_id)
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(res.bond_information.mix_id, mix_id_unbonding); assert_eq!(res.bond_information.mix_id, mix_unbonding.mix_id);
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_unbonded).unwrap(); let res = get_mixnode_details_by_id(test.deps().storage, mix_unbonded.mix_id).unwrap();
assert!(res.is_none()); assert!(res.is_none());
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_unbonded_leftover).unwrap(); let res =
get_mixnode_details_by_id(test.deps().storage, mix_unbonded_leftover.mix_id).unwrap();
assert!(res.is_none()) assert!(res.is_none())
} }
@@ -261,33 +318,69 @@ pub(crate) mod tests {
fn getting_mixnode_details_by_owner() { fn getting_mixnode_details_by_owner() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
let owner_exists = Addr::unchecked(OWNER_EXISTS); let nodes = setup_mix_combinations(&mut test, None);
let owner_unbonding = Addr::unchecked(OWNER_UNBONDING); let mix_exists = &nodes[0];
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED); let mix_unbonding = &nodes[1];
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER); let mix_unbonded = &nodes[2];
let mix_unbonded_leftover = &nodes[3];
let ids = setup_mix_combinations(&mut test);
let mix_id_exists = ids[0];
let mix_id_unbonding = ids[1];
// if this is a normally bonded mixnode, all should be fine // if this is a normally bonded mixnode, all should be fine
let res = get_mixnode_details_by_owner(test.deps().storage, owner_exists) let res = get_mixnode_details_by_owner(test.deps().storage, mix_exists.owner.clone())
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(res.bond_information.mix_id, mix_id_exists); assert_eq!(res.bond_information.mix_id, mix_exists.mix_id);
// if node is in the process of unbonding, we still should be capable of retrieving its details // if node is in the process of unbonding, we still should be capable of retrieving its details
let res = get_mixnode_details_by_owner(test.deps().storage, owner_unbonding) let res = get_mixnode_details_by_owner(test.deps().storage, mix_unbonding.owner.clone())
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(res.bond_information.mix_id, mix_id_unbonding); assert_eq!(res.bond_information.mix_id, mix_unbonding.mix_id);
// but if node has unbonded, the information is purged and query fails // but if node has unbonded, the information is purged and query fails
let res = get_mixnode_details_by_owner(test.deps().storage, owner_unbonded).unwrap(); let res =
get_mixnode_details_by_owner(test.deps().storage, mix_unbonded.owner.clone()).unwrap();
assert!(res.is_none()); assert!(res.is_none());
let res = let res =
get_mixnode_details_by_owner(test.deps().storage, owner_unbonded_leftover).unwrap(); get_mixnode_details_by_owner(test.deps().storage, mix_unbonded_leftover.owner.clone())
.unwrap();
assert!(res.is_none());
}
#[test]
fn getting_mixnode_details_by_identity() {
let mut test = TestSetup::new();
let nodes = setup_mix_combinations(&mut test, None);
let mix_exists = &nodes[0];
let mix_unbonding = &nodes[1];
let mix_unbonded = &nodes[2];
let mix_unbonded_leftover = &nodes[3];
// if this is a normally bonded mixnode, all should be fine
let res = get_mixnode_details_by_identity(test.deps().storage, mix_exists.identity.clone())
.unwrap()
.unwrap();
assert_eq!(res.bond_information.mix_id, mix_exists.mix_id);
// if node is in the process of unbonding, we still should be capable of retrieving its details
let res =
get_mixnode_details_by_identity(test.deps().storage, mix_unbonding.identity.clone())
.unwrap()
.unwrap();
assert_eq!(res.bond_information.mix_id, mix_unbonding.mix_id);
// but if node has unbonded, the information is purged and query fails
let res =
get_mixnode_details_by_identity(test.deps().storage, mix_unbonded.identity.clone())
.unwrap();
assert!(res.is_none());
let res = get_mixnode_details_by_identity(
test.deps().storage,
mix_unbonded_leftover.identity.clone(),
)
.unwrap();
assert!(res.is_none()); assert!(res.is_none());
} }
+27 -59
View File
@@ -7,7 +7,10 @@ use crate::constants::{
MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT, MIXNODE_DETAILS_MAX_RETRIEVAL_LIMIT, MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT, MIXNODE_DETAILS_MAX_RETRIEVAL_LIMIT,
UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT, UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT, UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT, UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT,
}; };
use crate::mixnodes::helpers::{get_mixnode_details_by_id, get_mixnode_details_by_owner}; use crate::mixnodes::helpers::{
attach_mix_details, get_mixnode_details_by_id, get_mixnode_details_by_identity,
get_mixnode_details_by_owner,
};
use crate::rewards::storage as rewards_storage; use crate::rewards::storage as rewards_storage;
use cosmwasm_std::{Deps, Order, StdResult, Storage}; use cosmwasm_std::{Deps, Order, StdResult, Storage};
use cw_storage_plus::Bound; use cw_storage_plus::Bound;
@@ -46,18 +49,12 @@ pub fn query_mixnode_bonds_paged(
)) ))
} }
fn attach_rewarding_info( fn attach_node_details(
storage: &dyn Storage, storage: &dyn Storage,
read_bond: StdResult<(MixId, MixNodeBond)>, read_bond: StdResult<(MixId, MixNodeBond)>,
) -> StdResult<MixNodeDetails> { ) -> StdResult<MixNodeDetails> {
match read_bond { match read_bond {
Ok((_, bond)) => { Ok((_, bond)) => attach_mix_details(storage, bond),
// if we managed to read the bond we MUST be able to also read rewarding information.
// if we fail, this is a hard error and the query should definitely fail and we should investigate
// the reasons for that.
let mix_rewarding = rewards_storage::MIXNODE_REWARDING.load(storage, bond.mix_id)?;
Ok(MixNodeDetails::new(bond, mix_rewarding))
}
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
@@ -76,7 +73,7 @@ pub fn query_mixnodes_details_paged(
let nodes = storage::mixnode_bonds() let nodes = storage::mixnode_bonds()
.range(deps.storage, start, None, Order::Ascending) .range(deps.storage, start, None, Order::Ascending)
.take(limit) .take(limit)
.map(|res| attach_rewarding_info(deps.storage, res)) .map(|res| attach_node_details(deps.storage, res))
.collect::<StdResult<Vec<MixNodeDetails>>>()?; .collect::<StdResult<Vec<MixNodeDetails>>>()?;
let start_next_after = nodes.last().map(|details| details.mix_id()); let start_next_after = nodes.last().map(|details| details.mix_id());
@@ -192,26 +189,12 @@ pub fn query_mixnode_details(deps: Deps<'_>, mix_id: MixId) -> StdResult<Mixnode
}) })
} }
// TODO: change the return type to be consistent with the other details queries
pub fn query_mixnode_details_by_identity( pub fn query_mixnode_details_by_identity(
deps: Deps<'_>, deps: Deps<'_>,
mix_identity: IdentityKey, mix_identity: IdentityKey,
) -> StdResult<Option<MixNodeDetails>> { ) -> StdResult<Option<MixNodeDetails>> {
if let Some(bond_information) = storage::mixnode_bonds() get_mixnode_details_by_identity(deps.storage, mix_identity)
.idx
.identity_key
.item(deps.storage, mix_identity)?
.map(|record| record.1)
{
// if bond exists, rewarding details MUST also exist
let rewarding_details =
rewards_storage::MIXNODE_REWARDING.load(deps.storage, bond_information.mix_id)?;
Ok(Some(MixNodeDetails::new(
bond_information,
rewarding_details,
)))
} else {
Ok(None)
}
} }
pub fn query_mixnode_rewarding_details( pub fn query_mixnode_rewarding_details(
@@ -459,10 +442,10 @@ pub(crate) mod tests {
fn obeys_limits() { fn obeys_limits() {
let mut deps = test_helpers::init_contract(); let mut deps = test_helpers::init_contract();
let _env = mock_env(); let _env = mock_env();
let mut rng = test_helpers::test_rng(); let rng = test_helpers::test_rng();
let limit = 2; let limit = 2;
test_helpers::add_dummy_unbonded_mixnodes(&mut rng, deps.as_mut(), 1000); test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(limit)).unwrap(); let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(limit)).unwrap();
assert_eq!(limit, page1.nodes.len() as u32); assert_eq!(limit, page1.nodes.len() as u32);
} }
@@ -471,8 +454,8 @@ pub(crate) mod tests {
fn has_default_limit() { fn has_default_limit() {
let mut deps = test_helpers::init_contract(); let mut deps = test_helpers::init_contract();
let _env = mock_env(); let _env = mock_env();
let mut rng = test_helpers::test_rng(); let rng = test_helpers::test_rng();
test_helpers::add_dummy_unbonded_mixnodes(&mut rng, deps.as_mut(), 1000); test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
// query without explicitly setting a limit // query without explicitly setting a limit
let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, None).unwrap(); let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, None).unwrap();
@@ -487,8 +470,8 @@ pub(crate) mod tests {
fn has_max_limit() { fn has_max_limit() {
let mut deps = test_helpers::init_contract(); let mut deps = test_helpers::init_contract();
let _env = mock_env(); let _env = mock_env();
let mut rng = test_helpers::test_rng(); let rng = test_helpers::test_rng();
test_helpers::add_dummy_unbonded_mixnodes(&mut rng, deps.as_mut(), 1000); test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
// query with a crazily high limit in an attempt to use too many resources // query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000; let crazy_limit = 1000;
@@ -589,16 +572,11 @@ pub(crate) mod tests {
fn obeys_limits() { fn obeys_limits() {
let mut deps = test_helpers::init_contract(); let mut deps = test_helpers::init_contract();
let _env = mock_env(); let _env = mock_env();
let mut rng = test_helpers::test_rng(); let rng = test_helpers::test_rng();
let limit = 2; let limit = 2;
let owner = "owner"; let owner = "owner";
test_helpers::add_dummy_unbonded_mixnodes_with_owner( test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
&mut rng,
deps.as_mut(),
owner,
1000,
);
let page1 = query_unbonded_mixnodes_by_owner_paged( let page1 = query_unbonded_mixnodes_by_owner_paged(
deps.as_ref(), deps.as_ref(),
owner.into(), owner.into(),
@@ -613,15 +591,10 @@ pub(crate) mod tests {
fn has_default_limit() { fn has_default_limit() {
let mut deps = test_helpers::init_contract(); let mut deps = test_helpers::init_contract();
let _env = mock_env(); let _env = mock_env();
let mut rng = test_helpers::test_rng(); let rng = test_helpers::test_rng();
let owner = "owner"; let owner = "owner";
test_helpers::add_dummy_unbonded_mixnodes_with_owner( test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
&mut rng,
deps.as_mut(),
owner,
1000,
);
// query without explicitly setting a limit // query without explicitly setting a limit
let page1 = let page1 =
@@ -638,15 +611,10 @@ pub(crate) mod tests {
fn has_max_limit() { fn has_max_limit() {
let mut deps = test_helpers::init_contract(); let mut deps = test_helpers::init_contract();
let _env = mock_env(); let _env = mock_env();
let mut rng = test_helpers::test_rng(); let rng = test_helpers::test_rng();
let owner = "owner"; let owner = "owner";
test_helpers::add_dummy_unbonded_mixnodes_with_owner( test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
&mut rng,
deps.as_mut(),
owner,
1000,
);
// query with a crazily high limit in an attempt to use too many resources // query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000; let crazy_limit = 1000;
@@ -836,12 +804,12 @@ pub(crate) mod tests {
fn obeys_limits() { fn obeys_limits() {
let mut deps = test_helpers::init_contract(); let mut deps = test_helpers::init_contract();
let _env = mock_env(); let _env = mock_env();
let mut rng = test_helpers::test_rng(); let rng = test_helpers::test_rng();
let limit = 2; let limit = 2;
let identity = "foomp123"; let identity = "foomp123";
test_helpers::add_dummy_unbonded_mixnodes_with_identity( test_helpers::add_dummy_unbonded_mixnodes_with_identity(
&mut rng, rng,
deps.as_mut(), deps.as_mut(),
identity, identity,
1000, 1000,
@@ -860,10 +828,10 @@ pub(crate) mod tests {
fn has_default_limit() { fn has_default_limit() {
let mut deps = test_helpers::init_contract(); let mut deps = test_helpers::init_contract();
let _env = mock_env(); let _env = mock_env();
let mut rng = test_helpers::test_rng(); let rng = test_helpers::test_rng();
let identity = "foomp123"; let identity = "foomp123";
test_helpers::add_dummy_unbonded_mixnodes_with_identity( test_helpers::add_dummy_unbonded_mixnodes_with_identity(
&mut rng, rng,
deps.as_mut(), deps.as_mut(),
identity, identity,
1000, 1000,
@@ -888,10 +856,10 @@ pub(crate) mod tests {
fn has_max_limit() { fn has_max_limit() {
let mut deps = test_helpers::init_contract(); let mut deps = test_helpers::init_contract();
let _env = mock_env(); let _env = mock_env();
let mut rng = test_helpers::test_rng(); let rng = test_helpers::test_rng();
let identity = "foomp123"; let identity = "foomp123";
test_helpers::add_dummy_unbonded_mixnodes_with_identity( test_helpers::add_dummy_unbonded_mixnodes_with_identity(
&mut rng, rng,
deps.as_mut(), deps.as_mut(),
identity, identity,
1000, 1000,
+10 -9
View File
@@ -1,23 +1,27 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::constants::{ use crate::constants::{
LAYER_DISTRIBUTION_KEY, MIXNODES_IDENTITY_IDX_NAMESPACE, MIXNODES_OWNER_IDX_NAMESPACE, LAYER_DISTRIBUTION_KEY, MIXNODES_IDENTITY_IDX_NAMESPACE, MIXNODES_OWNER_IDX_NAMESPACE,
MIXNODES_PK_NAMESPACE, MIXNODES_SPHINX_IDX_NAMESPACE, NODE_ID_COUNTER_KEY, MIXNODES_PK_NAMESPACE, MIXNODES_SPHINX_IDX_NAMESPACE, NODE_ID_COUNTER_KEY,
UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE, UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE, PENDING_MIXNODE_CHANGES_NAMESPACE, UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE,
UNBONDED_MIXNODES_PK_NAMESPACE, UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE, UNBONDED_MIXNODES_PK_NAMESPACE,
}; };
use cosmwasm_std::{StdResult, Storage}; use cosmwasm_std::{StdResult, Storage};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, MultiIndex, UniqueIndex}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex, UniqueIndex};
use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::UnbondedMixnode; use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode};
use mixnet_contract_common::SphinxKey; use mixnet_contract_common::SphinxKey;
use mixnet_contract_common::{Addr, IdentityKey, Layer, LayerDistribution, MixId, MixNodeBond}; use mixnet_contract_common::{Addr, IdentityKey, Layer, LayerDistribution, MixId, MixNodeBond};
pub const LAYERS: Item<'_, LayerDistribution> = Item::new(LAYER_DISTRIBUTION_KEY);
pub const MIXNODE_ID_COUNTER: Item<MixId> = Item::new(NODE_ID_COUNTER_KEY);
pub const PENDING_MIXNODE_CHANGES: Map<MixId, PendingMixNodeChanges> =
Map::new(PENDING_MIXNODE_CHANGES_NAMESPACE);
// keeps track of `node_id -> IdentityKey, Owner, unbonding_height` so we'd known a bit more about past mixnodes // keeps track of `node_id -> IdentityKey, Owner, unbonding_height` so we'd known a bit more about past mixnodes
// if we ever decide it's too bloaty, we can deprecate it and start removing all data in // if we ever decide it's too bloaty, we can deprecate it and start removing all data in
// subsequent migrations // subsequent migrations
pub(crate) struct UnbondedMixnodeIndex<'a> { pub(crate) struct UnbondedMixnodeIndex<'a> {
pub(crate) owner: MultiIndex<'a, Addr, UnbondedMixnode, MixId>, pub(crate) owner: MultiIndex<'a, Addr, UnbondedMixnode, MixId>,
@@ -48,9 +52,6 @@ pub(crate) fn unbonded_mixnodes<'a>(
IndexedMap::new(UNBONDED_MIXNODES_PK_NAMESPACE, indexes) IndexedMap::new(UNBONDED_MIXNODES_PK_NAMESPACE, indexes)
} }
pub(crate) const LAYERS: Item<'_, LayerDistribution> = Item::new(LAYER_DISTRIBUTION_KEY);
pub const MIXNODE_ID_COUNTER: Item<MixId> = Item::new(NODE_ID_COUNTER_KEY);
pub(crate) struct MixnodeBondIndex<'a> { pub(crate) struct MixnodeBondIndex<'a> {
pub(crate) owner: UniqueIndex<'a, Addr, MixNodeBond>, pub(crate) owner: UniqueIndex<'a, Addr, MixNodeBond>,
+637 -60
View File
@@ -1,7 +1,19 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use super::storage; use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_mixnode_bonding_event, new_mixnode_config_update_event,
new_mixnode_pending_cost_params_update_event, new_pending_mixnode_unbonding_event,
new_pending_pledge_decrease_event, new_pending_pledge_increase_event,
};
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingIntervalEventKind};
use mixnet_contract_common::{Layer, MixId, MixNode};
use nym_contracts_common::signing::MessageSignature;
use crate::interval::storage as interval_storage; use crate::interval::storage as interval_storage;
use crate::interval::storage::push_new_interval_event; use crate::interval::storage::push_new_interval_event;
use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage;
@@ -13,19 +25,11 @@ use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature;
use crate::signing::storage as signing_storage; use crate::signing::storage as signing_storage;
use crate::support::helpers::{ use crate::support::helpers::{
ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond, ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge, ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract,
validate_pledge,
}; };
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError; use super::storage;
use mixnet_contract_common::events::{
new_mixnode_bonding_event, new_mixnode_config_update_event,
new_mixnode_pending_cost_params_update_event, new_pending_mixnode_unbonding_event,
new_pending_pledge_increase_event,
};
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingIntervalEventKind};
use mixnet_contract_common::{Layer, MixId, MixNode};
use nym_contracts_common::signing::MessageSignature;
pub(crate) fn update_mixnode_layer( pub(crate) fn update_mixnode_layer(
mix_id: MixId, mix_id: MixId,
@@ -195,6 +199,7 @@ pub fn _try_increase_pledge(
) -> Result<Response, MixnetContractError> { ) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())? let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?; .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
let mut pending_changes = mix_details.pending_changes;
let mix_id = mix_details.mix_id(); let mix_id = mix_details.mix_id();
// increasing pledge is only allowed if the epoch is currently not in the process of being advanced // increasing pledge is only allowed if the epoch is currently not in the process of being advanced
@@ -202,6 +207,7 @@ pub fn _try_increase_pledge(
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?; ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?; ensure_bonded(&mix_details.bond_information)?;
ensure_no_pending_pledge_changes(&pending_changes)?;
let rewarding_denom = rewarding_denom(deps.storage)?; let rewarding_denom = rewarding_denom(deps.storage)?;
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?; let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
@@ -213,7 +219,95 @@ pub fn _try_increase_pledge(
mix_id, mix_id,
amount: pledge_increase, amount: pledge_increase,
}; };
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?; let epoch_event_id = interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
pending_changes.pledge_change = Some(epoch_event_id);
storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
Ok(Response::new().add_event(cosmos_event))
}
pub fn try_decrease_pledge(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
decrease_by: Coin,
) -> Result<Response, MixnetContractError> {
_try_decrease_pledge(deps, env, decrease_by, info.sender, None)
}
pub fn try_decrease_pledge_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
decrease_by: Coin,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_decrease_pledge(deps, env, decrease_by, owner, Some(proxy))
}
pub fn _try_decrease_pledge(
deps: DepsMut<'_>,
env: Env,
decrease_by: Coin,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
let mut pending_changes = mix_details.pending_changes;
let mix_id = mix_details.mix_id();
// decreasing pledge is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?;
ensure_no_pending_pledge_changes(&pending_changes)?;
let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?;
// check that the denomination is correct
if decrease_by.denom != minimum_pledge.denom {
return Err(MixnetContractError::WrongDenom {
received: decrease_by.denom,
expected: minimum_pledge.denom,
});
}
// also check if the request contains non-zero amount
// (otherwise it's a no-op and we should we waste gas when resolving events?)
if decrease_by.amount.is_zero() {
return Err(MixnetContractError::ZeroCoinAmount);
}
// decreasing pledge can't result in the new pledge being lower than the minimum amount
let new_pledge_amount = mix_details
.original_pledge()
.amount
.saturating_sub(decrease_by.amount);
if new_pledge_amount < minimum_pledge.amount {
return Err(MixnetContractError::InvalidPledgeReduction {
current: mix_details.original_pledge().amount,
decrease_by: decrease_by.amount,
minimum: minimum_pledge.amount,
denom: minimum_pledge.denom,
});
}
let cosmos_event = new_pending_pledge_decrease_event(mix_id, &decrease_by);
// push the event to execute it at the end of the epoch
let epoch_event = PendingEpochEventKind::DecreasePledge {
mix_id,
decrease_by,
};
let epoch_event_id = interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
pending_changes.pledge_change = Some(epoch_event_id);
storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
Ok(Response::new().add_event(cosmos_event)) Ok(Response::new().add_event(cosmos_event))
} }
@@ -246,6 +340,9 @@ pub(crate) fn _try_remove_mixnode(
proxy: Option<Addr>, proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> { ) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?; let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
let pending_changes = storage::PENDING_MIXNODE_CHANGES
.may_load(deps.storage, existing_bond.mix_id)?
.unwrap_or_default();
// unbonding is only allowed if the epoch is currently not in the process of being advanced // unbonding is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?; ensure_epoch_in_progress_state(deps.storage)?;
@@ -254,6 +351,9 @@ pub(crate) fn _try_remove_mixnode(
ensure_proxy_match(&proxy, &existing_bond.proxy)?; ensure_proxy_match(&proxy, &existing_bond.proxy)?;
ensure_bonded(&existing_bond)?; ensure_bonded(&existing_bond)?;
// if there are any pending requests to change the pledge, wait for them to resolve before allowing the unbonding
ensure_no_pending_pledge_changes(&pending_changes)?;
// set `is_unbonding` field // set `is_unbonding` field
let mut updated_bond = existing_bond.clone(); let mut updated_bond = existing_bond.clone();
updated_bond.is_unbonding = true; updated_bond.is_unbonding = true;
@@ -392,18 +492,22 @@ pub(crate) fn _try_update_mixnode_cost_params(
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Order, StdResult, Uint128};
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{
EpochState, EpochStatus, ExecuteMsg, Layer, LayerDistribution, Percent,
};
use crate::contract::execute; use crate::contract::execute;
use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge; use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge;
use crate::mixnodes::helpers::get_mixnode_details_by_id; use crate::mixnodes::helpers::get_mixnode_details_by_id;
use crate::support::tests::fixtures::{good_mixnode_pledge, TEST_COIN_DENOM}; use crate::support::tests::fixtures::{good_mixnode_pledge, TEST_COIN_DENOM};
use crate::support::tests::test_helpers::TestSetup; use crate::support::tests::test_helpers::TestSetup;
use crate::support::tests::{fixtures, test_helpers}; use crate::support::tests::{fixtures, test_helpers};
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Order, StdResult, Uint128}; use super::*;
use mixnet_contract_common::{
EpochState, EpochStatus, ExecuteMsg, Layer, LayerDistribution, Percent,
};
#[test] #[test]
fn mixnode_add() { fn mixnode_add() {
@@ -531,7 +635,7 @@ pub mod tests {
let res = try_add_mixnode( let res = try_add_mixnode(
test.deps_mut(), test.deps_mut(),
env.clone(), env.clone(),
info.clone(), info,
mixnode.clone(), mixnode.clone(),
cost_params.clone(), cost_params.clone(),
signature.clone(), signature.clone(),
@@ -601,7 +705,7 @@ pub mod tests {
res, res,
MixnetContractError::SenderIsNotVestingContract { MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy, received: illegal_proxy,
vesting_contract vesting_contract,
} }
) )
} }
@@ -669,7 +773,7 @@ pub mod tests {
res, res,
Err(MixnetContractError::ProxyMismatch { Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(), existing: "None".to_string(),
incoming: vesting_contract.into_string() incoming: vesting_contract.into_string(),
}) })
); );
@@ -717,11 +821,66 @@ pub mod tests {
res, res,
MixnetContractError::SenderIsNotVestingContract { MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy, received: illegal_proxy,
vesting_contract vesting_contract,
} }
) )
} }
#[test]
fn mixnode_remove_is_not_allowed_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
let env = test.env();
// prior increase
let owner = "mix-owner1";
test.add_dummy_mixnode(owner, None);
let sender = mock_info(owner, &[test.coin(1000)]);
try_increase_pledge(test.deps_mut(), env.clone(), sender.clone()).unwrap();
let res = try_remove_mixnode(test.deps_mut(), env.clone(), sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1
})
);
// prior decrease
let owner = "mix-owner2";
test.add_dummy_mixnode(owner, Some(Uint128::new(10000000000)));
let sender = mock_info(owner, &[]);
let amount = test.coin(1000);
try_decrease_pledge(test.deps_mut(), env.clone(), sender, amount).unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_remove_mixnode(test.deps_mut(), env.clone(), sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 2
})
);
// artificial event
let owner = "mix-owner3";
let mix_id = test.add_dummy_mixnode(owner, None);
let pending_change = PendingMixNodeChanges {
pledge_change: Some(1234),
};
storage::PENDING_MIXNODE_CHANGES
.save(test.deps_mut().storage, mix_id, &pending_change)
.unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_remove_mixnode(test.deps_mut(), env, sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1234
})
);
}
#[test] #[test]
fn updating_mixnode_config() { fn updating_mixnode_config() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
@@ -760,7 +919,7 @@ pub mod tests {
res, res,
Err(MixnetContractError::ProxyMismatch { Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(), existing: "None".to_string(),
incoming: vesting_contract.into_string() incoming: vesting_contract.into_string(),
}) })
); );
// "normal" update succeeds // "normal" update succeeds
@@ -812,7 +971,7 @@ pub mod tests {
res, res,
MixnetContractError::SenderIsNotVestingContract { MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy, received: illegal_proxy,
vesting_contract vesting_contract,
} }
) )
} }
@@ -895,7 +1054,7 @@ pub mod tests {
res, res,
Err(MixnetContractError::ProxyMismatch { Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(), existing: "None".to_string(),
incoming: vesting_contract.into_string() incoming: vesting_contract.into_string(),
}) })
); );
// "normal" update succeeds // "normal" update succeeds
@@ -918,7 +1077,7 @@ pub mod tests {
assert_eq!( assert_eq!(
PendingIntervalEventKind::ChangeMixCostParams { PendingIntervalEventKind::ChangeMixCostParams {
mix_id, mix_id,
new_costs: update.clone() new_costs: update.clone(),
}, },
event.1.kind event.1.kind
); );
@@ -967,7 +1126,7 @@ pub mod tests {
res, res,
MixnetContractError::SenderIsNotVestingContract { MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy, received: illegal_proxy,
vesting_contract vesting_contract,
} }
) )
} }
@@ -1013,7 +1172,7 @@ pub mod tests {
info_alice, info_alice,
mixnode1, mixnode1,
cost_params.clone(), cost_params.clone(),
sig1 sig1,
) )
.is_ok()); .is_ok());
@@ -1025,12 +1184,15 @@ pub mod tests {
#[cfg(test)] #[cfg(test)]
mod increasing_mixnode_pledge { mod increasing_mixnode_pledge {
use super::*; use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{EpochState, EpochStatus};
use crate::mixnodes::helpers::tests::{ use crate::mixnodes::helpers::tests::{
setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING, setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
}; };
use crate::support::tests::test_helpers::TestSetup; use crate::support::tests::test_helpers::TestSetup;
use mixnet_contract_common::{EpochState, EpochStatus};
use super::*;
#[test] #[test]
fn cant_be_performed_if_epoch_transition_is_in_progress() { fn cant_be_performed_if_epoch_transition_is_in_progress() {
@@ -1108,7 +1270,7 @@ pub mod tests {
res, res,
Err(MixnetContractError::ProxyMismatch { Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(), existing: "None".to_string(),
incoming: "proxy".to_string() incoming: "proxy".to_string(),
}) })
); );
@@ -1123,7 +1285,7 @@ pub mod tests {
res, res,
Err(MixnetContractError::ProxyMismatch { Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(), existing: "proxy".to_string(),
incoming: "None".to_string() incoming: "None".to_string(),
}) })
); );
@@ -1138,7 +1300,7 @@ pub mod tests {
res, res,
Err(MixnetContractError::ProxyMismatch { Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(), existing: "proxy".to_string(),
incoming: "unrelated-proxy".to_string() incoming: "unrelated-proxy".to_string(),
}) })
) )
} }
@@ -1154,8 +1316,8 @@ pub mod tests {
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED); let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER); let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
let ids = setup_mix_combinations(&mut test); let ids = setup_mix_combinations(&mut test, None);
let mix_id_unbonding = ids[1]; let mix_id_unbonding = ids[1].mix_id;
let res = try_increase_pledge( let res = try_increase_pledge(
test.deps_mut(), test.deps_mut(),
@@ -1214,11 +1376,66 @@ pub mod tests {
res, res,
Err(MixnetContractError::InsufficientPledge { Err(MixnetContractError::InsufficientPledge {
received: test.coin(0), received: test.coin(0),
minimum: test.coin(1) minimum: test.coin(1),
}) })
) )
} }
#[test]
fn is_not_allowed_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
let env = test.env();
// prior increase
let owner = "mix-owner1";
test.add_dummy_mixnode(owner, None);
let sender = mock_info(owner, &[test.coin(1000)]);
try_increase_pledge(test.deps_mut(), env.clone(), sender.clone()).unwrap();
let res = try_increase_pledge(test.deps_mut(), env.clone(), sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1
})
);
// prior decrease
let owner = "mix-owner2";
test.add_dummy_mixnode(owner, Some(Uint128::new(10000000000)));
let sender = mock_info(owner, &[]);
let amount = test.coin(1000);
try_decrease_pledge(test.deps_mut(), env.clone(), sender, amount).unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_increase_pledge(test.deps_mut(), env.clone(), sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 2
})
);
// artificial event
let owner = "mix-owner3";
let mix_id = test.add_dummy_mixnode(owner, None);
let pending_change = PendingMixNodeChanges {
pledge_change: Some(1234),
};
storage::PENDING_MIXNODE_CHANGES
.save(test.deps_mut().storage, mix_id, &pending_change)
.unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_increase_pledge(test.deps_mut(), env, sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1234
})
);
}
#[test] #[test]
fn with_valid_information_creates_pending_event() { fn with_valid_information_creates_pending_event() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
@@ -1238,38 +1455,398 @@ pub mod tests {
events[0].kind, events[0].kind,
PendingEpochEventKind::PledgeMore { PendingEpochEventKind::PledgeMore {
mix_id, mix_id,
amount: test.coin(1000) amount: test.coin(1000),
} }
); );
} }
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let res = try_increase_pledge_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
} }
#[test] #[cfg(test)]
fn fails_for_illegal_proxy() { mod decreasing_mixnode_pledge {
let mut test = TestSetup::new(); use mixnet_contract_common::mixnode::PendingMixNodeChanges;
let env = test.env(); use mixnet_contract_common::{EpochState, EpochStatus};
let illegal_proxy = Addr::unchecked("not-vesting-contract"); use crate::mixnodes::helpers::tests::{
let vesting_contract = test.vesting_contract(); setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
};
use crate::support::tests::test_helpers::TestSetup;
let owner = "alice"; use super::*;
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone()); #[test]
fn cant_be_performed_if_epoch_transition_is_in_progress() {
let bad_states = vec![
EpochState::Rewarding {
last_rewarded: 0,
final_node_id: 0,
},
EpochState::ReconcilingEvents,
EpochState::AdvancingEpoch,
];
let res = try_increase_pledge_on_behalf( for bad_state in bad_states {
test.deps_mut(), let mut test = TestSetup::new();
env, let env = test.env();
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), let owner = "mix-owner";
owner.to_string(), let decrease = test.coin(1000);
)
.unwrap_err();
assert_eq!( test.add_dummy_mixnode(owner, Some(Uint128::new(100_000_000_000)));
res,
MixnetContractError::SenderIsNotVestingContract { let mut status = EpochStatus::new(test.rewarding_validator().sender);
received: illegal_proxy, status.state = bad_state;
vesting_contract interval_storage::save_current_epoch_status(test.deps_mut().storage, &status)
.unwrap();
let sender = mock_info(owner, &[]);
let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
assert!(matches!(
res,
Err(MixnetContractError::EpochAdvancementInProgress { .. })
));
} }
) }
#[test]
fn is_not_allowed_if_account_doesnt_own_mixnode() {
let mut test = TestSetup::new();
let env = test.env();
let sender = mock_info("not-mix-owner", &[]);
let decrease = test.coin(1000);
let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: Addr::unchecked("not-mix-owner")
})
)
}
#[test]
fn is_not_allowed_if_theres_proxy_mismatch() {
let mut test = TestSetup::new();
let env = test.env();
let owner_without_proxy = Addr::unchecked("no-proxy");
let owner_with_proxy = Addr::unchecked("with-proxy");
let proxy = Addr::unchecked("proxy");
let wrong_proxy = Addr::unchecked("unrelated-proxy");
// just to make sure that after decrease the value would still be above the minimum
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
test.add_dummy_mixnode(owner_without_proxy.as_str(), Some(stake));
test.add_dummy_mixnode_with_illegal_proxy(
owner_with_proxy.as_str(),
Some(stake),
proxy.clone(),
);
let res = _try_decrease_pledge(
test.deps_mut(),
env.clone(),
decrease.clone(),
owner_without_proxy.clone(),
Some(proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: "proxy".to_string(),
})
);
let res = _try_decrease_pledge(
test.deps_mut(),
env.clone(),
decrease.clone(),
owner_with_proxy.clone(),
None,
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "None".to_string(),
})
);
let res = _try_decrease_pledge(
test.deps_mut(),
env,
decrease,
owner_with_proxy.clone(),
Some(wrong_proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "unrelated-proxy".to_string(),
})
)
}
#[test]
fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
let mut test = TestSetup::new();
let env = test.env();
// just to make sure that after decrease the value would still be above the minimum
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
// TODO: I dislike this cross-test access, but it provides us with exactly what we need
// perhaps it should be refactored a bit?
let owner_unbonding = Addr::unchecked(OWNER_UNBONDING);
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
let ids = setup_mix_combinations(&mut test, Some(stake));
let mix_id_unbonding = ids[1].mix_id;
let res = try_decrease_pledge(
test.deps_mut(),
env.clone(),
mock_info(owner_unbonding.as_str(), &[]),
decrease.clone(),
);
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding {
mix_id: mix_id_unbonding
})
);
// if the nodes are gone we treat them as tey never existed in the first place
// (regardless of if there's some leftover data)
let res = try_decrease_pledge(
test.deps_mut(),
env.clone(),
mock_info(owner_unbonded_leftover.as_str(), &[]),
decrease.clone(),
);
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: owner_unbonded_leftover
})
);
let res = try_decrease_pledge(
test.deps_mut(),
env,
mock_info(owner_unbonded.as_str(), &[]),
decrease,
);
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: owner_unbonded
})
)
}
#[test]
fn is_not_allowed_if_it_would_result_going_below_minimum_pledge() {
let mut test = TestSetup::new();
let env = test.env();
let owner = "mix-owner";
let minimum_pledge = minimum_mixnode_pledge(test.deps().storage).unwrap();
let pledge_amount = minimum_pledge.amount + Uint128::new(100);
let pledged = test.coin(pledge_amount.u128());
test.add_dummy_mixnode(owner, Some(pledge_amount));
let invalid_decrease = test.coin(150);
let valid_decrease = test.coin(50);
let sender = mock_info(owner, &[]);
let res = try_decrease_pledge(
test.deps_mut(),
env.clone(),
sender.clone(),
invalid_decrease.clone(),
);
assert_eq!(
res,
Err(MixnetContractError::InvalidPledgeReduction {
current: pledged.amount,
decrease_by: invalid_decrease.amount,
minimum: minimum_pledge.amount,
denom: minimum_pledge.denom,
})
);
let res = try_decrease_pledge(test.deps_mut(), env, sender, valid_decrease);
assert!(res.is_ok())
}
#[test]
fn provided_amount_has_to_be_nonzero() {
let mut test = TestSetup::new();
let env = test.env();
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(0);
let owner = "mix-owner";
test.add_dummy_mixnode(owner, Some(stake));
let sender = mock_info(owner, &[]);
let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
assert_eq!(res, Err(MixnetContractError::ZeroCoinAmount))
}
#[test]
fn is_not_allowed_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
let env = test.env();
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
// prior increase
let owner = "mix-owner1";
test.add_dummy_mixnode(owner, Some(stake));
let sender = mock_info(owner, &[test.coin(1000)]);
try_increase_pledge(test.deps_mut(), env.clone(), sender.clone()).unwrap();
let res = try_decrease_pledge(test.deps_mut(), env.clone(), sender, decrease.clone());
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1
})
);
// prior decrease
let owner = "mix-owner2";
test.add_dummy_mixnode(owner, Some(stake));
let sender = mock_info(owner, &[]);
let amount = test.coin(1000);
try_decrease_pledge(test.deps_mut(), env.clone(), sender, amount).unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_decrease_pledge(test.deps_mut(), env.clone(), sender, decrease.clone());
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 2
})
);
// artificial event
let owner = "mix-owner3";
let mix_id = test.add_dummy_mixnode(owner, Some(stake));
let pending_change = PendingMixNodeChanges {
pledge_change: Some(1234),
};
storage::PENDING_MIXNODE_CHANGES
.save(test.deps_mut().storage, mix_id, &pending_change)
.unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1234
})
);
}
#[test]
fn with_valid_information_creates_pending_event() {
let mut test = TestSetup::new();
let env = test.env();
// just to make sure that after decrease the value would still be above the minimum
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
let owner = "mix-owner";
let mix_id = test.add_dummy_mixnode(owner, Some(stake));
let events = test.pending_epoch_events();
assert!(events.is_empty());
let sender = mock_info(owner, &[]);
try_decrease_pledge(test.deps_mut(), env, sender, decrease.clone()).unwrap();
let events = test.pending_epoch_events();
assert_eq!(
events[0].kind,
PendingEpochEventKind::DecreasePledge {
mix_id,
decrease_by: decrease,
}
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(stake), illegal_proxy.clone());
let res = try_decrease_pledge_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
decrease,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
} }
} }
+40
View File
@@ -1,2 +1,42 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::interval::storage as interval_storage;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::DepsMut;
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::PendingEpochEventKind;
use std::collections::BTreeMap;
pub fn insert_pending_pledge_changes(deps: DepsMut<'_>) -> Result<(), MixnetContractError> {
let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(deps.storage)?;
let last_inserted = interval_storage::EPOCH_EVENT_ID_COUNTER.load(deps.storage)?;
let mut new_pending = BTreeMap::new();
for event_id in last_executed + 1..=last_inserted {
let event = interval_storage::PENDING_EPOCH_EVENTS.load(deps.storage, event_id)?;
match event.kind {
PendingEpochEventKind::PledgeMore { mix_id, .. }
| PendingEpochEventKind::DecreasePledge { mix_id, .. } => {
if new_pending.insert(mix_id, event_id).is_some() {
return Err(MixnetContractError::FailedMigration { comment: format!("mixnode {mix_id} has more than a single pledge change pending for this epoch. Run this migration again after the epoch has finished.") });
}
}
_ => (),
}
}
for (mix_id, event_id) in new_pending {
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(
deps.storage,
mix_id,
&PendingMixNodeChanges {
pledge_change: Some(event_id),
},
)?;
}
Ok(())
}
+2 -2
View File
@@ -253,8 +253,8 @@ mod tests {
fn withdrawing_delegator_reward() { fn withdrawing_delegator_reward() {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
let delegation_amount = Uint128::new(2500_000_000); let delegation_amount = Uint128::new(2_500_000_000);
let delegation_dec = 2500_000_000u32.into_base_decimal().unwrap(); let delegation_dec = 2_500_000_000_u32.into_base_decimal().unwrap();
let mix_id = test.add_dummy_mixnode("mix-owner", None); let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegator = "delegator"; let delegator = "delegator";
test.add_immediate_delegation(delegator, delegation_amount, mix_id); test.add_immediate_delegation(delegator, delegation_amount, mix_id);

Some files were not shown because too many files have changed in this diff Show More