Compare commits

..

12 Commits

Author SHA1 Message Date
durch 342883fcbe Put back PacketType 1 2023-04-27 09:17:18 +02:00
Tommy Verrall 61a0ee5a19 change output for cpu-cycle management logs 2023-04-26 16:37:29 +01:00
Tommy Verrall 3956109c7e change the workflow file to build with cpucycles 2023-04-26 12:13:22 +01:00
durch 8d725b13c5 Outfox client compat 2023-04-24 13:14:58 +02:00
durch 4d166c389b Address PR comments 2023-04-21 00:30:46 +02:00
durch 145c3c1223 Rename PacketMode 2023-04-21 00:12:35 +02:00
Drazen Urch cbd654d6fd Outfox rest compat (#3333)
* Outfox forwarding compat

* Tidy up interface

* PacketSize compat
2023-04-20 23:59:40 +02:00
durch e7be91a94c Remove serde cruft 2023-04-19 16:36:48 +02:00
durch 582e7d566a Outfox framing 2023-04-19 16:24:09 +02:00
durch 6464da5f01 Framing compat 2023-04-18 22:23:02 +02:00
durch d5e77e499b Framed encoding serde POC 2023-04-18 18:18:54 +02:00
durch f086f9c35a Experiment with serde 2023-04-18 16:54:21 +02:00
205 changed files with 1524 additions and 9502 deletions
+22 -17
View File
@@ -11,25 +11,30 @@
# In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones.
# contracts
/contracts/mixnet @durch @jstuczyn
/contracts/vesting @durch @jstuczyn
/contracts/service-provider-directory @octol
# Something weird not covered by anything else
* @futurechimp @mmsinclair
# crypto code
/common/crypto/ @jstuczyn
/common/nymcoconut/ @jstuczyn
/common/dkg/ @jstuczyn
/common/nymsphinx/ @jstuczyn
# Rust rules:
*.rs @durch @futurechimp @jstuczyn @neacsu @octol
Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol
# rust sdk
/sdk/rust/ @octol
# JS rules:
*.js @mmsinclair @fmtabbara
*.ts @mmsinclair @fmtabbara
*.tsx @mmsinclair @fmtabbara
*.jsx @mmsinclair @fmtabbara
# nym-connect (rust)
/nym-connect/desktop/src-tauri/ @octol
# Something looking like possible documentation rules:
*.md @mfahampshire
# nym-wallet (rust)
/nym-wallet/src-tauri/ @octol
# our docker scripts
/docker/ @neacsu
# documentation
/documentation @mfahampshire
# if there are any changes in the core crypto, I feel like Ania should take a look:
/common/crypto/ @aniampio
/common/nymsphinx/ @aniampio
# Explorer and wallet should probably get looked by the product team
/explorer/ @nymtech/product
/nym-wallet/ @nymtech/product
/wallet-web/ @nymtech/product
@@ -69,7 +69,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --release --all
args: --workspace --release --all --features cpucycles
- name: Install Rust stable
uses: actions-rs/toolchain@v1
+1 -2
View File
@@ -30,7 +30,6 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' }}
needs: matrix_prep
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
steps:
- uses: actions/checkout@v2
@@ -64,4 +63,4 @@ jobs:
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/Cargo.toml --workspace --all-targets -- -D warnings
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings
@@ -6,7 +6,7 @@
},
{
"os":"windows10",
"os":"windows-latest",
"rust":"stable",
"runOnEvent":"schedule"
},
@@ -22,7 +22,7 @@
"runOnEvent":"schedule"
},
{
"os":"windows10",
"os":"windows-latest",
"rust":"beta",
"runOnEvent":"schedule"
},
@@ -38,7 +38,7 @@
"runOnEvent":"schedule"
},
{
"os":"windows10",
"os":"windows-latest",
"rust":"nightly",
"runOnEvent":"schedule"
},
+1 -1
View File
@@ -64,4 +64,4 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features --all-targets -- -D warnings
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features -- -D warnings
+1 -2
View File
@@ -41,5 +41,4 @@ storybook-static
envs/qwerty.env
.parcel-cache
**/.DS_Store
cpu-cycles/libcpucycles/build
foxyfox.env
cpu-cycles/libcpucycles/build
+7 -22
View File
@@ -4,29 +4,14 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.16] (2023-04-25)
- nym-network-statistics properly handles signals ([#3209])
- add socks5 support for Rust SDK ([#3226], [#3255])
- add coconut bandwidth credential support for Rust SDK ([#3273])
- Explorer - Fix sorting function on Stake Saturation. It is currently working per page and not globally ([#3320])
- Poisson process gets stuck at too slow rate. Rework to more aggressively up-regulate ([#3309])
- decrease the logging level of warnings associated with clients dropping packets due to gateway being overloaded (I'd say reduce it to debug/trace) - there are few sources of those, e.g. in real and cover traffic streams ([#3299])
- Make the buffer size in `AvailableReader` depend on packet sizes the client is using + introduce read timeouts ([#3213])
- Rust SDK - Support coconut, credential storage etc ([#2755])
- version bump for next release ([#3349])
- added coconut credential generation example ([#3339])
- update mix-node setup docs with node description ([#3325])
- exposed missing gateway commands in nym-cli ([#3324])
- make sure to clear inner 'ack_map' in 'GatewaysReader' ([#3300])
[#3320]: https://github.com/nymtech/nym/issues/3320
[#3309]: https://github.com/nymtech/nym/issues/3309
[#3299]: https://github.com/nymtech/nym/issues/3299
[#3213]: https://github.com/nymtech/nym/issues/3213
[#2755]: https://github.com/nymtech/nym/issues/2755
[#3349]: https://github.com/nymtech/nym/pull/3349
[#3339]: https://github.com/nymtech/nym/pull/3339
[#3325]: https://github.com/nymtech/nym/pull/3325
[#3324]: https://github.com/nymtech/nym/pull/3324
[#3300]: https://github.com/nymtech/nym/pull/3300
[#3209]: https://github.com/nymtech/nym/issues/3209
[#3226]: https://github.com/nymtech/nym/pull/3226
[#3255]: https://github.com/nymtech/nym/pull/3255
[#3273]: https://github.com/nymtech/nym/pull/3273
## [v1.1.15] (2023-04-18)
Generated
+12 -31
View File
@@ -1576,7 +1576,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.16"
version = "1.1.15"
dependencies = [
"chrono",
"clap 4.1.11",
@@ -3011,7 +3011,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.17"
version = "1.1.16"
dependencies = [
"anyhow",
"async-trait",
@@ -3142,7 +3142,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.16"
version = "1.1.15"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -3203,7 +3203,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.16"
version = "1.1.15"
dependencies = [
"clap 4.1.11",
"dirs",
@@ -3471,7 +3471,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.1.16"
version = "1.1.15"
dependencies = [
"anyhow",
"async-trait",
@@ -3600,7 +3600,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.4.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3619,7 +3619,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.17"
version = "1.1.16"
dependencies = [
"anyhow",
"bs58",
@@ -3721,7 +3721,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.16"
version = "1.1.15"
dependencies = [
"async-file-watcher",
"async-trait",
@@ -3763,7 +3763,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.16"
version = "1.1.15"
dependencies = [
"dirs",
"log",
@@ -3778,25 +3778,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "nym-node-tester-utils"
version = "0.1.0"
dependencies = [
"futures",
"log",
"nym-crypto",
"nym-sphinx",
"nym-sphinx-params",
"nym-task",
"nym-topology",
"rand 0.7.3",
"serde",
"serde_json",
"thiserror",
"tokio",
"wasm-utils",
]
[[package]]
name = "nym-nonexhaustive-delayqueue"
version = "0.1.0"
@@ -3899,7 +3880,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.16"
version = "1.1.15"
dependencies = [
"clap 4.1.11",
"lazy_static",
@@ -4225,7 +4206,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract"
version = "1.4.0"
version = "1.3.1"
dependencies = [
"cosmwasm-derive",
"cosmwasm-std",
@@ -4243,7 +4224,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.5.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
-1
View File
@@ -48,7 +48,6 @@ members = [
"common/ledger",
"common/mixnode-common",
"common/network-defaults",
"common/node-tester-utils",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nymsphinx",
+2 -6
View File
@@ -13,10 +13,6 @@ happy: fmt clippy-happy test
# on all workspaces.
build-release: build-release-main wasm
# Deprecated
# For backwards compatibility
clippy-all: clippy
# -----------------------------------------------------------------------------
# Define targets for a given workspace
# $(1): name
@@ -56,11 +52,11 @@ fmt-$(1):
cargo fmt --manifest-path $(2)/Cargo.toml --all
clippy-happy: clippy-happy-$(1)
clippy: clippy-$(1) clippy-examples-$(1)
clippy-all: clippy-$(1) clippy-examples-$(1)
check: check-$(1)
cargo-test: test-$(1)
cargo-test-expensive: test-expensive-$(1)
build: build-$(1) build-examples-$(1)
build: build-$(1) build-$(1)-examples
build-release-all: build-release-$(1)
fmt: fmt-$(1)
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.16"
version = "1.1.15"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.16"
version = "1.1.15"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
-1
View File
@@ -91,7 +91,6 @@ impl From<Init> for OverrideConfig {
no_cover: init_config.no_cover,
nyxd_urls: init_config.nyxd_urls,
enabled_credentials_mode: init_config.enabled_credentials_mode,
outfox: false,
}
}
}
-8
View File
@@ -10,7 +10,6 @@ use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_config::{NymConfig, OptionalSet};
use nym_socks5_client_core::config::old_config_v1_1_13::OldConfigV1_1_13;
use nym_socks5_client_core::config::{BaseConfig, Config};
use nym_sphinx::params::PacketType;
use std::error::Error;
pub mod init;
@@ -65,7 +64,6 @@ pub(crate) struct OverrideConfig {
no_cover: bool,
nyxd_urls: Option<Vec<url::Url>>,
enabled_credentials_mode: Option<bool>,
outfox: bool,
}
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
@@ -82,15 +80,9 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Syn
}
pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
let packet_type = if args.outfox {
PacketType::Outfox
} else {
PacketType::Mix
};
config
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
.with_base(BaseConfig::with_packet_type, packet_type)
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
.with_optional(Config::with_port, args.port)
.with_optional_custom_env_ext(
-4
View File
@@ -67,9 +67,6 @@ pub(crate) struct Run {
/// with bandwidth credential requirement.
#[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>,
#[clap(long, hide = true, action)]
outfox: bool,
}
impl From<Run> for OverrideConfig {
@@ -82,7 +79,6 @@ impl From<Run> for OverrideConfig {
no_cover: run_config.no_cover,
nyxd_urls: run_config.nyxd_urls,
enabled_credentials_mode: run_config.enabled_credentials_mode,
outfox: run_config.outfox,
}
}
}
-2
View File
@@ -1,2 +0,0 @@
[build]
target = "wasm32-unknown-unknown"
-7
View File
@@ -17,7 +17,6 @@ default = ["console_error_panic_hook"]
offline-test = []
[dependencies]
bs58 = "0.4.0"
futures = "0.3"
js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
@@ -29,12 +28,8 @@ tokio = { version = "1.24.1", features = ["sync"] }
url = "2.2"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
thiserror = "1.0.40"
wasm-timer = { git = "https://github.com/mmsinclair/wasm-timer", rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"}
# internal
nym-node-tester-utils = { path = "../../common/node-tester-utils" }
nym-client-core = { path = "../../common/client-core", default-features = false, features = ["wasm"] }
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
@@ -42,8 +37,6 @@ nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto" }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-topology = { path = "../../common/topology" }
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
nym-task = { path = "../../common/task" }
@@ -1,2 +0,0 @@
node_modules
dist
-5
View File
@@ -1,5 +0,0 @@
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import('./index.js')
.catch(e => console.error('Error importing `index.js`:', e));
@@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym WebAssembly Demo</title>
</head>
<body>
<p>
<label>Sender: </label><input disabled="true" size="85" type="text" id="sender" value="">
</p>
<p>
<label>Recipient: </label><input size="85" type="text" id="recipient" value="">
</p>
<p>
<label>Message: </label><input type="text" id="message" value="Hello mixnet!">
</p>
<p>
<button id="send-button">Send</button>
</p>
<div>
<label>Mixnode Identity: </label>
<input type="text" size = "60" id="mixnode_identity" value="...">
<button id="magic-button">✨ Magic Test Button ✨</button>
</div>
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
<p><span style='color: blue;'>Sent</span> messages show in blue, <span style='color: green;'>received</span>
messages show in green.</p>
<hr>
<p>
<span id="output"></span>
</p>
<script src="./bootstrap.js"></script>
</body>
</html>
-170
View File
@@ -1,170 +0,0 @@
// Copyright 2020-2023 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
class WebWorkerClient {
worker = null;
constructor() {
this.worker = new Worker('./worker.js');
this.worker.onmessage = (ev) => {
if (ev.data && ev.data.kind) {
switch (ev.data.kind) {
case 'Ready':
const {selfAddress} = ev.data.args;
displaySenderAddress(selfAddress);
break;
case 'ReceiveMessage':
const {message, senderTag, isTestPacket } = ev.data.args;
displayReceived(message, senderTag, isTestPacket);
break;
case 'DisableMagicTestButton':
const magicButton = document.querySelector('#magic-button');
magicButton.setAttribute('disabled', "true")
break;
case 'DisplayTesterResults':
const {score, sentPackets, receivedPackets, receivedAcks, duplicatePackets, duplicateAcks} = ev.data.args;
const resultText = `Test score: ${score}. Sent ${sentPackets} packets. Received ${receivedPackets} packets and ${receivedAcks} acks back. We also got ${duplicatePackets} duplicate packets and ${duplicateAcks} duplicate acks.`
displayReceivedRawString(resultText)
break;
}
}
};
}
sendMessage = (message, recipient) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'SendMessage',
args: {
message, recipient,
},
});
};
sendTestPacket = (mixnodeIdentity) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'TestPacket',
args: {
mixnodeIdentity,
},
});
}
}
let client = null;
async function main() {
client = new WebWorkerClient();
const sendButton = document.querySelector('#send-button');
sendButton.onclick = function () {
sendMessageTo();
};
const magicButton = document.querySelector('#magic-button');
magicButton.onclick = function () {
sendTestPacket();
}
}
/**
* Create a Sphinx packet and send it to the mixnet through the gateway node.
*
* Message and recipient are taken from the values in the user interface.
*
*/
async function sendMessageTo() {
const message = document.getElementById('message').value;
const recipient = document.getElementById('recipient').value;
await client.sendMessage(message, recipient);
displaySend(message);
}
async function sendTestPacket() {
const mixnodeIdentity = document.getElementById('mixnode_identity').value;
await client.sendTestPacket(mixnodeIdentity)
displaySend(`sending test packets to: ${mixnodeIdentity}...`);
}
/**
* Display messages that have been sent up the websocket. Colours them blue.
*
* @param {string} message
*/
function displaySend(message) {
let timestamp = new Date().toISOString().substr(11, 12);
let sendDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: blue');
let paragraphContent = document.createTextNode(timestamp + ' sent >>> ' + message);
paragraph.appendChild(paragraphContent);
sendDiv.appendChild(paragraph);
document.getElementById('output').appendChild(sendDiv);
}
/**
* Display received text messages in the browser. Colour them green.
*
* @param {Uint8Array} raw
*/
function displayReceived(raw, sender_tag, isTestPacket) {
let content = new TextDecoder().decode(raw);
if (sender_tag !== undefined) {
console.log("this message also contained some surbs from", sender_tag)
}
if (isTestPacket) {
const decoded = JSON.parse(content)
content = `Received packet ${decoded.msg_id} / ${decoded.total_msgs} for node ${decoded.encoded_node_identity} (test: ${decoded.test_id})`
}
displayReceivedRawString(content)
}
function displayReceivedRawString(raw) {
let timestamp = new Date().toISOString().substr(11, 12);
let receivedDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: green');
let paragraphContent = document.createTextNode(timestamp + ' received >>> ' + raw);
paragraph.appendChild(paragraphContent);
receivedDiv.appendChild(paragraph);
document.getElementById('output').appendChild(receivedDiv);
}
/**
* Display the nymClient's sender address in the user interface
*
* @param {String} address
*/
function displaySenderAddress(address) {
document.getElementById('sender').value = address;
}
main();
@@ -1,39 +0,0 @@
{
"name": "create-wasm-app",
"version": "0.1.0",
"description": "create an app to consume rust-generated wasm packages",
"main": "index.js",
"bin": {
"create-wasm-app": ".bin/create-wasm-app.js"
},
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server --port 8001"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
},
"keywords": [
"webassembly",
"wasm",
"rust",
"webpack"
],
"author": "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/nymtech/nym/issues"
},
"homepage": "https://nymtech.net/docs",
"devDependencies": {
"copy-webpack-plugin": "^10.2.4",
"hello-wasm-pack": "^0.1.0",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"@nymproject/nym-client-wasm": "file:../pkg"
}
}
@@ -1,33 +0,0 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
entry: {
bootstrap: './bootstrap.js',
worker: './worker.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
// mode: 'development',
mode: 'production',
plugins: [
new CopyWebpackPlugin({
patterns: [
'index.html',
{
from: 'node_modules/@nymproject/nym-client-wasm/*.(js|wasm)',
to: '[name][ext]',
},
],
}),
],
experiments: { syncWebAssembly: true },
};
-294
View File
@@ -1,294 +0,0 @@
// Copyright 2020-2023 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
importScripts('nym_client_wasm.js');
console.log('Initializing worker');
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
const {
NymNodeTester,
WasmGateway,
WasmMixNode,
WasmNymTopology,
default_debug,
NymClientBuilder,
NymClient,
set_panic_hook,
Config,
GatewayEndpointConfig,
current_network_topology,
} = wasm_bindgen;
let client = null;
let tester = null;
function dummyTopology() {
const l1Mixnode = new WasmMixNode(
1,
'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47',
'178.79.143.65',
1789,
'4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz',
'8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG',
1,
'1.10.0',
);
const l2Mixnode = new WasmMixNode(
2,
'n1z93z44vf8ssvdhujjvxcj4rd5e3lz0l60wdk70',
'109.74.197.180',
1789,
'7sVjiMrPYZrDWRujku9QLxgE8noT7NTgBAqizCsu7AoK',
'GepXwRnKZDd8x2nBWAajGGBVvF3mrpVMQBkgfrGuqRCN',
2,
'1.10.0',
);
const l3Mixnode = new WasmMixNode(
3,
'n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77',
'176.58.101.80',
1789,
'FoM5Mx9Pxk1g3zEqkS3APgtBeTtTo3M8k7Yu4bV6kK1R',
'DeYjrDC2AcQRVFshiKnbUo6bRvPyZ33QGYR2DLeFJ9qD',
3,
'1.10.0',
);
const gateway = new WasmGateway(
'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts',
'85.159.212.96',
1789,
9000,
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
'BtYjoWihiuFihGKQypmpSspbhmWDPxzqeTVSd8ciCpWL',
'1.10.1',
);
const mixnodes = new Map();
mixnodes.set(1, [l1Mixnode]);
mixnodes.set(2, [l2Mixnode]);
mixnodes.set(3, [l3Mixnode]);
const gateways = [gateway];
return new WasmNymTopology(mixnodes, gateways)
}
function printAndDisplayTestResult(result) {
result.log_details();
self.postMessage({
kind: 'DisplayTesterResults',
args: {
score: result.score(),
sentPackets: result.sent_packets,
receivedPackets: result.received_packets,
receivedAcks: result.received_acks,
duplicatePackets: result.duplicate_packets,
duplicateAcks: result.duplicate_acks,
},
});
}
function dummyGatewayConfig() {
return new GatewayEndpointConfig(
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
'n1rqqw8km7a0rvf8lr6k8dsdqvvkyn2mglj7xxfm',
'ws://85.159.212.96:9000',
)
}
async function testWithTester() {
const gatewayConfig = dummyGatewayConfig();
// A) construct with hardcoded topology
const topology = dummyTopology()
const nodeTester = await new NymNodeTester(gatewayConfig, topology);
// B) first get topology directly from nym-api
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const topology = await current_network_topology(validator)
// const nodeTester = await new NymNodeTester(gatewayConfig, topology);
//
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const nodeTester = await NymNodeTester.new_with_api(gatewayConfig, validator)
self.onmessage = async event => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'TestPacket': {
const {mixnodeIdentity} = event.data.args;
console.log("starting node test...");
let result = await nodeTester.test_node(mixnodeIdentity);
printAndDisplayTestResult(result)
}
}
}
};
}
async function testWithNymClient() {
const gatewayConfig = dummyGatewayConfig();
const topology = dummyTopology()
let received = 0
const onMessageHandler = (message) => {
received += 1;
self.postMessage({
kind: 'ReceiveMessage',
args: {
message,
senderTag: undefined,
isTestPacket: true,
},
});
// it's really up to the user to create proper callback here...
console.log(`received ${received} packets so far`)
};
console.log('Instantiating WASM client...');
let clientBuilder = NymClientBuilder.new_tester(gatewayConfig, topology, onMessageHandler)
console.log('Web worker creating WASM client...');
let local_client = await clientBuilder.start_client();
console.log('WASM client running!');
const selfAddress = local_client.self_address();
// set the global (I guess we don't have to anymore?)
client = local_client;
console.log(`Client address is ${selfAddress}`);
self.postMessage({
kind: 'Ready',
args: {
selfAddress,
},
});
// Set callback to handle messages passed to the worker.
self.onmessage = async event => {
console.log(event)
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'SendMessage': {
const {message, recipient} = event.data.args;
let uint8Array = new TextEncoder().encode(message);
await client.send_regular_message(uint8Array, recipient);
break;
}
case 'TestPacket': {
const {mixnodeIdentity} = event.data.args;
const req = await client.try_construct_test_packet_request(mixnodeIdentity);
await client.change_hardcoded_topology(req.injectable_topology());
await client.try_send_test_packets(req);
break;
}
}
}
};
}
async function normalNymClientUsage() {
self.postMessage({kind: 'DisableMagicTestButton'});
// only really useful if you want to adjust some settings like traffic rate
// (if not needed you can just pass a null)
const debug = default_debug();
debug.disable_main_poisson_packet_distribution = true;
debug.disable_loop_cover_traffic_stream = true;
debug.use_extended_packet_size = false;
// debug.average_packet_delay_ms = BigInt(10);
// debug.average_ack_delay_ms = BigInt(10);
// debug.ack_wait_addition_ms = BigInt(3000);
// debug.ack_wait_multiplier = 10;
debug.topology_refresh_rate_ms = BigInt(60000)
const gatewayConfig = dummyGatewayConfig();
const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
const config = new Config('my-awesome-wasm-client', validator, gatewayConfig, debug);
const onMessageHandler = (message) => {
console.log(message);
self.postMessage({
kind: 'ReceiveMessage',
args: {
message,
},
});
};
console.log('Instantiating WASM client...');
let localClient = await new NymClient(config, onMessageHandler)
console.log('WASM client running!');
const selfAddress = localClient.self_address();
// set the global (I guess we don't have to anymore?)
client = localClient;
console.log(`Client address is ${selfAddress}`);
self.postMessage({
kind: 'Ready',
args: {
selfAddress,
},
});
// Set callback to handle messages passed to the worker.
self.onmessage = async event => {
console.log(event)
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'SendMessage': {
const {message, recipient} = event.data.args;
let uint8Array = new TextEncoder().encode(message);
await client.send_regular_message(uint8Array, recipient);
break;
}
}
}
};
}
async function main() {
// load WASM package
await wasm_bindgen('nym_client_wasm_bg.wasm');
console.log('Loaded WASM');
// sets up better stack traces in case of in-rust panics
set_panic_hook();
// run test on simplified and dedicated tester:
await testWithTester()
// hook-up the whole client for testing
// await testWithNymClient()
// 'Normal' client setup (to send 'normal' messages)
// await normalNymClientUsage()
}
// Let's get started!
main();
File diff suppressed because it is too large Load Diff
+4 -13
View File
@@ -25,7 +25,7 @@ pub struct Config {
/// ID specifies the human readable ID of this particular client.
pub(crate) id: String,
pub(crate) nym_api_url: Option<Url>,
pub(crate) nym_api_url: Url,
pub(crate) disabled_credentials_mode: bool,
@@ -46,11 +46,9 @@ impl Config {
) -> Self {
Config {
id,
nym_api_url: Some(
validator_server
.parse()
.expect("provided url was malformed"),
),
nym_api_url: validator_server
.parse()
.expect("provided url was malformed"),
disabled_credentials_mode: true,
gateway_endpoint,
debug: debug.map(Into::into).unwrap_or_default(),
@@ -231,11 +229,6 @@ pub struct Topology {
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
pub topology_resolution_timeout_ms: u64,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
}
impl From<Topology> for ConfigTopology {
@@ -245,7 +238,6 @@ impl From<Topology> for ConfigTopology {
topology_resolution_timeout: Duration::from_millis(
topology.topology_resolution_timeout_ms,
),
disable_refreshing: topology.disable_refreshing,
}
}
}
@@ -255,7 +247,6 @@ impl From<ConfigTopology> for Topology {
Topology {
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64,
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64,
disable_refreshing: topology.disable_refreshing,
}
}
}
+6 -145
View File
@@ -1,42 +1,16 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::tester::helpers::WasmTestMessageExt;
use crate::tester::{NodeTestMessage, DEFAULT_TEST_PACKETS};
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::base_client::{ClientInput, ClientState};
use nym_client_core::client::base_client::ClientInput;
use nym_client_core::client::inbound_messages::InputMessage;
use nym_topology::{MixLayer, NymTopology};
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, js_error, simple_js_error};
#[wasm_bindgen]
pub struct NymClientTestRequest {
// serialized NodeTestMessage
pub(crate) test_msgs: Vec<Vec<u8>>,
// specially constructed network topology that only contains the target
// node on the tested layer
pub(crate) testable_topology: NymTopology,
}
#[wasm_bindgen]
impl NymClientTestRequest {
pub fn injectable_topology(&self) -> WasmNymTopology {
self.testable_topology.clone().into()
}
}
// defining helper trait as we could directly call the method on the wrapper
pub(crate) trait InputSender {
fn send_message(&self, message: InputMessage) -> Promise;
fn send_messages(&self, messages: Vec<InputMessage>) -> Promise;
}
impl InputSender for Arc<ClientInput> {
@@ -45,125 +19,12 @@ impl InputSender for Arc<ClientInput> {
future_to_promise(async move {
match this.input_sender.send(message).await {
Ok(_) => Ok(JsValue::null()),
Err(_) => Err(simple_js_error(
"InputMessageReceiver has stopped receiving!",
)),
}
})
}
fn send_messages(&self, messages: Vec<InputMessage>) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
for message in messages {
if this.input_sender.send(message).await.is_err() {
return Err(simple_js_error(
"InputMessageReceiver has stopped receiving!",
));
Err(_) => {
let js_error =
js_sys::Error::new("InputMessageReceiver has stopped receiving!");
Err(JsValue::from(js_error))
}
}
Ok(JsValue::null())
})
}
}
pub(crate) trait WasmTopologyExt {
/// Changes the current network topology to the provided value.
fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise;
/// Returns the current network topology.
fn current_topology(&self) -> Promise;
/// Checks whether the provided node exists in the known network topology and if so, returns its layer.
fn check_for_mixnode_existence(&self, mixnode_identity: String) -> Promise;
/// Creates a `NymClientTestRequest` with a variant of `this` topology where the target node is the only one on its layer.
fn mix_test_request(
&self,
test_id: u32,
mixnode_identity: String,
num_test_packets: Option<u32>,
) -> Promise;
}
impl WasmTopologyExt for Arc<ClientState> {
fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
let nym_topology: NymTopology = topology.into();
console_log!("changing topology to {nym_topology:?}");
this.topology_accessor
.manually_change_topology(nym_topology)
.await;
Ok(JsValue::null())
})
}
fn current_topology(&self) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
match this.topology_accessor.current_topology().await {
Some(topology) => Ok(JsValue::from(WasmNymTopology::from(topology))),
None => Err(WasmClientError::UnavailableNetworkTopology.into()),
}
})
}
/// Checks whether the target mixnode exists in the known network topology and returns its layer.
fn check_for_mixnode_existence(&self, mixnode_identity: String) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
let Some(current_topology) = this.topology_accessor.current_topology().await else {
return Err(WasmClientError::UnavailableNetworkTopology.into())
};
match current_topology.find_mix_by_identity(&mixnode_identity) {
None => Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into()),
Some(node) => Ok(JsValue::from(MixLayer::from(node.layer))),
}
})
}
fn mix_test_request(
&self,
test_id: u32,
mixnode_identity: String,
num_test_packets: Option<u32>,
) -> Promise {
let num_test_packets = num_test_packets.unwrap_or(DEFAULT_TEST_PACKETS);
let this = Arc::clone(self);
future_to_promise(async move {
let Some(current_topology) = this.topology_accessor.current_topology().await else {
return Err(WasmClientError::UnavailableNetworkTopology.into())
};
let Some(mix) = current_topology.find_mix_by_identity(&mixnode_identity) else {
return Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into());
};
let mut test_msgs = Vec::with_capacity(num_test_packets as usize);
for i in 1..=num_test_packets {
let msg = NodeTestMessage::new_mix(
mix,
i,
num_test_packets,
WasmTestMessageExt::new(test_id),
);
let serialized = match msg.as_bytes() {
Ok(bytes) => bytes,
Err(err) => return Err(js_error!("failed to serialize test message: {err}")),
};
test_msgs.push(serialized);
}
let mut updated = current_topology.clone();
updated.set_mixes_in_layer(mix.layer.into(), vec![mix.to_owned()]);
Ok(JsValue::from(NymClientTestRequest {
test_msgs,
testable_topology: updated,
}))
})
}
}
+98 -180
View File
@@ -1,37 +1,28 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use self::config::Config;
use crate::client::helpers::{InputSender, NymClientTestRequest, WasmTopologyExt};
use crate::client::helpers::InputSender;
use crate::client::response_pusher::ResponsePusher;
use crate::error::WasmClientError;
use crate::helpers::{
parse_recipient, parse_sender_tag, setup_new_key_manager, setup_reply_surb_storage_backend,
};
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::{
BaseClientBuilder, ClientInput, ClientOutput, ClientState, CredentialsToggle,
BaseClientBuilder, ClientInput, ClientOutput, CredentialsToggle,
};
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
use nym_client_core::config::{
CoverTraffic, DebugConfig, GatewayEndpointConfig, Topology, Traffic,
};
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
use nym_topology::NymTopology;
use rand::rngs::OsRng;
use rand::RngCore;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{check_promise_result, console_log, PromisableResult};
use wasm_utils::{console_error, console_log};
pub mod config;
mod helpers;
@@ -41,11 +32,6 @@ mod response_pusher;
pub struct NymClient {
self_address: String,
client_input: Arc<ClientInput>,
client_state: Arc<ClientState>,
// keep track of the "old" topology for the purposes of node tester
// so that it could be restored after the check is done
_full_topology: Option<NymTopology>,
// 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
@@ -56,7 +42,6 @@ pub struct NymClient {
#[wasm_bindgen]
pub struct NymClientBuilder {
config: Config,
custom_topology: Option<NymTopology>,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
@@ -78,10 +63,9 @@ impl NymClientBuilder {
pub fn new(config: Config, on_message: js_sys::Function) -> Self {
//, key_manager: Option<KeyManager>) {
NymClientBuilder {
reply_surb_storage_backend: setup_reply_surb_storage_backend(config.debug.reply_surbs),
reply_surb_storage_backend: Self::setup_reply_surb_storage_backend(&config),
config,
custom_topology: None,
key_manager: setup_new_key_manager(),
key_manager: Self::setup_key_manager(),
on_message,
bandwidth_controller: None,
disabled_credentials: true,
@@ -89,182 +73,110 @@ impl NymClientBuilder {
}
}
// no cover traffic
// no poisson delay
// hardcoded topology
// NOTE: you most likely want to use `[NymNodeTester]` instead.
pub fn new_tester(
gateway_config: GatewayEndpointConfig,
topology: WasmNymTopology,
on_message: js_sys::Function,
) -> Self {
if !topology.ensure_contains(&gateway_config) {
panic!("the specified topology does not contain the gateway used by the client")
}
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
// a prior shared keypair between the client and the gateway
let full_config = Config {
id: "ephemeral-id".to_string(),
nym_api_url: None,
disabled_credentials_mode: true,
gateway_endpoint: gateway_config,
debug: DebugConfig {
traffic: Traffic {
disable_main_poisson_packet_distribution: true,
..Default::default()
},
cover_traffic: CoverTraffic {
disable_loop_cover_traffic_stream: true,
..Default::default()
},
topology: Topology {
disable_refreshing: true,
..Default::default()
},
..Default::default()
},
};
// perhaps this should be public?
fn setup_key_manager() -> KeyManager {
let mut rng = OsRng;
// for time being generate new keys each time...
console_log!("generated new set of keys");
KeyManager::new(&mut rng)
}
NymClientBuilder {
reply_surb_storage_backend: setup_reply_surb_storage_backend(
full_config.debug.reply_surbs,
),
config: full_config,
custom_topology: Some(topology.into()),
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
// a prior shared keypair between the client and the gateway
key_manager: setup_new_key_manager(),
on_message,
bandwidth_controller: None,
disabled_credentials: true,
packet_type: None,
}
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no persistence
fn setup_reply_surb_storage_backend(config: &Config) -> browser_backend::Backend {
browser_backend::Backend::new(
config
.debug
.reply_surbs
.minimum_reply_surb_storage_threshold,
config
.debug
.reply_surbs
.maximum_reply_surb_storage_threshold,
)
}
fn start_reconstructed_pusher(client_output: ClientOutput, on_message: js_sys::Function) {
ResponsePusher::new(client_output, on_message).start()
}
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider>> {
if let Some(hardcoded_topology) = self.custom_topology.take() {
Some(Box::new(HardcodedTopologyProvider::new(hardcoded_topology)))
} else {
None
}
}
pub async fn start_client(self) -> Promise {
future_to_promise(async move {
console_log!("Starting the wasm client");
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> {
console_log!("Starting the wasm client");
let disabled_credentials = if self.disabled_credentials {
CredentialsToggle::Disabled
} else {
CredentialsToggle::Enabled
};
let maybe_topology_provider = self.topology_provider();
let base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
self.key_manager,
self.bandwidth_controller,
self.reply_surb_storage_backend,
disabled_credentials,
vec![self.config.nym_api_url.clone()],
);
let disabled_credentials = if self.disabled_credentials {
CredentialsToggle::Disabled
} else {
CredentialsToggle::Enabled
};
let self_address = base_builder.as_mix_recipient().to_string();
let mut started_client = match base_builder.start_base().await {
Ok(base_client) => base_client,
Err(err) => {
let error_msg = format!("failed to start the base client components - {err}");
console_error!("{}", error_msg);
let js_error = js_sys::Error::new(&error_msg);
return Err(JsValue::from(js_error));
}
};
let nym_api_endpoints = match self.config.nym_api_url {
Some(endpoint) => vec![endpoint],
None => Vec::new(),
};
let mut base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
self.key_manager,
self.bandwidth_controller,
self.reply_surb_storage_backend,
disabled_credentials,
nym_api_endpoints,
);
if let Some(topology_provider) = maybe_topology_provider {
base_builder = base_builder.with_topology_provider(topology_provider);
}
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let self_address = base_builder.as_mix_recipient().to_string();
let mut started_client = base_builder.start_base().await?;
Self::start_reconstructed_pusher(client_output, self.on_message);
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
Self::start_reconstructed_pusher(client_output, self.on_message);
Ok(NymClient {
self_address,
client_input: Arc::new(client_input),
client_state: Arc::new(started_client.client_state),
_full_topology: None,
_task_manager: started_client.task_manager,
packet_type: self.packet_type,
Ok(JsValue::from(NymClient {
self_address,
client_input: Arc::new(client_input),
_task_manager: started_client.task_manager,
packet_type: self.packet_type,
}))
})
}
pub fn start_client(self) -> Promise {
future_to_promise(async move { self.start_client_async().await.into_promise_result() })
}
}
#[wasm_bindgen]
impl NymClient {
async fn _new(
config: Config,
on_message: js_sys::Function,
) -> Result<NymClient, WasmClientError> {
NymClientBuilder::new(config, on_message)
.start_client_async()
.await
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(config: Config, on_message: js_sys::Function) -> Promise {
future_to_promise(async move { Self::_new(config, on_message).await.into_promise_result() })
}
pub fn self_address(&self) -> String {
self.self_address.clone()
}
pub fn try_construct_test_packet_request(
&self,
mixnode_identity: String,
num_test_packets: Option<u32>,
) -> Promise {
// TODO: improve the source of rng (i.e. don't make it ephemeral...)
let mut ephemeral_rng = OsRng;
let test_id = ephemeral_rng.next_u32();
self.client_state
.mix_test_request(test_id, mixnode_identity, num_test_packets)
fn parse_recipient(recipient: &str) -> Result<Recipient, JsValue> {
match Recipient::try_from_base58_string(recipient) {
Ok(recipient) => Ok(recipient),
Err(err) => {
let error_msg = format!("{recipient} is not a valid Nym network recipient - {err}");
console_error!("{}", error_msg);
let js_error = js_sys::Error::new(&error_msg);
Err(JsValue::from(js_error))
}
}
}
pub fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise {
self.client_state.change_hardcoded_topology(topology)
}
pub fn current_network_topology(&self) -> Promise {
self.client_state.current_topology()
}
/// Sends a test packet through the current network topology.
/// It's the responsibility of the caller to ensure the correct topology has been injected and
/// correct onmessage handlers have been setup.
pub fn try_send_test_packets(&mut self, request: NymClientTestRequest) -> Promise {
// TOOD: use the premade packets instead
console_log!(
"Attempting to send {} test packets",
request.test_msgs.len()
);
// our address MUST BE valid
let recipient = parse_recipient(&self.self_address()).unwrap();
let lane = TransmissionLane::General;
let input_msgs = request
.test_msgs
.into_iter()
.map(|p| InputMessage::new_regular(recipient, p, lane, None))
.collect();
self.client_input.send_messages(input_msgs)
fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, JsValue> {
match AnonymousSenderTag::try_from_base58_string(tag) {
Ok(tag) => Ok(tag),
Err(err) => {
let error_msg = format!("{tag} is not a valid Nym AnonymousSenderTag - {err}");
console_error!("{}", error_msg);
let js_error = js_sys::Error::new(&error_msg);
Err(JsValue::from(js_error))
}
}
}
/// The simplest message variant where no additional information is attached.
@@ -277,8 +189,10 @@ impl NymClient {
message.len() as f64 / 1024.0
);
let recipient = check_promise_result!(parse_recipient(&recipient));
let recipient = match Self::parse_recipient(&recipient) {
Ok(recipient) => recipient,
Err(err) => return Promise::reject(&err),
};
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
@@ -304,8 +218,10 @@ impl NymClient {
message.len() as f64 / 1024.0
);
let recipient = check_promise_result!(parse_recipient(&recipient));
let recipient = match Self::parse_recipient(&recipient) {
Ok(recipient) => recipient,
Err(err) => return Promise::reject(&err),
};
let lane = TransmissionLane::General;
let input_msg =
@@ -323,8 +239,10 @@ impl NymClient {
message.len() as f64 / 1024.0
);
let sender_tag = check_promise_result!(parse_sender_tag(&recipient_tag));
let sender_tag = match Self::parse_sender_tag(&recipient_tag) {
Ok(recipient) => recipient,
Err(err) => return Promise::reject(&err),
};
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(sender_tag, message, lane, self.packet_type);
-99
View File
@@ -1,99 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::topology::WasmTopologyError;
use js_sys::Promise;
use nym_client_core::error::ClientCoreError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_gateway_client::error::GatewayClientError;
use nym_node_tester_utils::error::NetworkTestingError;
use nym_sphinx::addressing::clients::RecipientFormattingError;
use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation;
use nym_validator_client::ValidatorClientError;
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_utils::simple_js_error;
// might as well start using well-defined error enum...
#[derive(Debug, Error)]
pub enum WasmClientError {
#[error(
"A node test is already in progress. Wait for it to finish before starting another one."
)]
TestInProgress,
#[error("experienced an issue with internal client components: {source}")]
BaseClientError {
#[from]
source: ClientCoreError,
},
#[error("The provided gateway identity is invalid: {source}")]
InvalidGatewayIdentity { source: Ed25519RecoveryError },
#[error("Gateway communication failure: {source}")]
GatewayClientError {
#[from]
source: GatewayClientError,
},
#[error("failed to query nym api: {source}")]
NymApiError {
#[from]
source: ValidatorClientError,
},
#[error("The provided topology was invalid: {source}")]
WasmTopologyError {
#[from]
source: WasmTopologyError,
},
#[error("failed to test the node: {source}")]
NodeTestingFailure {
#[from]
source: NetworkTestingError,
},
#[error("{raw} is not a valid url: {source}")]
MalformedUrl {
raw: String,
source: url::ParseError,
},
#[error("Network topology is currently unavailable")]
UnavailableNetworkTopology,
#[error("Mixnode {mixnode_identity} is not present in the current network topology")]
NonExistentMixnode { mixnode_identity: String },
#[error("{raw} is not a valid Nym network recipient: {source}")]
MalformedRecipient {
raw: String,
source: RecipientFormattingError,
},
#[error("{raw} is not a valid Nym AnonymousSenderTag: {source}")]
MalformedSenderTag {
raw: String,
source: InvalidAnonymousSenderTagRepresentation,
},
}
impl WasmClientError {
pub fn into_rejected_promise(self) -> Promise {
self.into()
}
}
impl From<WasmClientError> for JsValue {
fn from(value: WasmClientError) -> Self {
simple_js_error(value.to_string())
}
}
impl From<WasmClientError> for Promise {
fn from(value: WasmClientError) -> Self {
Promise::reject(&value.into())
}
}
-82
View File
@@ -1,82 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::NymTopology;
use nym_validator_client::NymApiClient;
use rand::rngs::OsRng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, PromisableResult};
pub(crate) fn setup_new_key_manager() -> KeyManager {
let mut rng = OsRng;
console_log!("generated new set of keys");
KeyManager::new(&mut rng)
}
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no persistence
pub(crate) fn setup_reply_surb_storage_backend(
config: config::ReplySurbs,
) -> browser_backend::Backend {
browser_backend::Backend::new(
config.minimum_reply_surb_storage_threshold,
config.maximum_reply_surb_storage_threshold,
)
}
pub(crate) fn parse_recipient(recipient: &str) -> Result<Recipient, WasmClientError> {
Recipient::try_from_base58_string(recipient).map_err(|source| {
WasmClientError::MalformedRecipient {
raw: recipient.to_string(),
source,
}
})
}
pub(crate) fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmClientError> {
AnonymousSenderTag::try_from_base58_string(tag).map_err(|source| {
WasmClientError::MalformedSenderTag {
raw: tag.to_string(),
source,
}
})
}
pub(crate) async fn current_network_topology_async(
nym_api_url: String,
) -> Result<WasmNymTopology, WasmClientError> {
let url: Url = match nym_api_url.parse() {
Ok(url) => url,
Err(source) => {
return Err(WasmClientError::MalformedUrl {
raw: nym_api_url,
source,
})
}
};
let api_client = NymApiClient::new(url);
let mixnodes = api_client.get_cached_active_mixnodes().await?;
let gateways = api_client.get_cached_gateways().await?;
Ok(NymTopology::from_detailed(mixnodes, gateways).into())
}
#[wasm_bindgen]
pub fn current_network_topology(nym_api_url: String) -> Promise {
future_to_promise(async move {
current_network_topology_async(nym_api_url)
.await
.into_promise_result()
})
}
-8
View File
@@ -7,19 +7,11 @@ use wasm_bindgen::prelude::*;
mod client;
#[cfg(target_arch = "wasm32")]
pub mod encoded_payload_helper;
pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod gateway_selector;
#[cfg(target_arch = "wasm32")]
pub mod tester;
#[cfg(target_arch = "wasm32")]
pub mod topology;
#[cfg(target_arch = "wasm32")]
pub mod validation;
#[cfg(target_arch = "wasm32")]
mod helpers;
#[wasm_bindgen]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
@@ -1,117 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::tester::helpers::NodeTestResult;
use crate::tester::NodeTestMessage;
use futures::StreamExt;
use nym_node_tester_utils::receiver::{Received, ReceivedReceiver};
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use std::collections::HashSet;
use std::time::Duration;
use tokio::sync::MutexGuard as AsyncMutexGuard;
use wasm_utils::{console_error, console_log, console_warn};
pub(crate) struct EphemeralTestReceiver<'a> {
sent_packets: u32,
expected_acks: HashSet<FragmentIdentifier>,
received_valid_messages: HashSet<u32>,
received_valid_acks: HashSet<FragmentIdentifier>,
duplicate_packets: u32,
duplicate_acks: u32,
timeout_duration: Duration,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver>,
}
impl<'a> EphemeralTestReceiver<'a> {
pub(crate) fn finish(self) -> NodeTestResult {
NodeTestResult {
sent_packets: self.sent_packets,
received_packets: self.received_valid_messages.len() as u32,
received_acks: self.received_valid_acks.len() as u32,
duplicate_packets: self.duplicate_packets,
duplicate_acks: self.duplicate_acks,
}
}
pub(crate) fn new(
sent_packets: u32,
expected_acks: HashSet<FragmentIdentifier>,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver>,
timeout: Duration,
) -> Self {
EphemeralTestReceiver {
sent_packets,
expected_acks,
received_valid_messages: Default::default(),
received_valid_acks: Default::default(),
duplicate_packets: 0,
duplicate_acks: 0,
timeout_duration: timeout,
receiver_permit,
}
}
fn on_next_received_packet(&mut self, packet: Option<Received>) -> bool {
let Some(received_packet) = packet else {
// can't do anything more...
console_error!("packet receiver has stopped processing results!");
return true
};
match received_packet {
Received::Message(msg) => match NodeTestMessage::try_recover(msg) {
Ok(test_msg) => {
if !self.received_valid_messages.insert(test_msg.msg_id) {
self.duplicate_packets += 1;
}
}
Err(err) => {
console_warn!("failed to recover test message from received packet: {err}")
}
},
Received::Ack(frag_id) => {
if self.expected_acks.contains(&frag_id) {
if !self.received_valid_acks.insert(frag_id) {
self.duplicate_acks += 1
}
} else {
console_warn!("received an ack that was not part of the test! (id: {frag_id})")
}
}
}
if self.received_all() {
console_log!("already received all the packets! finishing the test...");
true
} else {
false
}
}
fn received_all(&self) -> bool {
self.received_valid_acks.len() == self.received_valid_messages.len()
&& self.received_valid_acks.len() == self.sent_packets as usize
}
pub(crate) async fn perform_test(mut self) -> NodeTestResult {
let mut timeout_fut = wasm_timer::Delay::new(self.timeout_duration);
loop {
tokio::select! {
_ = &mut timeout_fut => {
console_warn!("reached test timeout before receiving all packets.");
break
}
received_packet = self.receiver_permit.next() => {
let is_done = self.on_next_received_packet(received_packet);
if is_done {
break
}
}
}
}
self.finish()
}
}
-108
View File
@@ -1,108 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to expansion of #[wasm_bindgen] macro on NodeTestResult
#![allow(clippy::drop_non_drop)]
use nym_node_tester_utils::receiver::{Received, ReceivedReceiver};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard};
use wasm_bindgen::prelude::*;
use wasm_utils::{console_log, console_warn};
#[derive(Clone)]
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver>>);
impl ReceivedReceiverWrapper {
pub(super) fn new(inner: ReceivedReceiver) -> Self {
ReceivedReceiverWrapper(Arc::new(AsyncMutex::new(inner)))
}
pub(super) async fn clear_received_channel(&self) {
let mut lost_msgs = 0;
let mut lost_acks = 0;
let mut permit = self.0.lock().await;
while let Ok(Some(received)) = permit.try_next() {
match received {
Received::Message(_) => lost_msgs += 1,
Received::Ack(_) => lost_acks += 1,
}
}
if lost_msgs > 0 || lost_acks > 0 {
console_warn!("while preparing for the test run, we cleared {lost_msgs} messages and {lost_acks} acks that were received in the meantime.")
}
}
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver> {
self.0.lock().await
}
}
#[derive(Serialize, Deserialize, Copy, Clone)]
pub struct WasmTestMessageExt {
pub test_id: u32,
}
impl WasmTestMessageExt {
pub fn new(test_id: u32) -> Self {
WasmTestMessageExt { test_id }
}
}
// TODO: maybe put it in the tester utils
#[wasm_bindgen]
pub struct NodeTestResult {
pub sent_packets: u32,
pub received_packets: u32,
pub received_acks: u32,
pub duplicate_packets: u32,
pub duplicate_acks: u32,
}
impl Display for NodeTestResult {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Test results: ")?;
writeln!(f, "Total score: {:.2}%", self.score())?;
writeln!(f, "Sent packets: {}", self.sent_packets)?;
writeln!(f, "Received (valid) packets: {}", self.received_packets)?;
writeln!(f, "Received (valid) acks: {}", self.received_acks)?;
writeln!(f, "Received duplicate packets: {}", self.duplicate_packets)?;
write!(f, "Received duplicate acks: {}", self.duplicate_acks)
}
}
#[wasm_bindgen]
impl NodeTestResult {
pub fn log_details(&self) {
console_log!("{}", self)
}
pub fn score(&self) -> f32 {
let expected = self.sent_packets * 2;
let actual = (self.received_packets + self.received_acks)
.saturating_sub(self.duplicate_packets + self.duplicate_acks);
actual as f32 / expected as f32 * 100.
}
}
pub(crate) struct TestMarker {
value: Arc<AtomicBool>,
}
impl TestMarker {
pub fn new(value: Arc<AtomicBool>) -> Self {
Self { value }
}
}
impl Drop for TestMarker {
// make sure to clear the test flag when the marker is dropped
fn drop(&mut self) {
self.value.store(false, Ordering::SeqCst)
}
}
-316
View File
@@ -1,316 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::helpers::{current_network_topology_async, setup_new_key_manager};
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
use crate::tester::helpers::{
NodeTestResult, ReceivedReceiverWrapper, TestMarker, WasmTestMessageExt,
};
use crate::topology::WasmNymTopology;
use futures::channel::mpsc;
use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::config::GatewayEndpointConfig;
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_node_tester_utils::receiver::SimpleMessageReceiver;
use nym_node_tester_utils::{NodeTester, TestMessage};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::params::PacketSize;
use nym_sphinx::preparer::PreparedFragment;
use nym_task::TaskManager;
use nym_topology::NymTopology;
use rand::rngs::OsRng;
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex as SyncMutex};
use std::time::Duration;
use tokio::sync::Mutex as AsyncMutex;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{check_promise_result, console_log, console_warn, PromisableResult};
mod ephemeral_receiver;
pub(crate) mod helpers;
pub type NodeTestMessage = TestMessage<WasmTestMessageExt>;
type LockedGatewayClient =
Arc<AsyncMutex<GatewayClient<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>>;
pub(crate) const DEFAULT_TEST_TIMEOUT: Duration = Duration::from_secs(10);
pub(crate) const DEFAULT_TEST_PACKETS: u32 = 20;
#[wasm_bindgen]
pub struct NymNodeTester {
test_in_progress: Arc<AtomicBool>,
// we need to increment the nonce between tests to distinguish the packets
// but we can't make the tester mutable because of wasm...
// so we're using the atomics
current_test_nonce: AtomicU32,
// blame all those mutexes on being unable to have an async method with internal mutability...
tester: Arc<SyncMutex<NodeTester<OsRng>>>,
gateway_client: LockedGatewayClient,
// we have to put it behind the lock due to wasm limitations and borrowing...
// the mutex acquisition should be instant as there aren't going to be any threads attempting
// to get simultaneous access
processed_receiver: ReceivedReceiverWrapper,
// 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
_task_manager: TaskManager,
}
#[wasm_bindgen]
pub struct NymNodeTesterBuilder {
gateway_config: GatewayEndpointConfig,
base_topology: NymTopology,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
// unimplemented
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
}
fn address(keys: &KeyManager, gateway_identity: NodeIdentity) -> Recipient {
Recipient::new(
*keys.identity_keypair().public_key(),
*keys.encryption_keypair().public_key(),
gateway_identity,
)
}
#[wasm_bindgen]
impl NymNodeTesterBuilder {
#[wasm_bindgen(constructor)]
pub fn new(
gateway_config: GatewayEndpointConfig,
base_topology: WasmNymTopology,
) -> NymNodeTesterBuilder {
NymNodeTesterBuilder {
gateway_config,
base_topology: base_topology.into(),
key_manager: setup_new_key_manager(),
bandwidth_controller: None,
}
}
async fn _new_with_api(
gateway_config: GatewayEndpointConfig,
api_url: String,
) -> Result<Self, WasmClientError> {
let topology = current_network_topology_async(api_url).await?;
Ok(NymNodeTesterBuilder::new(gateway_config, topology))
}
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
future_to_promise(async move {
Self::_new_with_api(gateway_config, api_url)
.await
.into_promise_result()
})
}
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
let rng = OsRng;
let task_manager = TaskManager::default();
let gateway_identity =
identity::PublicKey::from_base58_string(self.gateway_config.gateway_id)
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
// we **REALLY** need persistence...
let shared_key = if self.key_manager.is_gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
console_warn!("Gateway key not set - will derive a fresh one.");
None
};
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
let mut gateway_client = GatewayClient::new(
self.gateway_config.gateway_listener,
self.key_manager.identity_keypair(),
gateway_identity,
shared_key,
mixnet_message_sender,
ack_sender,
Duration::from_secs(10),
self.bandwidth_controller.take(),
task_manager.subscribe(),
);
gateway_client.set_disabled_credentials_mode(true);
let shared_keys = gateway_client.authenticate_and_start().await?;
// currently pointless but might as well do it for the future ¯\_(ツ)_/¯
self.key_manager.insert_gateway_shared_key(shared_keys);
// TODO: make those values configurable later
let tester = NodeTester::new(
rng,
self.base_topology,
address(&self.key_manager, gateway_identity),
PacketSize::default(),
Duration::from_millis(5),
Duration::from_millis(5),
self.key_manager.ack_key(),
);
let (processed_sender, processed_receiver) = mpsc::unbounded();
let mut receiver = SimpleMessageReceiver::new_sphinx_receiver(
self.key_manager.encryption_keypair(),
self.key_manager.ack_key(),
mixnet_message_receiver,
ack_receiver,
processed_sender,
task_manager.subscribe(),
);
nym_task::spawn(async move { receiver.run().await });
Ok(NymNodeTester {
test_in_progress: Arc::new(AtomicBool::new(false)),
current_test_nonce: Default::default(),
tester: Arc::new(SyncMutex::new(tester)),
gateway_client: Arc::new(AsyncMutex::new(gateway_client)),
processed_receiver: ReceivedReceiverWrapper::new(processed_receiver),
_task_manager: task_manager,
})
}
pub fn setup_client(self) -> Promise {
future_to_promise(async move { self._setup_client().await.into_promise_result() })
}
}
async fn test_mixnode(
test_packets: Vec<PreparedFragment>,
gateway_client: LockedGatewayClient,
processed_receiver: ReceivedReceiverWrapper,
_test_marker: TestMarker,
timeout: Duration,
) -> Result<NodeTestResult, WasmClientError> {
let num_test_packets = test_packets.len() as u32;
let expected_ack_ids = test_packets
.iter()
.map(|p| p.fragment_identifier)
.collect::<HashSet<_>>();
let mix_packets = test_packets.into_iter().map(|p| p.mix_packet).collect();
// start by clearing any messages that might have been received between tests
processed_receiver.clear_received_channel().await;
// locking the gateway client so that we could get mutable access to data without having to declare
// self mutable
let mut gateway_permit = gateway_client.lock().await;
gateway_permit.batch_send_mix_packets(mix_packets).await?;
let receiver_permit = processed_receiver.lock().await;
let result =
EphemeralTestReceiver::new(num_test_packets, expected_ack_ids, receiver_permit, timeout)
.perform_test()
.await;
Ok(result)
}
#[wasm_bindgen]
impl NymNodeTester {
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(gateway_config: GatewayEndpointConfig, topology: WasmNymTopology) -> Promise {
console_log!("constructing node tester!");
NymNodeTesterBuilder::new(gateway_config, topology).setup_client()
}
async fn _new_with_api(
gateway_config: GatewayEndpointConfig,
api_url: String,
) -> Result<Self, WasmClientError> {
NymNodeTesterBuilder::_new_with_api(gateway_config, api_url)
.await?
._setup_client()
.await
}
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
future_to_promise(async move {
Self::_new_with_api(gateway_config, api_url)
.await
.into_promise_result()
})
}
fn prepare_test_packets(
&self,
mixnode_identity: String,
test_nonce: u32,
num_test_packets: u32,
) -> Result<Vec<PreparedFragment>, WasmClientError> {
let test_ext = WasmTestMessageExt::new(test_nonce);
let mut tester_permit = self.tester.lock().expect("mutex got poisoned");
tester_permit
.existing_identity_mixnode_test_packets(mixnode_identity, test_ext, num_test_packets)
.map_err(Into::into)
}
pub fn test_node(
&self,
mixnode_identity: String,
timeout_millis: Option<u64>,
num_test_packets: Option<u32>,
) -> Promise {
// establish test parameters
let timeout = timeout_millis
.map(Duration::from_millis)
.unwrap_or(DEFAULT_TEST_TIMEOUT);
let num_test_packets = num_test_packets.unwrap_or(DEFAULT_TEST_PACKETS);
// mark start of the test
if self.test_in_progress.swap(true, Ordering::SeqCst) {
return WasmClientError::TestInProgress.into_rejected_promise();
}
// prepare test packets
// (I simultaneously feel both disgusted and amazed by this workaround)
let test_nonce = self.current_test_nonce.fetch_add(1, Ordering::Relaxed);
let test_packets = check_promise_result!(self.prepare_test_packets(
mixnode_identity,
test_nonce,
num_test_packets
));
let processed_receiver_clone = self.processed_receiver.clone();
let gateway_client_clone = Arc::clone(&self.gateway_client);
let tester_marker = TestMarker::new(Arc::clone(&self.test_in_progress));
// start doing async things (send packets and watch for anything coming back)
future_to_promise(async move {
test_mixnode(
test_packets,
gateway_client_clone,
processed_receiver_clone,
tester_marker,
timeout,
)
.await
.into_promise_result()
})
}
}
-262
View File
@@ -1,262 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::GatewayEndpointConfig;
use nym_crypto::asymmetric::{encryption, identity};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::{Layer, MixnodeConversionError};
use nym_topology::{gateway, mix, MixLayer, NymTopology};
use nym_validator_client::client::MixId;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use thiserror::Error;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use wasm_utils::{console_log, simple_js_error};
#[derive(Debug, Error)]
pub enum WasmTopologyError {
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
InvalidMixLayer { value: u8 },
#[error(transparent)]
GatewayConversion(#[from] GatewayConversionError),
#[error(transparent)]
MixnodeConversion(#[from] MixnodeConversionError),
#[error("The provided mixnode map was malformed: {source}")]
MalformedMixnodeMap { source: serde_wasm_bindgen::Error },
#[error("The provided gateway list was malformed: {source}")]
MalformedGatewayList { source: serde_wasm_bindgen::Error },
}
impl From<WasmTopologyError> for JsValue {
fn from(value: WasmTopologyError) -> Self {
simple_js_error(value.to_string())
}
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct WasmNymTopology {
inner: NymTopology,
}
#[wasm_bindgen]
impl WasmNymTopology {
#[wasm_bindgen(constructor)]
pub fn new(
// expected: BTreeMap<MixLayer, Vec<WasmMixNode>>,
// HashMap<MixLayer, Vec<WasmMixNode>> will also work because it has the same json representation
mixnodes: JsValue,
// expected: Vec<WasmGateway>
gateways: JsValue,
) -> Result<WasmNymTopology, WasmTopologyError> {
let mixnodes: BTreeMap<MixLayer, Vec<WasmMixNode>> =
serde_wasm_bindgen::from_value(mixnodes)
.map_err(|source| WasmTopologyError::MalformedMixnodeMap { source })?;
let gateways: Vec<WasmGateway> = serde_wasm_bindgen::from_value(gateways)
.map_err(|source| WasmTopologyError::MalformedGatewayList { source })?;
let mut converted_mixes = BTreeMap::new();
for (layer, nodes) in mixnodes {
let layer_nodes = nodes
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
converted_mixes.insert(layer, layer_nodes);
}
let gateways = gateways
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
Ok(WasmNymTopology {
inner: NymTopology::new(converted_mixes, gateways),
})
}
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
self.inner
.gateways()
.iter()
.any(|g| g.identity_key.to_base58_string() == gateway_config.gateway_id)
}
pub fn print(&self) {
if !self.inner.mixes().is_empty() {
console_log!("mixnodes:");
for (layer, nodes) in self.inner.mixes() {
console_log!("\tlayer {layer}:");
for node in nodes {
console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
}
}
} else {
console_log!("NO MIXNODES")
}
if !self.inner.gateways().is_empty() {
console_log!("gateways:");
for gateway in self.inner.gateways() {
console_log!("\t{}", gateway.identity_key)
}
} else {
console_log!("NO GATEWAYS")
}
}
}
impl From<WasmNymTopology> for NymTopology {
fn from(value: WasmNymTopology) -> Self {
value.inner
}
}
impl From<NymTopology> for WasmNymTopology {
fn from(value: NymTopology) -> Self {
WasmNymTopology { inner: value }
}
}
#[wasm_bindgen]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WasmMixNode {
pub mix_id: MixId,
#[wasm_bindgen(getter_with_clone)]
pub owner: String,
#[wasm_bindgen(getter_with_clone)]
pub host: String,
pub mix_port: u16,
#[wasm_bindgen(getter_with_clone)]
pub identity_key: String,
#[wasm_bindgen(getter_with_clone)]
pub sphinx_key: String,
pub layer: MixLayer,
#[wasm_bindgen(getter_with_clone)]
pub version: String,
}
#[wasm_bindgen]
impl WasmMixNode {
#[wasm_bindgen(constructor)]
#[allow(clippy::too_many_arguments)]
pub fn new(
mix_id: MixId,
owner: String,
host: String,
mix_port: u16,
identity_key: String,
sphinx_key: String,
layer: MixLayer,
version: String,
) -> Self {
Self {
mix_id,
owner,
host,
mix_port,
identity_key,
sphinx_key,
layer,
version,
}
}
}
impl TryFrom<WasmMixNode> for mix::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmMixNode) -> Result<Self, Self::Error> {
let host = mix::Node::parse_host(&value.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::Node::extract_mix_host(&host, value.mix_port)?;
Ok(mix::Node {
mix_id: value.mix_id,
owner: value.owner,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(MixnodeConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(MixnodeConversionError::from)?,
layer: Layer::try_from(value.layer)
.map_err(|_| WasmTopologyError::InvalidMixLayer { value: value.layer })?,
version: value.version,
})
}
}
#[wasm_bindgen]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WasmGateway {
#[wasm_bindgen(getter_with_clone)]
pub owner: String,
#[wasm_bindgen(getter_with_clone)]
pub host: String,
pub mix_port: u16,
pub clients_port: u16,
#[wasm_bindgen(getter_with_clone)]
pub identity_key: String,
#[wasm_bindgen(getter_with_clone)]
pub sphinx_key: String,
#[wasm_bindgen(getter_with_clone)]
pub version: String,
}
#[wasm_bindgen]
impl WasmGateway {
#[wasm_bindgen(constructor)]
pub fn new(
owner: String,
host: String,
mix_port: u16,
clients_port: u16,
identity_key: String,
sphinx_key: String,
version: String,
) -> Self {
Self {
owner,
host,
mix_port,
clients_port,
identity_key,
sphinx_key,
version,
}
}
}
impl TryFrom<WasmGateway> for gateway::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmGateway) -> Result<Self, Self::Error> {
let host = gateway::Node::parse_host(&value.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = gateway::Node::extract_mix_host(&host, value.mix_port)?;
Ok(gateway::Node {
owner: value.owner,
host,
mix_host,
clients_port: value.clients_port,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(GatewayConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(GatewayConversionError::from)?,
version: value.version,
})
}
}
@@ -22,7 +22,7 @@ use crate::client::topology_control::{
};
use crate::config::{Config, DebugConfig, GatewayEndpointConfig};
use crate::error::ClientCoreError;
use crate::{config, spawn_future};
use crate::spawn_future;
use futures::channel::mpsc;
use log::{debug, info};
use nym_bandwidth_controller::BandwidthController;
@@ -39,6 +39,7 @@ use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender,
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use std::sync::Arc;
use std::time::Duration;
use tap::TapFallible;
use url::Url;
@@ -370,12 +371,11 @@ where
// the current global view of topology
async fn start_topology_refresher(
topology_provider: Box<dyn TopologyProvider>,
topology_config: config::Topology,
refresh_rate: Duration,
topology_accessor: TopologyAccessor,
mut shutdown: TaskClient,
shutdown: TaskClient,
) -> Result<(), ClientCoreError> {
let topology_refresher_config =
TopologyRefresherConfig::new(topology_config.topology_refresh_rate);
let topology_refresher_config = TopologyRefresherConfig::new(refresh_rate);
let mut topology_refresher = TopologyRefresher::new(
topology_refresher_config,
@@ -395,17 +395,8 @@ where
return Err(ClientCoreError::InsufficientNetworkTopology(err));
}
if topology_config.disable_refreshing {
// if we're not spawning the refresher, don't cause shutdown immediately
info!("The topology refesher is not going to be started");
shutdown.mark_as_success();
} else {
// don't spawn the refresher if we don't want to be refreshing the topology.
// only use the initial values obtained
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
@@ -509,7 +500,7 @@ where
);
Self::start_topology_refresher(
topology_provider,
self.debug_config.topology,
self.debug_config.topology.topology_refresh_rate,
shared_topology_accessor.clone(),
task_manager.subscribe(),
)
@@ -1,9 +1,5 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
@@ -12,14 +8,6 @@ pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
#[derive(Debug)]
pub enum InputMessage {
/// Fire an already prepared mix packets into the network.
/// No guarantees are made about it. For example no retransmssion
/// will be attempted if it gets dropped.
Premade {
msgs: Vec<MixPacket>,
lane: TransmissionLane,
},
/// The simplest message variant where no additional information is attached.
/// You're simply sending your `data` to specified `recipient` without any tagging.
///
@@ -62,19 +50,6 @@ pub enum InputMessage {
}
impl InputMessage {
pub fn new_premade(
msgs: Vec<MixPacket>,
lane: TransmissionLane,
packet_type: PacketType,
) -> Self {
let message = InputMessage::Premade { msgs, lane };
if packet_type == PacketType::Mix {
message
} else {
InputMessage::new_wrapper(message, packet_type)
}
}
pub fn new_wrapper(message: InputMessage, packet_type: PacketType) -> Self {
InputMessage::MessageWrapper {
message: Box::new(message),
@@ -142,8 +117,7 @@ impl InputMessage {
match self {
InputMessage::Regular { lane, .. }
| InputMessage::Anonymous { lane, .. }
| InputMessage::Reply { lane, .. }
| InputMessage::Premade { lane, .. } => lane,
| InputMessage::Reply { lane, .. } => lane,
InputMessage::MessageWrapper { message, .. } => message.lane(),
}
}
@@ -3,12 +3,10 @@
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver};
use crate::client::real_messages_control::message_handler::MessageHandler;
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use crate::client::replies::reply_controller::ReplyControllerSender;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
@@ -44,18 +42,6 @@ where
}
}
async fn handle_premade_packets(&mut self, packets: Vec<MixPacket>, lane: TransmissionLane) {
self.message_handler
.send_premade_mix_packets(
packets
.into_iter()
.map(|p| RealMessage::new(p, None))
.collect(),
lane,
)
.await
}
async fn handle_reply(
&mut self,
recipient_tag: AnonymousSenderTag,
@@ -126,7 +112,6 @@ where
} => {
self.handle_reply(recipient_tag, data, lane).await;
}
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
InputMessage::MessageWrapper {
message,
packet_type,
@@ -155,9 +140,6 @@ where
} => {
self.handle_reply(recipient_tag, data, lane).await;
}
InputMessage::Premade { msgs, lane } => {
self.handle_premade_packets(msgs, lane).await
}
// MessageWrappers can't be nested
InputMessage::MessageWrapper { .. } => unimplemented!(),
},
@@ -133,10 +133,7 @@ where
// send to `OutQueueControl` to eventually send to the mix network
self.message_handler
.forward_messages(
vec![RealMessage::new(
prepared_fragment.mix_packet,
Some(frag_id),
)],
vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
TransmissionLane::Retransmission,
)
.await
@@ -291,10 +291,8 @@ where
.try_prepare_single_reply_chunk_for_sending(reply_surb, chunk_clone)
.await?;
let real_messages = RealMessage::new(
prepared_fragment.mix_packet,
Some(chunk.fragment_identifier()),
);
let real_messages =
RealMessage::new(prepared_fragment.mix_packet, chunk.fragment_identifier());
let delay = prepared_fragment.total_delay;
let pending_ack =
PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request);
@@ -386,8 +384,7 @@ where
let lane = raw.0;
let fragment = raw.1;
let real_message =
RealMessage::new(prepared.mix_packet, Some(prepared.fragment_identifier));
let real_message = RealMessage::new(prepared.mix_packet, prepared.fragment_identifier);
let delay = prepared.total_delay;
let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false);
@@ -404,14 +401,6 @@ where
Ok(())
}
pub(crate) async fn send_premade_mix_packets(
&mut self,
msgs: Vec<RealMessage>,
lane: TransmissionLane,
) {
self.forward_messages(msgs, lane).await;
}
pub(crate) async fn try_send_plain_message(
&mut self,
recipient: Recipient,
@@ -455,13 +444,11 @@ where
topology,
&self.config.ack_key,
&recipient,
&packet_type,
packet_type,
)?;
let real_message = RealMessage::new(
prepared_fragment.mix_packet,
Some(fragment.fragment_identifier()),
);
let real_message =
RealMessage::new(prepared_fragment.mix_packet, fragment.fragment_identifier());
let delay = prepared_fragment.total_delay;
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
@@ -545,7 +532,7 @@ where
topology,
&self.config.ack_key,
&recipient,
&packet_type,
packet_type,
)
.unwrap();
@@ -582,7 +569,6 @@ where
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)
.unwrap()
})
@@ -602,13 +588,7 @@ where
let prepared_fragment = self
.message_preparer
.prepare_reply_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)
.prepare_reply_chunk_for_sending(chunk, topology, &self.config.ack_key, reply_surb)
.unwrap();
Ok(prepared_fragment)
@@ -121,7 +121,7 @@ where
#[derive(Debug)]
pub(crate) struct RealMessage {
mix_packet: MixPacket,
fragment_id: Option<FragmentIdentifier>,
fragment_id: FragmentIdentifier,
// TODO: add info about it being constructed with reply-surb
}
@@ -129,7 +129,7 @@ impl From<PreparedFragment> for RealMessage {
fn from(fragment: PreparedFragment) -> Self {
RealMessage {
mix_packet: fragment.mix_packet,
fragment_id: Some(fragment.fragment_identifier),
fragment_id: fragment.fragment_identifier,
}
}
}
@@ -139,7 +139,7 @@ impl RealMessage {
self.mix_packet.packet().len()
}
pub(crate) fn new(mix_packet: MixPacket, fragment_id: Option<FragmentIdentifier>) -> Self {
pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self {
RealMessage {
mix_packet,
fragment_id,
@@ -255,7 +255,7 @@ where
)
}
StreamMessage::Real(real_message) => {
(real_message.mix_packet, real_message.fragment_id)
(real_message.mix_packet, Some(real_message.fragment_id))
}
};
+1 -19
View File
@@ -3,7 +3,7 @@
use nym_config::defaults::NymNetworkDetails;
use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
use nym_sphinx::params::{PacketSize, PacketType};
use nym_sphinx::params::PacketSize;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::path::PathBuf;
@@ -217,11 +217,6 @@ impl<T> Config<T> {
self
}
pub fn with_packet_type(mut self, packet_type: PacketType) -> Self {
self.client.packet_type = Some(packet_type);
self
}
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.traffic.average_packet_delay = Duration::from_millis(10);
// basically don't really send cover messages
@@ -409,10 +404,6 @@ impl<T> Config<T> {
pub fn get_maximum_reply_key_age(&self) -> Duration {
self.debug.reply_surbs.maximum_reply_key_age
}
pub fn get_packet_type(&self) -> PacketType {
self.client.packet_type.unwrap_or(PacketType::Mix)
}
}
impl<T: NymConfig> Default for Config<T> {
@@ -527,8 +518,6 @@ pub struct Client<T> {
#[serde(skip)]
pub super_struct: PhantomData<T>,
pub packet_type: Option<PacketType>,
}
impl<T: NymConfig> Default for Client<T> {
@@ -567,7 +556,6 @@ impl<T: NymConfig> Default for Client<T> {
reply_surb_database_path: Default::default(),
nym_root_directory: T::default_root_directory(),
super_struct: Default::default(),
packet_type: Default::default(),
}
}
}
@@ -755,11 +743,6 @@ pub struct Topology {
/// did not reach its destination.
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
}
impl Default for Topology {
@@ -767,7 +750,6 @@ impl Default for Topology {
Topology {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
}
}
}
@@ -142,7 +142,6 @@ impl From<OldDebugConfigV1_1_13> for DebugConfig {
topology: Topology {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: false,
},
reply_surbs: ReplySurbs {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
@@ -210,8 +209,8 @@ impl<T, U> From<OldConfigV1_1_13<T>> for Config<U> {
database_path: value.client.database_path,
reply_surb_database_path: value.client.reply_surb_database_path,
nym_root_directory: value.client.nym_root_directory,
super_struct: PhantomData,
packet_type: Some(nym_sphinx::params::PacketType::Mix),
},
logging: value.logging,
debug: value.debug.into(),
@@ -13,8 +13,7 @@ pub mod nyxd;
pub mod signing;
pub use crate::error::ValidatorClientError;
pub use client::NymApiClient;
pub use nym_api_requests::*;
#[cfg(feature = "nyxd-client")]
pub use client::{Client, CoconutApiClient, Config};
pub use client::{Client, CoconutApiClient, Config, NymApiClient};
@@ -24,9 +24,8 @@ use nym_mixnet_contract_common::{
MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse,
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse,
PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse,
PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventResponse,
PendingEpochEventsResponse, PendingIntervalEventResponse, PendingIntervalEventsResponse,
QueryMsg as MixnetQueryMsg,
PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse,
PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
};
use serde::Deserialize;
@@ -175,16 +174,6 @@ pub trait MixnetQueryClient {
.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(
&self,
mix_id: MixId,
@@ -385,20 +374,14 @@ pub trait MixnetQueryClient {
.await
}
async fn get_pending_epoch_event(
async fn get_mixnode_details_by_identity(
&self,
event_id: EpochEventId,
) -> Result<PendingEpochEventResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvent { event_id })
.await
}
async fn get_pending_interval_event(
&self,
event_id: IntervalEventId,
) -> Result<PendingIntervalEventResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvent { event_id })
.await
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
}
async fn get_number_of_pending_events(
@@ -331,38 +331,6 @@ pub trait MixnetSigningClient {
.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> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
.await
@@ -91,21 +91,6 @@ pub trait VestingSigningClient {
.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_track_unbond_mixnode(
@@ -6,9 +6,11 @@ use clap::{Args, Subcommand};
pub mod rewards;
pub mod delegate_to_mixnode;
pub mod pledge_more;
pub mod query_for_delegations;
pub mod undelegate_from_mixnode;
pub mod vesting_delegate_to_mixnode;
pub mod vesting_pledge_more;
pub mod vesting_undelegate_from_mixnode;
#[derive(Debug, Args)]
@@ -32,4 +34,8 @@ pub enum MixnetDelegatorsCommands {
DelegateVesting(vesting_delegate_to_mixnode::Args),
/// Undelegate from a mixnode (when originally using locked tokens)
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 async fn vesting_update_config(args: Args, client: SigningClient) {
pub async fn vesting_update_config(client: SigningClient, args: Args) {
info!("Update vesting gateway config!");
let current_details = match client
@@ -45,9 +45,7 @@ pub struct Args {
pub force: bool,
}
pub async fn vesting_bond_gateway(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str) {
info!("Starting vesting gateway bonding!");
// if we're trying to bond less than 1 token
@@ -1,29 +0,0 @@
// 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,17 +4,13 @@
use clap::{Args, Subcommand};
pub mod bond_mixnode;
pub mod decrease_pledge;
pub mod families;
pub mod keys;
pub mod mixnode_bonding_sign_payload;
pub mod pledge_more;
pub mod rewards;
pub mod settings;
pub mod unbond_mixnode;
pub mod vesting_bond_mixnode;
pub mod vesting_decrease_pledge;
pub mod vesting_pledge_more;
pub mod vesting_unbond_mixnode;
#[derive(Debug, Args)]
@@ -44,12 +40,4 @@ pub enum MixnetOperatorsMixnodeCommands {
UnbondVesting(vesting_unbond_mixnode::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
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),
}
@@ -1,29 +0,0 @@
// 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,6 +1,6 @@
[package]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.4.0"
description = "Common library for the Nym mixnet contract"
rust-version = "1.62"
edition = { workspace = true }
@@ -1,16 +1,13 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{EpochEventId, EpochState, IdentityKey, MixId};
use crate::{EpochState, IdentityKey, MixId};
use contracts_common::signing::verifier::ApiVerifierError;
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
use cosmwasm_std::{Addr, Coin, Decimal};
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum MixnetContractError {
#[error("could not perform contract migration: {comment}")]
FailedMigration { comment: String },
#[error("{source}")]
StdErr {
#[from]
@@ -29,17 +26,6 @@ pub enum MixnetContractError {
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
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})")]
InsufficientDelegation { received: Coin, minimum: Coin },
@@ -204,9 +190,6 @@ pub enum MixnetContractError {
#[error("epoch duration must be > 0")]
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}.")]
RewardingValidatorMismatch {
current_validator: Addr,
@@ -243,11 +226,3 @@ pub enum MixnetContractError {
source: ApiVerifierError,
},
}
impl MixnetContractError {
pub fn inconsistent_state<S: Into<String>>(comment: S) -> Self {
MixnetContractError::InconsistentState {
comment: comment.into(),
}
}
}
@@ -15,8 +15,6 @@ pub enum MixnetEventType {
MixnodeBonding,
PendingPledgeIncrease,
PledgeIncrease,
PendingPledgeDecrease,
PledgeDecrease,
GatewayBonding,
GatewayUnbonding,
PendingMixnodeUnbonding,
@@ -60,8 +58,6 @@ impl ToString for MixnetEventType {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase",
MixnetEventType::PledgeIncrease => "pledge_increase",
MixnetEventType::PendingPledgeDecrease => "pending_pledge_decrease",
MixnetEventType::PledgeDecrease => "pledge_decrease",
MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
@@ -358,19 +354,6 @@ pub fn new_pledge_increase_event(created_at: BlockHeight, mix_id: MixId, amount:
.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 {
Event::new(MixnetEventType::MixnodeUnbonding)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
@@ -3,10 +3,7 @@
use crate::error::MixnetContractError;
use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent};
use crate::{
EpochEventId, EpochId, IntervalEventId, IntervalId, MixId, PendingEpochEventData,
PendingIntervalEventData,
};
use crate::{EpochId, IntervalId, MixId};
use cosmwasm_std::{Addr, Env};
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject};
@@ -531,30 +528,6 @@ 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)]
pub struct NumberOfPendingEventsResponse {
pub epoch_events: u32,
@@ -32,8 +32,7 @@ pub use gateway::{
};
pub use interval::{
CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse,
PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEventResponse,
PendingIntervalEventsResponse,
PendingEpochEventsResponse, PendingIntervalEventsResponse,
};
pub use mixnode::{
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
@@ -10,7 +10,7 @@ use crate::helpers::IntoBaseDecimal;
use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -37,20 +37,13 @@ impl RewardedSetNodeStatus {
pub struct MixNodeDetails {
pub bond_information: MixNodeBond,
pub rewarding_details: MixNodeRewarding,
#[serde(default)]
pub pending_changes: PendingMixNodeChanges,
}
impl MixNodeDetails {
pub fn new(
bond_information: MixNodeBond,
rewarding_details: MixNodeRewarding,
pending_changes: PendingMixNodeChanges,
) -> Self {
pub fn new(bond_information: MixNodeBond, rewarding_details: MixNodeRewarding) -> Self {
MixNodeDetails {
bond_information,
rewarding_details,
pending_changes,
}
}
@@ -80,10 +73,6 @@ impl MixNodeDetails {
pub fn total_stake(&self) -> Decimal {
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)]
@@ -343,22 +332,6 @@ impl MixNodeRewarding {
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(
&mut self,
amount: Uint128,
@@ -628,25 +601,6 @@ 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",
@@ -10,13 +10,10 @@ use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
};
use crate::{
delegation, ContractStateParams, EpochEventId, IntervalEventId, Layer, LayerAssignment, MixId,
Percent,
};
use crate::{delegation, ContractStateParams, Layer, LayerAssignment, MixId, Percent};
use crate::{Gateway, IdentityKey, MixNode};
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Coin, Decimal};
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::time::Duration;
@@ -164,13 +161,6 @@ pub enum ExecuteMsg {
PledgeMoreOnBehalf {
owner: String,
},
DecreasePledge {
decrease_by: Coin,
},
DecreasePledgeOnBehalf {
owner: String,
decrease_by: Coin,
},
UnbondMixnode {},
UnbondMixnodeOnBehalf {
owner: String,
@@ -307,10 +297,6 @@ impl ExecuteMsg {
}
ExecuteMsg::PledgeMore {} => "pledging additional tokens".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::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
@@ -520,12 +506,6 @@ pub enum QueryMsg {
limit: Option<u32>,
start_after: Option<u32>,
},
GetPendingEpochEvent {
event_id: EpochEventId,
},
GetPendingIntervalEvent {
event_id: IntervalEventId,
},
GetNumberOfPendingEvents {},
// signing-related
@@ -38,10 +38,6 @@ pub enum PendingEpochEventKind {
mix_id: MixId,
amount: Coin,
},
DecreasePledge {
mix_id: MixId,
decrease_by: Coin,
},
UnbondMixnode {
mix_id: MixId,
},
@@ -70,7 +66,7 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingIntervalEvent {
pub id: IntervalEventId,
pub id: EpochEventId,
pub event: PendingIntervalEventData,
}
@@ -1,6 +1,6 @@
[package]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.5.0"
description = "Common library for the Nym vesting contract"
edition = { workspace = true }
authors = { workspace = true }
@@ -9,7 +9,7 @@ repository = { workspace = true }
[dependencies]
cosmwasm-std = { workspace = true }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.5.0" }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.4.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
@@ -15,7 +15,6 @@ 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_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
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_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
pub const VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE: &str = "vesting_update_gateway_config";
@@ -23,7 +22,6 @@ pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
"vesting_update_mixnode_cost_params";
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_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation";
pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward";
@@ -120,10 +118,6 @@ pub fn new_vesting_pledge_more_event() -> Event {
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 {
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
}
@@ -152,10 +146,6 @@ pub fn new_track_mixnode_unbond_event() -> Event {
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 {
Event::new(TRACK_GATEWAY_UNBOND_EVENT_TYPE)
}
@@ -123,18 +123,11 @@ pub enum ExecuteMsg {
PledgeMore {
amount: Coin,
},
DecreasePledge {
amount: Coin,
},
UnbondMixnode {},
TrackUnbondMixnode {
owner: String,
amount: Coin,
},
TrackDecreasePledge {
owner: String,
amount: Coin,
},
BondGateway {
gateway: Gateway,
owner_signature: MessageSignature,
@@ -182,10 +175,8 @@ impl ExecuteMsg {
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
ExecuteMsg::PledgeMore { .. } => "VestingExecuteMsg::PledgeMore",
ExecuteMsg::DecreasePledge { .. } => "VestingExecuteMsg::DecreasePledge",
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
ExecuteMsg::TrackDecreasePledge { .. } => "VestingExecuteMsg::TrackDecreasePledge",
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
-30
View File
@@ -1,30 +0,0 @@
[package]
name = "nym-node-tester-utils"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = "0.3.28"
rand = "0.7.3"
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["macros"]}
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
nym-task = { path = "../task" }
nym-topology = { path = "../topology" }
nym-sphinx-params = { path = "../nymsphinx/params" }
# TODO: do we need the whole nymsphinx?
nym-sphinx = { path = "../nymsphinx" }
## non-wasm-only dependencies
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.log]
workspace = true
## wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
path = "../wasm-utils"
-49
View File
@@ -1,49 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::MixId;
use nym_sphinx::chunking::ChunkingError;
use nym_sphinx::receiver::MessageRecoveryError;
use nym_topology::NymTopologyError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum NetworkTestingError {
#[error(transparent)]
SerializationFailure(#[from] serde_json::Error),
#[error("could not recover received test message: {source}")]
MalformedTestMessageReceived { source: serde_json::Error },
#[error(transparent)]
InvalidTopology(#[from] NymTopologyError),
#[error("The specified mixnode (id: {mix_id}) doesn't exist")]
NonExistentMixnode { mix_id: MixId },
#[error("The specified mixnode (identity: {mix_identity}) doesn't exist")]
NonExistentMixnodeIdentity { mix_identity: String },
#[error("The specified gateway (id: {gateway_identity}) doesn't exist")]
NonExistentGateway { gateway_identity: String },
#[error("The provided test message is too long to fit in a single sphinx packet")]
TestMessageTooLong,
#[error(
"could not recover underlying data from the received packet since it was malformed: {source}"
)]
MalformedPacketReceived {
#[from]
source: MessageRecoveryError,
},
#[error("Received ack packet could not be recovered")]
UnrecoverableAck,
#[error("could not recover ack FragmentIdentifier: {source}")]
MalformedAckIdentifier { source: ChunkingError },
#[error("received a packet that could not be reconstructed into a full message with a single fragment")]
NonReconstructablePacket,
}
-46
View File
@@ -1,46 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod error;
pub mod message;
pub mod receiver;
pub mod tester;
pub use message::{Empty, TestMessage};
pub use tester::NodeTester;
// it feels wrong to redefine it, but I don't want to import the whole of contract commons just for this one type
pub(crate) type MixId = u32;
#[macro_export]
macro_rules! log_err {
($($t:tt)*) => {{
#[cfg(target_arch = "wasm32")]
{::wasm_utils::console_error!($($t)*)}
#[cfg(not(target_arch = "wasm32"))]
{::log::error!($($t)*)}
}};
}
#[macro_export]
macro_rules! log_warn {
($($t:tt)*) => {{
#[cfg(target_arch = "wasm32")]
{::wasm_utils::console_warn!($($t)*)}
#[cfg(not(target_arch = "wasm32"))]
{::log::warn!($($t)*)}
}};
}
#[macro_export]
macro_rules! log_info {
($($t:tt)*) => {{
#[cfg(target_arch = "wasm32")]
{::wasm_utils::console_log!($($t)*)}
#[cfg(not(target_arch = "wasm32"))]
{::log::info!($($t)*)}
}};
}
-99
View File
@@ -1,99 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::MixId;
use nym_sphinx::message::NymMessage;
use nym_topology::{gateway, mix};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
#[derive(Serialize, Deserialize, Hash, Clone, Copy)]
pub enum NodeType {
Mixnode(MixId),
Gateway,
}
#[derive(Serialize, Deserialize, Hash, Clone, Copy)]
pub struct Empty;
#[derive(Serialize, Deserialize, Clone)]
pub struct TestMessage<T = Empty> {
pub encoded_node_identity: String,
pub node_owner: String,
pub node_type: NodeType,
pub msg_id: u32,
pub total_msgs: u32,
// any additional fields that might be required by a specific tester.
// For example nym-api might want to attach route ids
#[serde(flatten)]
pub ext: T,
}
impl<T> TestMessage<T> {
pub fn new_mix(node: &mix::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
TestMessage {
encoded_node_identity: node.identity_key.to_base58_string(),
node_owner: node.owner.clone(),
node_type: NodeType::Mixnode(node.mix_id),
msg_id,
total_msgs,
ext,
}
}
pub fn new_gateway(node: &gateway::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
TestMessage {
encoded_node_identity: node.identity_key.to_base58_string(),
node_owner: node.owner.clone(),
node_type: NodeType::Gateway,
msg_id,
total_msgs,
ext,
}
}
pub fn as_json_string(&self) -> Result<String, NetworkTestingError>
where
T: Serialize,
{
serde_json::to_string(self).map_err(Into::into)
}
pub fn as_bytes(&self) -> Result<Vec<u8>, NetworkTestingError>
where
T: Serialize,
{
// the test messages are supposed to be rather small so we can use the good old serde_json
// (the performance penalty over bincode or custom serialization should be minimal)
serde_json::to_vec(self).map_err(Into::into)
}
pub fn try_recover(msg: NymMessage) -> Result<Self, NetworkTestingError>
where
T: DeserializeOwned,
{
let inner = msg.into_inner_data();
Self::try_recover_from_bytes(&inner)
}
pub fn try_recover_from_bytes(raw: &[u8]) -> Result<Self, NetworkTestingError>
where
T: DeserializeOwned,
{
serde_json::from_slice(raw)
.map_err(|source| NetworkTestingError::MalformedTestMessageReceived { source })
}
}
impl<T: Hash> Hash for TestMessage<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.encoded_node_identity.hash(state);
self.node_owner.hash(state);
self.node_type.hash(state);
self.ext.hash(state)
}
}
-148
View File
@@ -1,148 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::{log_err, log_info, log_warn};
use futures::channel::mpsc;
use futures::StreamExt;
use nym_crypto::asymmetric::encryption;
use nym_sphinx::message::NymMessage;
use nym_sphinx::receiver::{MessageReceiver, SphinxMessageReceiver};
use nym_sphinx::{
acknowledgements::{identifier::recover_identifier, AckKey},
chunking::fragment::FragmentIdentifier,
};
use nym_task::TaskClient;
use std::sync::Arc;
pub type ReceivedSender = mpsc::UnboundedSender<Received>;
pub type ReceivedReceiver = mpsc::UnboundedReceiver<Received>;
// simple enum containing aggregated processed results
pub enum Received {
Message(NymMessage),
Ack(FragmentIdentifier),
}
impl From<NymMessage> for Received {
fn from(value: NymMessage) -> Self {
Received::Message(value)
}
}
impl From<FragmentIdentifier> for Received {
fn from(value: FragmentIdentifier) -> Self {
Received::Ack(value)
}
}
// the 'Simple' bit comes from the fact that it expects all received messages to consist of a single `Fragment`
pub struct SimpleMessageReceiver<R: MessageReceiver = SphinxMessageReceiver> {
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
/// Structure responsible for decrypting and recovering plaintext message from received ciphertexts.
message_receiver: R,
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
received_sender: ReceivedSender,
shutdown: TaskClient,
}
impl SimpleMessageReceiver<SphinxMessageReceiver> {
pub fn new_sphinx_receiver(
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
received_sender: ReceivedSender,
shutdown: TaskClient,
) -> Self {
SimpleMessageReceiver {
local_encryption_keypair,
ack_key,
message_receiver: SphinxMessageReceiver::new(),
mixnet_message_receiver,
acks_receiver,
received_sender,
shutdown,
}
}
}
impl<R: MessageReceiver> SimpleMessageReceiver<R> {
fn forward_received<T: Into<Received>>(&self, received: T) {
// TODO: remove the unwrap once/if we do graceful shutdowns here
self.received_sender
.unbounded_send(received.into())
.expect("ReceivedReceiver has stopped receiving");
}
fn on_mixnet_message(&mut self, mut raw_message: Vec<u8>) -> Result<(), NetworkTestingError> {
let plaintext = self
.message_receiver
.recover_plaintext_from_regular_packet(
self.local_encryption_keypair.private_key(),
&mut raw_message,
)?;
let fragment = self.message_receiver.recover_fragment(plaintext)?;
let (recovered, _) = self
.message_receiver
.insert_new_fragment(fragment)?
.ok_or(NetworkTestingError::NonReconstructablePacket)?; // by definition of this receiver, the message must consist of a single fragment
self.forward_received(recovered);
Ok(())
}
fn on_ack(&mut self, raw_ack: Vec<u8>) -> Result<(), NetworkTestingError> {
let serialized_ack = recover_identifier(&self.ack_key, &raw_ack)
.ok_or(NetworkTestingError::UnrecoverableAck)?;
let frag_id = FragmentIdentifier::try_from_bytes(serialized_ack)
.map_err(|source| NetworkTestingError::MalformedAckIdentifier { source })?;
self.forward_received(frag_id);
Ok(())
}
pub async fn run(&mut self) {
while !self.shutdown.is_shutdown() {
tokio::select! {
biased;
_ = self.shutdown.recv() => {
log_info!("SimpleMessageReceiver: received shutdown")
}
mixnet_messages = self.mixnet_message_receiver.next() => {
let Some(mixnet_messages) = mixnet_messages else {
log_err!("the mixnet messages stream has terminated!");
// note: this will cause global shutdown, but we have no choice if we stopped receiving mixnet messages
break
};
for message in mixnet_messages {
if let Err(err) = self.on_mixnet_message(message) {
log_warn!("failed to process received mixnet message: {err}")
}
}
}
acks = self.acks_receiver.next() => {
let Some(acks) = acks else {
log_err!("the ack messages stream has terminated!");
// note: this will cause global shutdown, but we have no choice if we stopped receiving mixnet messages
break
};
for ack in acks {
if let Err(err) = self.on_ack(ack) {
log_warn!("failed to process received ack message: {err}")
}
}
}
}
}
log_info!("SimpleMessageReceiver: Exiting")
}
}
-203
View File
@@ -1,203 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::Empty;
use crate::MixId;
use crate::TestMessage;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::message::NymMessage;
use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::preparer::{FragmentPreparer, PreparedFragment};
use nym_sphinx_params::PacketType;
use nym_topology::{gateway, mix, NymTopology};
use rand::{CryptoRng, Rng};
use serde::Serialize;
use std::sync::Arc;
use std::time::Duration;
pub struct NodeTester<R> {
rng: R,
base_topology: NymTopology,
recipient: Recipient,
packet_size: PacketSize,
/// Average delay a data packet is going to get delay at a single mixnode.
average_packet_delay: Duration,
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
average_ack_delay: Duration,
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
/// Note that it does not include gateway hops.
num_mix_hops: u8,
// while acks are going to be ignored they still need to be constructed
// so that the gateway would be able to correctly process and forward the message
ack_key: Arc<AckKey>,
}
impl<R> NodeTester<R>
where
R: Rng + CryptoRng,
{
pub fn new(
rng: R,
base_topology: NymTopology,
recipient: Recipient,
packet_size: PacketSize,
average_packet_delay: Duration,
average_ack_delay: Duration,
ack_key: Arc<AckKey>,
) -> Self {
Self {
rng,
base_topology,
recipient,
packet_size,
average_packet_delay,
average_ack_delay,
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
ack_key,
}
}
/// Allows setting non-default number of expected mix hops in the network.
#[allow(dead_code)]
pub fn with_mix_hops(mut self, hops: u8) -> Self {
self.num_mix_hops = hops;
self
}
pub fn testable_mix_topology(&self, node: &mix::Node) -> NymTopology {
let mut topology = self.base_topology.clone();
topology.set_mixes_in_layer(node.layer as u8, vec![node.clone()]);
topology
}
pub fn testable_gateway_topology(&self, gateway: &gateway::Node) -> NymTopology {
let mut topology = self.base_topology.clone();
topology.set_gateways(vec![gateway.clone()]);
topology
}
pub fn simple_mixnode_test_packets(
&mut self,
mix: &mix::Node,
test_packets: u32,
) -> Result<Vec<PreparedFragment>, NetworkTestingError> {
self.mixnode_test_packets(mix, Empty, test_packets)
}
pub fn mixnode_test_packets<T>(
&mut self,
mix: &mix::Node,
msg_ext: T,
test_packets: u32,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
{
let ephemeral_topology = self.testable_mix_topology(mix);
let mut packets = Vec::with_capacity(test_packets as usize);
for i in 1..=test_packets {
let msg = TestMessage::new_mix(mix, i, test_packets, msg_ext.clone());
packets.push(self.create_test_packet(&msg, &ephemeral_topology)?);
}
Ok(packets)
}
pub fn existing_mixnode_test_packets<T>(
&mut self,
mix_id: MixId,
msg_ext: T,
test_packets: u32,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
{
let Some(node) = self.base_topology.find_mix(mix_id) else {
return Err(NetworkTestingError::NonExistentMixnode {mix_id})
};
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets)
}
pub fn existing_identity_mixnode_test_packets<T>(
&mut self,
encoded_mix_identity: String,
msg_ext: T,
test_packets: u32,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
{
let Some(node) = self.base_topology.find_mix_by_identity(&encoded_mix_identity) else {
return Err(NetworkTestingError::NonExistentMixnodeIdentity { mix_identity: encoded_mix_identity })
};
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets)
}
pub fn create_test_packet<T>(
&mut self,
message: &TestMessage<T>,
topology: &NymTopology,
) -> Result<PreparedFragment, NetworkTestingError>
where
T: Serialize,
{
let serialized = message.as_bytes()?;
let message = NymMessage::new_plain(serialized);
let mut fragments = self.pad_and_split_message(message, self.packet_size);
if fragments.len() != 1 {
return Err(NetworkTestingError::TestMessageTooLong);
}
// SAFETY: the unwrap here is fine as if the vec was somehow empty
// we would have returned the error when checking for its length
let fragment = fragments.pop().unwrap();
// the packet is designed to be sent from ourselves to ourselves
let address = self.recipient;
// TODO: can we avoid this arc clone?
let ack_key = Arc::clone(&self.ack_key);
Ok(self.prepare_chunk_for_sending(
fragment,
topology,
&ack_key,
&address,
&address,
&PacketType::Mix,
)?)
}
}
impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
type Rng = R;
fn rng(&mut self) -> &mut Self::Rng {
&mut self.rng
}
fn num_mix_hops(&self) -> u8 {
self.num_mix_hops
}
fn average_packet_delay(&self) -> Duration {
self.average_packet_delay
}
fn average_ack_delay(&self) -> Duration {
self.average_ack_delay
}
}
@@ -6,7 +6,7 @@ use nym_crypto::{generic_array::typenum::Unsigned, Digest};
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
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::{delays, NymPacket, SURBMaterial, SphinxError, SURB};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
@@ -173,7 +173,6 @@ impl ReplySurb {
self,
message: M,
packet_size: PacketSize,
_packet_type: PacketType,
) -> Result<(NymPacket, NymNodeRoutingAddress), ReplySurbError> {
let message_bytes = message.as_ref();
if message_bytes.len() != packet_size.plaintext_size() {
+1 -7
View File
@@ -148,13 +148,7 @@ mod packet_encoding {
node3_pk,
);
let (_, node4_pk) = crypto::keygen();
let node4 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node4_pk,
);
let route = &[node1, node2, node3, node4];
let route = &[node1, node2, node3];
let payload = vec![1; 48];
+1 -4
View File
@@ -287,10 +287,7 @@ impl PacketSize {
let outfox_packet_size = plaintext_size + OUTFOX_PACKET_OVERHEAD;
match Self::get_type(sphinx_packet_size) {
Ok(t) => Ok(t),
Err(_) => {
println!("Got Outfox!");
Self::get_type(outfox_packet_size)
}
Err(_) => Self::get_type(outfox_packet_size),
}
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ pub enum PacketType {
#[default]
Mix = 0,
/// Represents a packet that should be sent through the network as fast as possible.
/// Represents a packet that should be sent through the network as fast as possible.
Vpn = 1,
/// Abusing this to add Outfox support
+1 -1
View File
@@ -19,4 +19,4 @@ pub use nym_sphinx_params as params;
pub use nym_sphinx_types::*;
// TEMP UNTIL FURTHER REFACTORING
pub use preparer::payload::NymPayloadBuilder;
pub use preparer::payload::NymsphinxPayloadBuilder;
+151 -266
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::message::{NymMessage, ACK_OVERHEAD};
use crate::NymPayloadBuilder;
use crate::NymsphinxPayloadBuilder;
use nym_crypto::asymmetric::encryption;
use nym_crypto::Digest;
use nym_outfox::packet::OutfoxPacket;
@@ -39,226 +39,6 @@ pub struct PreparedFragment {
pub fragment_identifier: FragmentIdentifier,
}
// this is extracted into a trait with default implementation to remove duplicate code
// (which we REALLY want to avoid with crypto)
pub trait FragmentPreparer {
type Rng: CryptoRng + Rng;
fn rng(&mut self) -> &mut Self::Rng;
fn num_mix_hops(&self) -> u8;
fn average_packet_delay(&self) -> Duration;
fn average_ack_delay(&self) -> Duration;
fn generate_reply_surbs(
&mut self,
amount: usize,
topology: &NymTopology,
reply_recipient: &Recipient,
) -> Result<Vec<ReplySurb>, NymTopologyError> {
let mut reply_surbs = Vec::with_capacity(amount);
let packet_delay = self.average_packet_delay();
for _ in 0..amount {
let reply_surb =
ReplySurb::construct(self.rng(), reply_recipient, packet_delay, topology)?;
reply_surbs.push(reply_surb)
}
Ok(reply_surbs)
}
fn generate_surb_ack(
&mut self,
recipient: &Recipient,
fragment_id: FragmentIdentifier,
topology: &NymTopology,
ack_key: &AckKey,
) -> Result<SurbAck, NymTopologyError> {
let ack_delay = self.average_ack_delay();
SurbAck::construct(
self.rng(),
recipient,
ack_key,
fragment_id.to_bytes(),
ack_delay,
topology,
)
}
/// The procedure is as follows:
/// For each fragment:
/// - compute SURB_ACK
/// - generate (x, g^x)
/// - obtain key k from the reply-surb which was computed as follows:
/// k = KDF(remote encryption key ^ x) this is equivalent to KDF( dh(remote, x) )
/// - compute v_b = AES-128-CTR(k, serialized_fragment)
/// - compute vk_b = H(k) || v_b
/// - compute sphinx_plaintext = SURB_ACK || H(k) || v_b
/// - compute sphinx_packet by applying the reply surb on the sphinx_plaintext
fn prepare_reply_chunk_for_sending(
&mut self,
fragment: Fragment,
topology: &NymTopology,
ack_key: &AckKey,
reply_surb: ReplySurb,
packet_sender: &Recipient,
packet_type: PacketType,
) -> Result<PreparedFragment, NymTopologyError> {
// each reply attaches the digest of the encryption key so that the recipient could
// lookup correct key for decryption,
let reply_overhead = ReplySurbKeyDigestAlgorithm::output_size();
let expected_plaintext = fragment.serialized_size() + ACK_OVERHEAD + reply_overhead;
// the reason we're unwrapping (or rather 'expecting') here rather than handling the error
// more gracefully is that this error should never be reached as it implies incorrect chunking
let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext)
.expect("the message has been incorrectly fragmented");
// this is not going to be accurate by any means. but that's the best estimation we can do
let expected_forward_delay = Delay::new_from_millis(
(self.average_packet_delay().as_millis() * self.num_mix_hops() as u128) as u64,
);
let fragment_identifier = fragment.fragment_identifier();
// create an ack
let surb_ack =
self.generate_surb_ack(packet_sender, fragment_identifier, topology, ack_key)?;
let ack_delay = surb_ack.expected_total_delay();
let packet_payload = match NymPayloadBuilder::new(fragment, surb_ack)
.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
// and we just very carefully constructed a (presumably) valid one
let (sphinx_packet, first_hop_address) = reply_surb
.apply_surb(packet_payload, packet_size, packet_type)
.unwrap();
Ok(PreparedFragment {
// the round-trip delay is the sum of delays of all hops on the forward route as
// well as the total delay of the ack packet.
// we don't know the delays inside the reply surbs so we use best-effort estimation from our poisson distribution
total_delay: expected_forward_delay + ack_delay,
mix_packet: MixPacket::new(first_hop_address, sphinx_packet, Default::default()),
fragment_identifier,
})
}
/// Tries to convert this [`Fragment`] into a [`SphinxPacket`] that can be sent through the Nym mix-network,
/// such that it contains required SURB-ACK and public component of the ephemeral key used to
/// derive the shared key.
/// Also all the data, apart from the said public component, is encrypted with an ephemeral shared key.
/// This method can fail if the provided network topology is invalid.
/// It returns total expected delay as well as the [`SphinxPacket`] (including first hop address)
/// to be sent through the network.
///
/// The procedure is as follows:
/// For each fragment:
/// - compute SURB_ACK
/// - generate (x, g^x)
/// - compute k = KDF(remote encryption key ^ x) this is equivalent to KDF( dh(remote, x) )
/// - compute v_b = AES-128-CTR(k, serialized_fragment)
/// - compute vk_b = g^x || v_b
/// - compute sphinx_plaintext = SURB_ACK || g^x || v_b
/// - compute sphinx_packet = Sphinx(recipient, sphinx_plaintext)
fn prepare_chunk_for_sending(
&mut self,
fragment: Fragment,
topology: &NymTopology,
ack_key: &AckKey,
packet_sender: &Recipient,
packet_recipient: &Recipient,
packet_type: &PacketType,
) -> Result<PreparedFragment, NymTopologyError> {
// 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
// the packet encryption key
let non_reply_overhead = encryption::PUBLIC_KEY_SIZE;
let expected_plaintext = fragment.serialized_size() + ACK_OVERHEAD + non_reply_overhead;
// the reason we're unwrapping (or rather 'expecting') here rather than handling the error
// more gracefully is that this error should never be reached as it implies incorrect chunking
let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext)
.expect("the message has been incorrectly fragmented");
let fragment_identifier = fragment.fragment_identifier();
// create an ack
let surb_ack =
self.generate_surb_ack(packet_sender, fragment_identifier, topology, ack_key)?;
let ack_delay = surb_ack.expected_total_delay();
let packet_payload = match NymPayloadBuilder::new(fragment, surb_ack)
.build_regular(self.rng(), packet_recipient.encryption_key())
{
Ok(payload) => payload,
Err(_e) => return Err(NymTopologyError::PayloadBuilder),
};
// generate pseudorandom route for the packet
let hops = self.num_mix_hops();
let route =
topology.random_route_to_gateway(self.rng(), hops, packet_recipient.gateway())?;
let destination = packet_recipient.as_sphinx_destination();
// including set of delays
let delays =
delays::generate_from_average_duration(route.len(), self.average_packet_delay());
// create the actual sphinx packet here. With valid route and correct payload size,
// there's absolutely no reason for this call to fail.
let packet = match packet_type {
PacketType::Outfox => NymPacket::Outfox(OutfoxPacket::build(
packet_payload,
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
let first_hop_address =
NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap();
Ok(PreparedFragment {
// the round-trip delay is the sum of delays of all hops on the forward route as
// well as the total delay of the ack packet.
// note that the last hop of the packet is a gateway that does not do any delays
total_delay: delays.iter().take(delays.len() - 1).sum::<Delay>() + ack_delay,
mix_packet: MixPacket::new(first_hop_address, packet, Default::default()),
fragment_identifier,
})
}
fn pad_and_split_message(
&mut self,
message: NymMessage,
packet_size: PacketSize,
) -> Vec<Fragment> {
let plaintext_per_packet = message.available_sphinx_plaintext_per_packet(packet_size);
message
.pad_to_full_packet_lengths(plaintext_per_packet)
.split_into_fragments(self.rng(), plaintext_per_packet)
}
}
/// Prepares the message that is to be sent through the mix network by attaching
/// an optional reply-SURB, padding it to appropriate length, encrypting its content,
/// and chunking into appropriate size [`Fragment`]s.
@@ -332,57 +112,178 @@ where
Ok(reply_surbs)
}
/// The procedure is as follows:
/// For each fragment:
/// - compute SURB_ACK
/// - generate (x, g^x)
/// - obtain key k from the reply-surb which was computed as follows:
/// k = KDF(remote encryption key ^ x) this is equivalent to KDF( dh(remote, x) )
/// - compute v_b = AES-128-CTR(k, serialized_fragment)
/// - compute vk_b = H(k) || v_b
/// - compute sphinx_plaintext = SURB_ACK || H(k) || v_b
/// - compute sphinx_packet by applying the reply surb on the sphinx_plaintext
pub fn prepare_reply_chunk_for_sending(
&mut self,
fragment: Fragment,
topology: &NymTopology,
ack_key: &AckKey,
reply_surb: ReplySurb,
packet_type: PacketType,
) -> Result<PreparedFragment, NymTopologyError> {
let sender = self.sender_address;
// each reply attaches the digest of the encryption key so that the recipient could
// lookup correct key for decryption,
let reply_overhead = ReplySurbKeyDigestAlgorithm::output_size();
let expected_plaintext = fragment.serialized_size() + ACK_OVERHEAD + reply_overhead;
<Self as FragmentPreparer>::prepare_reply_chunk_for_sending(
self,
fragment,
topology,
ack_key,
reply_surb,
&sender,
packet_type,
)
// the reason we're unwrapping (or rather 'expecting') here rather than handling the error
// more gracefully is that this error should never be reached as it implies incorrect chunking
let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext)
.expect("the message has been incorrectly fragmented");
// this is not going to be accurate by any means. but that's the best estimation we can do
let expected_forward_delay = Delay::new_from_millis(
(self.average_packet_delay.as_millis() * self.num_mix_hops as u128) as u64,
);
let fragment_identifier = fragment.fragment_identifier();
// create an ack
let surb_ack = self.generate_surb_ack(fragment_identifier, topology, ack_key)?;
let ack_delay = surb_ack.expected_total_delay();
let packet_payload = match NymsphinxPayloadBuilder::new(fragment, surb_ack)
.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
// and we just very carefully constructed a (presumably) valid one
let (sphinx_packet, first_hop_address) =
reply_surb.apply_surb(packet_payload, packet_size).unwrap();
Ok(PreparedFragment {
// the round-trip delay is the sum of delays of all hops on the forward route as
// well as the total delay of the ack packet.
// we don't know the delays inside the reply surbs so we use best-effort estimation from our poisson distribution
total_delay: expected_forward_delay + ack_delay,
mix_packet: MixPacket::new(first_hop_address, sphinx_packet, Default::default()),
fragment_identifier,
})
}
/// Tries to convert this [`Fragment`] into a [`SphinxPacket`] that can be sent through the Nym mix-network,
/// such that it contains required SURB-ACK and public component of the ephemeral key used to
/// derive the shared key.
/// Also all the data, apart from the said public component, is encrypted with an ephemeral shared key.
/// This method can fail if the provided network topology is invalid.
/// It returns total expected delay as well as the [`SphinxPacket`] (including first hop address)
/// to be sent through the network.
///
/// The procedure is as follows:
/// For each fragment:
/// - compute SURB_ACK
/// - generate (x, g^x)
/// - compute k = KDF(remote encryption key ^ x) this is equivalent to KDF( dh(remote, x) )
/// - compute v_b = AES-128-CTR(k, serialized_fragment)
/// - compute vk_b = g^x || v_b
/// - compute sphinx_plaintext = SURB_ACK || g^x || v_b
/// - compute sphinx_packet = Sphinx(recipient, sphinx_plaintext)
pub fn prepare_chunk_for_sending(
&mut self,
fragment: Fragment,
topology: &NymTopology,
ack_key: &AckKey,
packet_recipient: &Recipient,
packet_type: &PacketType,
packet_type: PacketType,
) -> Result<PreparedFragment, NymTopologyError> {
let sender = self.sender_address;
// 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
// the packet encryption key
let non_reply_overhead = encryption::PUBLIC_KEY_SIZE;
let expected_plaintext = fragment.serialized_size() + ACK_OVERHEAD + non_reply_overhead;
<Self as FragmentPreparer>::prepare_chunk_for_sending(
self,
fragment,
topology,
ack_key,
&sender,
packet_recipient,
packet_type,
)
// the reason we're unwrapping (or rather 'expecting') here rather than handling the error
// more gracefully is that this error should never be reached as it implies incorrect chunking
let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext)
.expect("the message has been incorrectly fragmented");
let fragment_identifier = fragment.fragment_identifier();
// create an ack
let surb_ack = self.generate_surb_ack(fragment_identifier, topology, ack_key)?;
let ack_delay = surb_ack.expected_total_delay();
let packet_payload = match NymsphinxPayloadBuilder::new(fragment, surb_ack)
.build_regular(&mut self.rng, packet_recipient.encryption_key())
{
Ok(payload) => payload,
Err(_e) => return Err(NymTopologyError::PayloadBuilder),
};
// generate pseudorandom route for the packet
let route = topology.random_route_to_gateway(
&mut self.rng,
self.num_mix_hops,
packet_recipient.gateway(),
)?;
let destination = packet_recipient.as_sphinx_destination();
// including set of delays
let delays = delays::generate_from_average_duration(route.len(), self.average_packet_delay);
// create the actual sphinx packet here. With valid route and correct payload size,
// there's absolutely no reason for this call to fail.
let sphinx_packet = match packet_type {
PacketType::Outfox => NymPacket::Outfox(OutfoxPacket::build(
packet_payload,
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
let first_hop_address =
NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap();
Ok(PreparedFragment {
// the round-trip delay is the sum of delays of all hops on the forward route as
// well as the total delay of the ack packet.
// note that the last hop of the packet is a gateway that does not do any delays
total_delay: delays.iter().take(delays.len() - 1).sum::<Delay>() + ack_delay,
mix_packet: MixPacket::new(first_hop_address, sphinx_packet, Default::default()),
fragment_identifier,
})
}
/// Construct an acknowledgement SURB for the given [`FragmentIdentifier`]
pub fn generate_surb_ack(
fn generate_surb_ack(
&mut self,
fragment_id: FragmentIdentifier,
topology: &NymTopology,
ack_key: &AckKey,
) -> Result<SurbAck, NymTopologyError> {
let sender = self.sender_address;
<Self as FragmentPreparer>::generate_surb_ack(self, &sender, fragment_id, topology, ack_key)
SurbAck::construct(
&mut self.rng,
&self.sender_address,
ack_key,
fragment_id.to_bytes(),
self.average_ack_delay,
topology,
)
}
pub fn pad_and_split_message(
@@ -390,27 +291,11 @@ where
message: NymMessage,
packet_size: PacketSize,
) -> Vec<Fragment> {
<Self as FragmentPreparer>::pad_and_split_message(self, message, packet_size)
}
}
let plaintext_per_packet = message.available_sphinx_plaintext_per_packet(packet_size);
impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
type Rng = R;
fn rng(&mut self) -> &mut Self::Rng {
&mut self.rng
}
fn num_mix_hops(&self) -> u8 {
self.num_mix_hops
}
fn average_packet_delay(&self) -> Duration {
self.average_packet_delay
}
fn average_ack_delay(&self) -> Duration {
self.average_ack_delay
message
.pad_to_full_packet_lengths(plaintext_per_packet)
.split_into_fragments(&mut self.rng, plaintext_per_packet)
}
}
+9 -9
View File
@@ -14,21 +14,21 @@ use nym_sphinx_params::{
};
use rand::{CryptoRng, RngCore};
pub struct NymPayloadBuilder {
pub struct NymsphinxPayloadBuilder {
fragment: Fragment,
surb_ack: SurbAck,
}
impl NymPayloadBuilder {
impl NymsphinxPayloadBuilder {
pub fn new(fragment: Fragment, surb_ack: SurbAck) -> Self {
NymPayloadBuilder { fragment, surb_ack }
NymsphinxPayloadBuilder { fragment, surb_ack }
}
fn build<C>(
self,
packet_encryption_key: &CipherKey<C>,
variant_data: impl IntoIterator<Item = u8>,
) -> Result<NymPayload, SurbAckRecoveryError>
) -> Result<NymsphinxPayload, SurbAckRecoveryError>
where
C: StreamCipher + KeyIvInit,
{
@@ -46,7 +46,7 @@ impl NymPayloadBuilder {
// where variant-specific data is as follows:
// 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
Ok(NymPayload(
Ok(NymsphinxPayload(
surb_ack_bytes
.into_iter()
.chain(variant_data.into_iter())
@@ -58,7 +58,7 @@ impl NymPayloadBuilder {
pub fn build_reply(
self,
packet_encryption_key: &SurbEncryptionKey,
) -> Result<NymPayload, SurbAckRecoveryError> {
) -> Result<NymsphinxPayload, SurbAckRecoveryError> {
let key_digest = packet_encryption_key.compute_digest();
self.build::<ReplySurbEncryptionAlgorithm>(
packet_encryption_key.inner(),
@@ -70,7 +70,7 @@ impl NymPayloadBuilder {
self,
rng: &mut R,
recipient_encryption_key: &encryption::PublicKey,
) -> Result<NymPayload, SurbAckRecoveryError>
) -> Result<NymsphinxPayload, SurbAckRecoveryError>
where
R: RngCore + CryptoRng,
{
@@ -91,9 +91,9 @@ impl NymPayloadBuilder {
// the actual byte data that will be put into the sphinx packet paylaod.
// no more transformations are going to happen to it
// TODO: use that fact for some better compile time assertions
pub struct NymPayload(Vec<u8>);
pub struct NymsphinxPayload(Vec<u8>);
impl AsRef<[u8]> for NymPayload {
impl AsRef<[u8]> for NymsphinxPayload {
fn as_ref(&self) -> &[u8] {
&self.0
}
+2 -2
View File
@@ -218,7 +218,7 @@ mod message_receiver {
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::Layer;
use nym_topology::{gateway, mix, NymTopology};
use std::collections::BTreeMap;
use std::collections::HashMap;
// TODO: is it somehow maybe possible to move it to `topology` and have if conditionally
// available to other modules?
@@ -226,7 +226,7 @@ mod message_receiver {
/// tests requiring instance of topology.
#[allow(dead_code)]
fn topology_fixture() -> NymTopology {
let mut mixes = BTreeMap::new();
let mut mixes = HashMap::new();
mixes.insert(
1,
vec![mix::Node {
+1 -3
View File
@@ -1,9 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use nym_outfox::{
constants::MIX_PARAMS_LEN, constants::OUTFOX_PACKET_OVERHEAD, error::OutfoxError,
};
pub use nym_outfox::{error::OutfoxError, format::MIX_PARAMS_LEN, packet::OUTFOX_PACKET_OVERHEAD};
// re-exporting types and constants available in sphinx
use nym_outfox::packet::OutfoxPacket;
pub use sphinx_packet::{
+7 -23
View File
@@ -5,12 +5,17 @@ use crate::config::{Config, Socks5};
use crate::error::Socks5ClientCoreError;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::NymSocksServer,
server::SphinxSocksServer,
};
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_bandwidth_controller::BandwidthController;
#[cfg(target_os = "android")]
use nym_client_core::client::base_client::helpers::setup_empty_reply_surb_backend;
#[cfg(not(target_os = "android"))]
use nym_client_core::client::base_client::non_wasm_helpers;
use nym_client_core::client::base_client::{
BaseClientBuilder, ClientInput, ClientOutput, ClientState,
};
@@ -18,18 +23,11 @@ use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use nym_credential_storage::storage::Storage;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
use nym_task::{TaskClient, TaskManager};
use nym_validator_client::nyxd::QueryNyxdClient;
use nym_validator_client::Client;
use std::error::Error;
#[cfg(target_os = "android")]
use nym_client_core::client::base_client::helpers::setup_empty_reply_surb_backend;
#[cfg(not(target_os = "android"))]
use nym_client_core::client::base_client::non_wasm_helpers;
use nym_client_core::config::DebugConfig;
pub mod config;
pub mod error;
pub mod socks;
@@ -102,16 +100,13 @@ impl NymClient {
BandwidthController::new(storage, client)
}
#[allow(clippy::too_many_arguments)]
pub fn start_socks5_listener(
socks5_config: &Socks5,
debug_config: DebugConfig,
client_input: ClientInput,
client_output: ClientOutput,
client_status: ClientState,
self_address: Recipient,
shutdown: TaskClient,
packet_type: PacketType,
) {
info!("Starting socks5 listener...");
let auth_methods = vec![AuthenticationMethods::NoAuth as u8];
@@ -131,20 +126,14 @@ impl NymClient {
..
} = client_status;
let packet_size = debug_config
.traffic
.secondary_packet_size
.unwrap_or(debug_config.traffic.primary_packet_size);
let authenticator = Authenticator::new(auth_methods, allowed_users);
let mut sphinx_socks = NymSocksServer::new(
let mut sphinx_socks = SphinxSocksServer::new(
socks5_config.get_listening_port(),
authenticator,
socks5_config.get_provider_mix_address(),
self_address,
shared_lane_queue_lengths,
socks::client::Config::new(
packet_size,
socks5_config.get_provider_interface_version(),
socks5_config.get_socks5_protocol_version(),
socks5_config.get_send_anonymously(),
@@ -152,7 +141,6 @@ impl NymClient {
socks5_config.get_per_request_surbs(),
),
shutdown.clone(),
packet_type,
);
nym_task::spawn_with_report_error(
async move {
@@ -265,17 +253,13 @@ impl NymClient {
let client_output = started_client.client_output.register_consumer();
let client_state = started_client.client_state;
info!("{:?}", self.config.get_base().get_packet_type());
Self::start_socks5_listener(
self.config.get_socks5(),
*self.config.get_debug_settings(),
client_input,
client_output,
client_state,
self_address,
started_client.task_manager.subscribe(),
self.config.get_base().get_packet_type(),
);
info!("Client startup finished!");
@@ -17,7 +17,6 @@ use nym_socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketSize;
use nym_sphinx::params::PacketType;
use nym_task::connections::{LaneQueueLengths, TransmissionLane};
use nym_task::TaskClient;
@@ -133,7 +132,6 @@ impl AsyncWrite for StreamState {
#[derive(Debug, Copy, Clone)]
pub(crate) struct Config {
biggest_packet_size: PacketSize,
provider_interface_version: ProviderInterfaceVersion,
socks5_protocol_version: Socks5ProtocolVersion,
use_surbs_for_responses: bool,
@@ -143,7 +141,6 @@ pub(crate) struct Config {
impl Config {
pub(crate) fn new(
biggest_packet_size: PacketSize,
provider_interface_version: ProviderInterfaceVersion,
socks5_protocol_version: Socks5ProtocolVersion,
use_surbs_for_responses: bool,
@@ -151,7 +148,6 @@ impl Config {
per_request_surbs: u32,
) -> Self {
Self {
biggest_packet_size,
provider_interface_version,
socks5_protocol_version,
use_surbs_for_responses,
@@ -421,9 +417,6 @@ impl SocksClient {
remote_proxy_target,
conn_receiver,
input_sender,
// FIXME: this does NOT include overhead due to acks or chunking
// (so actual true plaintext is smaller)
self.config.biggest_packet_size.plaintext_size(),
connection_id,
Some(self.lane_queue_lengths.clone()),
self.shutdown_listener.clone(),
+6 -11
View File
@@ -10,7 +10,6 @@ use nym_client_core::client::{
};
use nym_socks5_proxy_helpers::connection_controller::Controller;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
use nym_task::connections::{ConnectionCommandSender, LaneQueueLengths};
use nym_task::TaskClient;
use std::net::SocketAddr;
@@ -18,7 +17,7 @@ use tap::TapFallible;
use tokio::net::TcpListener;
/// A Socks5 server that listens for connections.
pub struct NymSocksServer {
pub struct SphinxSocksServer {
authenticator: Authenticator,
listening_address: SocketAddr,
service_provider: Recipient,
@@ -26,12 +25,10 @@ pub struct NymSocksServer {
client_config: client::Config,
lane_queue_lengths: LaneQueueLengths,
shutdown: TaskClient,
packet_type: PacketType,
}
impl NymSocksServer {
impl SphinxSocksServer {
/// Create a new SphinxSocks instance
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
port: u16,
authenticator: Authenticator,
@@ -40,13 +37,12 @@ impl NymSocksServer {
lane_queue_lengths: LaneQueueLengths,
client_config: client::Config,
shutdown: TaskClient,
packet_type: PacketType,
) -> Self {
// hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can
// just modify the config
let ip = "127.0.0.1";
info!("Listening on {}:{}", ip, port);
NymSocksServer {
SphinxSocksServer {
authenticator,
listening_address: format!("{ip}:{port}").parse().unwrap(),
service_provider,
@@ -54,7 +50,6 @@ impl NymSocksServer {
client_config,
lane_queue_lengths,
shutdown,
packet_type,
}
}
@@ -109,7 +104,7 @@ impl NymSocksServer {
&self.self_address,
self.lane_queue_lengths.clone(),
self.shutdown.clone(),
Some(self.packet_type)
None
);
tokio::spawn(async move {
@@ -125,8 +120,8 @@ impl NymSocksServer {
});
},
_ = self.shutdown.recv() => {
log::trace!("NymSocksServer: Received shutdown");
log::debug!("NymSocksServer: Exiting");
log::trace!("SphinxSocksServer: Received shutdown");
log::debug!("SphinxSocksServer: Exiting");
return Ok(());
}
}
@@ -1,36 +1,208 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bytes::Bytes;
use bytes::{BufMut, Bytes, BytesMut};
use futures::Stream;
use std::cell::RefCell;
use std::future::Future;
use std::io;
use std::ops::DerefMut;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::AsyncRead;
use tokio::time::{sleep, Duration, Instant, Sleep};
use tokio_util::io::poll_read_buf;
// note, min_capacity doesn't mean we're going to always read at least this amount of data,
// it defines the smallest allowed (by yours truly) upper bound
const MIN_CAPACITY: usize = 16 * 1024;
const DEFAULT_CAPACITY: usize = 64 * 1024;
const MAX_READ_AMOUNT: usize = 500 * 1000; // 0.5MB
const GRACE_DURATION: Duration = Duration::from_millis(1);
pub struct AvailableReader<R> {
inner: tokio_util::io::ReaderStream<R>,
pub struct AvailableReader<'a, R: AsyncRead + Unpin> {
// TODO: come up with a way to avoid using RefCell (not sure if possible though due to having to
// mutably borrow both inner reader and buffer at the same time)
buf: RefCell<BytesMut>,
inner: RefCell<&'a mut R>,
grace_period: Option<Pin<Box<Sleep>>>,
}
impl<R: AsyncRead> AvailableReader<R> {
pub fn new(reader: R, capacity: Option<usize>) -> Self {
let capacity = capacity.unwrap_or(DEFAULT_CAPACITY).max(MIN_CAPACITY);
impl<'a, R> AvailableReader<'a, R>
where
R: AsyncRead + Unpin,
{
const BUF_INCREMENT: usize = 4096;
pub fn new(reader: &'a mut R) -> Self {
AvailableReader {
inner: tokio_util::io::ReaderStream::with_capacity(reader, capacity),
buf: RefCell::new(BytesMut::with_capacity(Self::BUF_INCREMENT)),
inner: RefCell::new(reader),
grace_period: Some(Box::pin(sleep(GRACE_DURATION))),
}
}
}
impl<R: AsyncRead + Unpin> Stream for AvailableReader<R> {
impl<'a, R: AsyncRead + Unpin> Stream for AvailableReader<'a, R> {
type Item = io::Result<Bytes>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.inner).poll_next(cx)
// if we have no space in buffer left - expand it
if !self.buf.borrow().has_remaining_mut() {
self.buf.borrow_mut().reserve(Self::BUF_INCREMENT);
}
// note: poll_read_buf calls `buf.advance_mut(n)`
let poll_res = poll_read_buf(
Pin::new(self.inner.borrow_mut().deref_mut()),
cx,
self.buf.borrow_mut().deref_mut(),
);
match poll_res {
Poll::Pending => {
// there's nothing for us here, just return whatever we have (assuming we read anything!)
if self.buf.borrow().is_empty() {
Poll::Pending
} else {
// if exists - check grace period
if let Some(grace_period) = self.grace_period.as_mut() {
if Pin::new(grace_period).poll(cx).is_pending() {
return Poll::Pending;
}
}
let buf = self.buf.replace(BytesMut::new());
Poll::Ready(Some(Ok(buf.freeze())))
}
}
Poll::Ready(Err(err)) => Poll::Ready(Some(Err(err))),
Poll::Ready(Ok(n)) => {
// if exists - reset grace period
if let Some(grace_period) = self.grace_period.as_mut() {
let now = Instant::now();
grace_period.as_mut().reset(now + GRACE_DURATION);
}
// if we read a non-0 amount, we're not done yet!
if n == 0 {
let buf = self.buf.replace(BytesMut::new());
if !buf.is_empty() {
Poll::Ready(Some(Ok(buf.freeze())))
} else {
Poll::Ready(None)
}
} else {
// tell the waker we should be polled again!
cx.waker().wake_by_ref();
// if we reached our maximum amount - return it
let read_bytes_len = self.buf.borrow().len();
if read_bytes_len >= MAX_READ_AMOUNT {
let buf = self.buf.replace(BytesMut::new());
return Poll::Ready(Some(Ok(buf.freeze())));
}
Poll::Pending
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::{poll, StreamExt};
use std::io::Cursor;
use std::time::Duration;
use tokio::io::AsyncReadExt;
use tokio_test::assert_pending;
#[tokio::test]
async fn available_reader_reads_all_available_data_smaller_than_its_buf() {
let data = vec![42u8; 100];
let mut reader = Cursor::new(data.clone());
let mut available_reader = AvailableReader::new(&mut reader);
let read_data = available_reader.next().await.unwrap().unwrap();
assert_eq!(read_data, data);
assert!(available_reader.next().await.is_none());
}
#[tokio::test]
async fn available_reader_reads_all_available_data_bigger_than_its_buf() {
let data = vec![42u8; AvailableReader::<Cursor<Vec<u8>>>::BUF_INCREMENT + 100];
let mut reader = Cursor::new(data.clone());
let mut available_reader = AvailableReader::new(&mut reader);
let read_data = available_reader.next().await.unwrap().unwrap();
assert_eq!(read_data, data);
assert!(available_reader.next().await.is_none());
}
#[tokio::test]
async fn available_reader_will_not_wait_for_more_data_if_it_already_has_some() {
let first_data_chunk = vec![42u8; 100];
let second_data_chunk = vec![123u8; 100];
let mut reader_mock = tokio_test::io::Builder::new()
.read(&first_data_chunk)
.wait(Duration::from_millis(100)) // delay is irrelevant, what matters is that we don't get everything immediately
.read(&second_data_chunk)
.build();
let mut available_reader = AvailableReader::new(&mut reader_mock);
let read_data = available_reader.next().await.unwrap().unwrap();
assert_eq!(read_data, first_data_chunk);
assert_pending!(poll!(available_reader.next()));
// before dropping the mock, we need to empty it
let mut buf = vec![0u8; second_data_chunk.len()];
assert_eq!(reader_mock.read(&mut buf).await.unwrap(), 100);
}
#[tokio::test]
async fn available_reader_will_wait_for_more_data_if_it_doesnt_have_anything() {
let data = vec![42u8; 100];
let mut reader_mock = tokio_test::io::Builder::new()
.wait(Duration::from_millis(100))
.read(&data)
.build();
let mut available_reader = AvailableReader::new(&mut reader_mock);
let read_data = available_reader.next().await.unwrap().unwrap();
assert_eq!(read_data, data);
assert!(available_reader.next().await.is_none());
}
// perhaps the issue of tokio io builder will be resolved in tokio 0.3?
// #[tokio::test]
// async fn available_reader_will_wait_for_more_data_if_its_within_grace_period() {
// let first_data_chunk = vec![42u8; 100];
// let second_data_chunk = vec![123u8; 100];
//
// let combined_chunks: Vec<_> = first_data_chunk
// .iter()
// .cloned()
// .chain(second_data_chunk.iter().cloned())
// .collect();
//
// let mut reader_mock = tokio_test::io::Builder::new()
// .read(&first_data_chunk)
// .wait(Duration::from_millis(2))
// .read(&second_data_chunk)
// .build();
//
// let mut available_reader = AvailableReader {
// buf: RefCell::new(BytesMut::with_capacity(4096)),
// inner: RefCell::new(&mut reader_mock),
// grace_period: Some(delay_for(Duration::from_millis(5))),
// };
//
// let read_data = available_reader.next().await.unwrap().unwrap();
//
// assert_eq!(read_data, combined_chunks);
// assert!(available_reader.next().await.is_none())
// }
}
@@ -4,7 +4,6 @@
use super::MixProxySender;
use super::SHUTDOWN_TIMEOUT;
use crate::available_reader::AvailableReader;
use crate::proxy_runner::KEEPALIVE_INTERVAL;
use bytes::Bytes;
use futures::FutureExt;
use futures::StreamExt;
@@ -36,23 +35,6 @@ async fn send_empty_close<F, S>(
.expect("BatchRealMessageReceiver has stopped receiving!");
}
async fn send_empty_keepalive<F, S>(
connection_id: ConnectionId,
message_sender: &mut OrderedMessageSender,
mix_sender: &MixProxySender<S>,
adapter_fn: F,
) where
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
S: Debug,
{
log::trace!("Sending keepalive for connection: {connection_id}");
let ordered_msg = message_sender.wrap_message(Vec::new()).into_bytes();
mix_sender
.send(adapter_fn(connection_id, ordered_msg, false))
.await
.expect("BatchRealMessageReceiver has stopped receiving!");
}
#[allow(clippy::too_many_arguments)]
async fn deal_with_data<F, S>(
read_data: Option<io::Result<Bytes>>,
@@ -185,7 +167,6 @@ pub(super) async fn run_inbound<F, S>(
remote_source_address: String,
connection_id: ConnectionId,
mix_sender: MixProxySender<S>,
available_plaintext_per_mix_packet: usize,
adapter_fn: F,
shutdown_notify: Arc<Notify>,
lane_queue_lengths: Option<LaneQueueLengths>,
@@ -195,16 +176,12 @@ where
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static,
S: Debug,
{
// TODO: this multiplication by 4 is completely arbitrary here
let mut available_reader =
AvailableReader::new(&mut reader, Some(available_plaintext_per_mix_packet * 4));
let mut available_reader = AvailableReader::new(&mut reader);
let mut message_sender = OrderedMessageSender::new();
let shutdown_future = shutdown_notify.notified().then(|_| sleep(SHUTDOWN_TIMEOUT));
tokio::pin!(shutdown_future);
let mut keepalive_timer = tokio::time::interval(KEEPALIVE_INTERVAL);
loop {
select! {
read_data = &mut available_reader.next() => {
@@ -220,10 +197,6 @@ where
).await {
break
}
keepalive_timer.reset();
}
_ = keepalive_timer.tick() => {
send_empty_keepalive(connection_id, &mut message_sender, &mix_sender, &adapter_fn).await;
}
_ = &mut shutdown_future => {
debug!(
@@ -1,4 +1,4 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::connection_controller::ConnectionReceiver;
@@ -15,10 +15,6 @@ mod outbound;
// TODO: make this configurable
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(30);
// Send empty keepalive messages regurarly to keep the connection alive. This should be smaller
// than [`MIX_TTL`].
const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(60);
#[derive(Debug)]
pub struct ProxyMessage {
pub data: Vec<u8>,
@@ -53,8 +49,6 @@ pub struct ProxyRunner<S> {
connection_id: ConnectionId,
lane_queue_lengths: Option<LaneQueueLengths>,
available_plaintext_per_mix_packet: usize,
// Listens to shutdown commands from higher up
shutdown_listener: TaskClient,
}
@@ -70,7 +64,6 @@ where
remote_source_address: String,
mix_receiver: ConnectionReceiver,
mix_sender: MixProxySender<S>,
available_plaintext_per_mix_packet: usize,
connection_id: ConnectionId,
lane_queue_lengths: Option<LaneQueueLengths>,
shutdown_listener: TaskClient,
@@ -83,7 +76,6 @@ where
remote_source_address,
connection_id,
lane_queue_lengths,
available_plaintext_per_mix_packet,
shutdown_listener,
}
}
@@ -104,7 +96,6 @@ where
self.remote_source_address.clone(),
self.connection_id,
self.mix_sender.clone(),
self.available_plaintext_per_mix_packet,
adapter_fn,
Arc::clone(&shutdown_notify),
self.lane_queue_lengths.clone(),
+1 -1
View File
@@ -11,4 +11,4 @@ pub use manager::{StatusReceiver, StatusSender, TaskClient, TaskManager};
#[cfg(not(target_arch = "wasm32"))]
pub use signal::wait_for_signal_and_error;
pub use spawn::{spawn, spawn_with_report_error};
pub use spawn::spawn_with_report_error;
+2 -2
View File
@@ -2,7 +2,7 @@ use crate::TaskClient;
use std::future::Future;
#[cfg(target_arch = "wasm32")]
pub fn spawn<F>(future: F)
pub(crate) fn spawn<F>(future: F)
where
F: Future<Output = ()> + 'static,
{
@@ -10,7 +10,7 @@ where
}
#[cfg(not(target_arch = "wasm32"))]
pub fn spawn<F>(future: F)
pub(crate) fn spawn<F>(future: F)
where
F: Future + Send + 'static,
F::Output: Send + 'static,
+1 -14
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use nym_bin_common::version_checker;
use std::collections::{BTreeMap, HashMap};
use std::collections::HashMap;
use std::hash::Hash;
pub trait Versioned: Clone {
@@ -40,16 +40,3 @@ where
.collect()
}
}
impl<T, K, V> VersionFilterable<T> for BTreeMap<K, V>
where
K: Eq + Ord + Clone,
V: VersionFilterable<T>,
T: Versioned,
{
fn filter_by_version(&self, expected_version: &str) -> Self {
self.iter()
.map(|(k, v)| (k.clone(), v.filter_by_version(expected_version)))
.collect()
}
}
+14 -22
View File
@@ -42,26 +42,6 @@ pub struct Node {
}
impl Node {
pub fn parse_host(raw: &str) -> Result<NetworkAddress, GatewayConversionError> {
raw.parse()
.map_err(|err| GatewayConversionError::InvalidAddress {
value: raw.to_owned(),
source: err,
})
}
pub fn extract_mix_host(
host: &NetworkAddress,
mix_port: u16,
) -> Result<SocketAddr, GatewayConversionError> {
Ok(host.to_socket_addrs(mix_port).map_err(|err| {
GatewayConversionError::InvalidAddress {
value: host.to_string(),
source: err,
}
})?[0])
}
pub fn identity(&self) -> &NodeIdentity {
&self.identity_key
}
@@ -101,11 +81,23 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
type Error = GatewayConversionError;
fn try_from(bond: &'a GatewayBond) -> Result<Self, Self::Error> {
let host = Self::parse_host(&bond.gateway.host)?;
let host: NetworkAddress =
bond.gateway
.host
.parse()
.map_err(|err| GatewayConversionError::InvalidAddress {
value: bond.gateway.host.clone(),
source: err,
})?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = Self::extract_mix_host(&host, bond.gateway.mix_port)?;
let mix_host = host.to_socket_addrs(bond.gateway.mix_port).map_err(|err| {
GatewayConversionError::InvalidAddress {
value: bond.gateway.host.clone(),
source: err,
}
})?[0];
Ok(Node {
owner: bond.owner.as_str().to_owned(),
+9 -38
View File
@@ -1,15 +1,15 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::filter::VersionFilterable;
use log::warn;
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_mixnet_contract_common::GatewayBond;
use nym_sphinx_addressing::nodes::NodeIdentity;
use nym_sphinx_types::Node as SphinxNode;
use rand::{CryptoRng, Rng};
use std::array::TryFromSliceError;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt::{self, Display, Formatter};
use std::io;
@@ -106,45 +106,16 @@ pub type MixLayer = u8;
#[derive(Debug, Clone)]
pub struct NymTopology {
mixes: BTreeMap<MixLayer, Vec<mix::Node>>,
mixes: HashMap<MixLayer, Vec<mix::Node>>,
gateways: Vec<gateway::Node>,
}
impl NymTopology {
pub fn new(mixes: BTreeMap<MixLayer, Vec<mix::Node>>, gateways: Vec<gateway::Node>) -> Self {
pub fn new(mixes: HashMap<MixLayer, Vec<mix::Node>>, gateways: Vec<gateway::Node>) -> Self {
NymTopology { mixes, gateways }
}
pub fn from_detailed(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
) -> Self {
nym_topology_from_detailed(mix_details, gateway_bonds)
}
pub fn find_mix(&self, mix_id: MixId) -> Option<&mix::Node> {
for nodes in self.mixes.values() {
for node in nodes {
if node.mix_id == mix_id {
return Some(node);
}
}
}
None
}
pub fn find_mix_by_identity(&self, mixnode_identity: IdentityKeyRef) -> Option<&mix::Node> {
for nodes in self.mixes.values() {
for node in nodes {
if node.identity_key.to_base58_string() == mixnode_identity {
return Some(node);
}
}
}
None
}
pub fn mixes(&self) -> &BTreeMap<MixLayer, Vec<mix::Node>> {
pub fn mixes(&self) -> &HashMap<MixLayer, Vec<mix::Node>> {
&self.mixes
}
@@ -347,7 +318,7 @@ pub fn nym_topology_from_detailed(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
) -> NymTopology {
let mut mixes = BTreeMap::new();
let mut mixes = HashMap::new();
for bond in mix_details
.into_iter()
.map(|details| details.bond_information)
@@ -428,7 +399,7 @@ mod converting_mixes_to_vec {
..node1.clone()
};
let mut mixes: BTreeMap<MixLayer, Vec<mix::Node>> = BTreeMap::new();
let mut mixes: HashMap<MixLayer, Vec<mix::Node>> = HashMap::new();
mixes.insert(1, vec![node1, node2]);
mixes.insert(2, vec![node3]);
@@ -444,7 +415,7 @@ mod converting_mixes_to_vec {
#[test]
fn returns_an_empty_vec() {
let topology = NymTopology::new(BTreeMap::new(), vec![]);
let topology = NymTopology::new(HashMap::new(), vec![]);
let mixvec = topology.mixes_as_vec();
assert!(mixvec.is_empty());
}
+14 -24
View File
@@ -42,28 +42,6 @@ pub struct Node {
pub version: String,
}
impl Node {
pub fn parse_host(raw: &str) -> Result<NetworkAddress, MixnodeConversionError> {
raw.parse()
.map_err(|err| MixnodeConversionError::InvalidAddress {
value: raw.to_owned(),
source: err,
})
}
pub fn extract_mix_host(
host: &NetworkAddress,
mix_port: u16,
) -> Result<SocketAddr, MixnodeConversionError> {
Ok(host.to_socket_addrs(mix_port).map_err(|err| {
MixnodeConversionError::InvalidAddress {
value: host.to_string(),
source: err,
}
})?[0])
}
}
impl filter::Versioned for Node {
fn version(&self) -> String {
self.version.clone()
@@ -84,11 +62,23 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node {
type Error = MixnodeConversionError;
fn try_from(bond: &'a MixNodeBond) -> Result<Self, Self::Error> {
let host = Self::parse_host(&bond.mix_node.host)?;
let host: NetworkAddress =
bond.mix_node
.host
.parse()
.map_err(|err| MixnodeConversionError::InvalidAddress {
value: bond.mix_node.host.clone(),
source: err,
})?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = Self::extract_mix_host(&host, bond.mix_node.mix_port)?;
let mix_host = host
.to_socket_addrs(bond.mix_node.mix_port)
.map_err(|err| MixnodeConversionError::InvalidAddress {
value: bond.mix_node.host.clone(),
source: err,
})?[0];
Ok(Node {
mix_id: bond.mix_id,
-11
View File
@@ -61,10 +61,6 @@ pub enum PendingEpochEventData {
mix_id: MixId,
amount: DecCoin,
},
DecreasePledge {
mix_id: MixId,
decrease_by: DecCoin,
},
UnbondMixnode {
mix_id: MixId,
},
@@ -105,13 +101,6 @@ impl PendingEpochEventData {
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 } => {
Ok(PendingEpochEventData::UnbondMixnode { mix_id })
}
+1 -74
View File
@@ -15,7 +15,7 @@ macro_rules! console_log {
($($t:tt)*) => ($crate::log(&format_args!($($t)*).to_string()))
}
// will cause messages to be written as if console.warn("...") was called
// will cause messages to be written as if console.warm("...") was called
#[macro_export]
macro_rules! console_warn {
($($t:tt)*) => ($crate::warn(&format_args!($($t)*).to_string()))
@@ -50,76 +50,3 @@ pub async fn sleep(ms: i32) -> Result<(), JsValue> {
js_fut.await?;
Ok(())
}
/// A helper that construct a `JsValue` containing an error with the provided message.
pub fn simple_js_error<S: AsRef<str>>(message: S) -> JsValue {
let js_error = js_sys::Error::new(message.as_ref());
JsValue::from(js_error)
}
#[macro_export]
macro_rules! js_error {
($($t:tt)*) => {{
let js_error = js_sys::Error::new(&format!($($t)*));
wasm_bindgen::JsValue::from(js_error)
}}
}
/// Maps provided `Result`'s inner values into a pair of `JsValue` that can be returned
/// inside a promise (and in particular from inside `future_to_promise`)
pub fn into_promise_result<T, E>(res: Result<T, E>) -> Result<JsValue, JsValue>
where
T: Into<JsValue>,
E: Into<JsValue>,
{
res.map(Into::into).map_err(Into::into)
}
pub fn map_promise_err<T, E>(res: Result<T, E>) -> Result<T, JsValue>
where
E: Into<JsValue>,
{
res.map_err(Into::into)
}
pub trait PromisableResult {
fn into_promise_result(self) -> Result<JsValue, JsValue>;
}
// this should probably get renamed : )
pub trait PromisableResultError {
type Ok;
fn map_promise_err(self) -> Result<Self::Ok, JsValue>;
}
impl<T, E> PromisableResult for Result<T, E>
where
T: Into<JsValue>,
E: Into<JsValue>,
{
fn into_promise_result(self) -> Result<JsValue, JsValue> {
into_promise_result(self)
}
}
impl<T, E> PromisableResultError for Result<T, E>
where
E: Into<JsValue>,
{
type Ok = T;
fn map_promise_err(self) -> Result<T, JsValue> {
map_promise_err(self)
}
}
#[macro_export]
macro_rules! check_promise_result {
( $x:expr ) => {
match $crate::PromisableResultError::map_promise_err($x) {
Ok(r) => r,
Err(err) => return js_sys::Promise::reject(&err),
}
};
}
-6
View File
@@ -1,6 +0,0 @@
[alias]
wasm = "build --target wasm32-unknown-unknown"
[build]
rustflags = ["-C", "link-arg=-s"]
#target = "wasm32-unknown-unknown"
-11
View File
@@ -2,17 +2,6 @@
## Unreleased
## [v1.4.0] (2023-04-25)
- Allow mixnode operators to decrease their bond amount without having to rebond (will require a lot of testing EXACT reward values to make sure the "unit delegation" isn't broken afterwards) ([#3233])
- Fix a few clippy warnings in contract test code ([#3340])
- Add --all-targets to clippy for contracts ([#3337])
- A branch with all clippy warnings dealt with in contracts ([#3294])
[#3233]: https://github.com/nymtech/nym/issues/3233
[#3340]: https://github.com/nymtech/nym/pull/3340
[#3337]: https://github.com/nymtech/nym/pull/3337
[#3294]: https://github.com/nymtech/nym/pull/3294
## [v1.3.1] (2023-04-18)
- Add a query to the vesting contract for the amount of delegated tokens towards a particular `mix_id` (might be needed by NG) ([#3228])
+133 -159
View File
@@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array 0.14.7",
"generic-array 0.14.6",
]
[[package]]
@@ -31,7 +31,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.8",
"once_cell",
"version_check",
]
@@ -44,9 +44,9 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "arrayref"
version = "0.3.7"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
@@ -122,7 +122,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array 0.14.7",
"generic-array 0.14.6",
]
[[package]]
@@ -131,7 +131,7 @@ version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array 0.14.7",
"generic-array 0.14.6",
]
[[package]]
@@ -142,9 +142,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
[[package]]
name = "bumpalo"
version = "3.12.1"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byte-tools"
@@ -219,7 +219,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array 0.14.7",
"generic-array 0.14.6",
]
[[package]]
@@ -331,9 +331,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.7"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [
"libc",
]
@@ -393,7 +393,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21"
dependencies = [
"generic-array 0.14.7",
"generic-array 0.14.6",
"rand_core 0.6.4",
"subtle 2.4.1",
"zeroize",
@@ -405,7 +405,7 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array 0.14.7",
"generic-array 0.14.6",
"rand_core 0.6.4",
"typenum",
]
@@ -426,7 +426,7 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array 0.14.7",
"generic-array 0.14.6",
"subtle 2.4.1",
]
@@ -632,7 +632,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.7",
"generic-array 0.14.6",
]
[[package]]
@@ -718,7 +718,7 @@ dependencies = [
"crypto-bigint",
"der",
"ff",
"generic-array 0.14.7",
"generic-array 0.14.6",
"group",
"rand_core 0.6.4",
"sec1",
@@ -748,13 +748,13 @@ dependencies = [
[[package]]
name = "errno"
version = "0.3.1"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
"winapi",
]
[[package]]
@@ -818,9 +818,9 @@ dependencies = [
[[package]]
name = "generic-array"
version = "0.14.7"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
dependencies = [
"typenum",
"version_check",
@@ -841,9 +841,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.9"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"js-sys",
@@ -970,7 +970,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array 0.14.7",
"generic-array 0.14.6",
]
[[package]]
@@ -984,13 +984,13 @@ dependencies = [
[[package]]
name = "io-lifetimes"
version = "1.0.10"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.48.0",
"windows-sys 0.45.0",
]
[[package]]
@@ -1053,9 +1053,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.142"
version = "0.2.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
name = "libgit2-sys"
@@ -1089,9 +1089,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.3.4"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "lioness"
@@ -1123,22 +1123,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mixnet-vesting-integration-tests"
version = "0.1.0"
dependencies = [
"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]]
name = "num-traits"
version = "0.2.15"
@@ -1252,7 +1236,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract"
version = "1.4.0"
version = "1.3.1"
dependencies = [
"bs58",
"cosmwasm-derive",
@@ -1276,7 +1260,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.4.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -1313,7 +1297,7 @@ dependencies = [
"chacha20",
"chacha20poly1305",
"curve25519-dalek",
"getrandom 0.2.9",
"getrandom 0.2.8",
"rand",
"rayon",
"sphinx-packet",
@@ -1366,7 +1350,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract"
version = "1.4.0"
version = "1.3.1"
dependencies = [
"base64 0.21.0",
"cosmwasm-crypto",
@@ -1389,7 +1373,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.5.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
@@ -1493,9 +1477,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.56"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
dependencies = [
"unicode-ident",
]
@@ -1586,7 +1570,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.8",
]
[[package]]
@@ -1632,27 +1616,27 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.3.5"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.8.1"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.1"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rfc6979"
@@ -1676,16 +1660,16 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.37.15"
version = "0.36.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
"windows-sys 0.45.0",
]
[[package]]
@@ -1749,7 +1733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1"
dependencies = [
"der",
"generic-array 0.14.7",
"generic-array 0.14.6",
"pkcs8",
"subtle 2.4.1",
"zeroize",
@@ -1763,9 +1747,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "serde"
version = "1.0.160"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
dependencies = [
"serde_derive",
]
@@ -1781,13 +1765,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.160"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
"syn 2.0.3",
]
[[package]]
@@ -1803,9 +1787,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.96"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"itoa",
"ryu",
@@ -1820,7 +1804,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
"syn 2.0.3",
]
[[package]]
@@ -1920,9 +1904,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.15"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
checksum = "e8234ae35e70582bfa0f1fedffa6daa248e41dd045310b19800c4a36382c8f60"
dependencies = [
"proc-macro2",
"quote",
@@ -1930,16 +1914,28 @@ dependencies = [
]
[[package]]
name = "tempfile"
version = "3.5.0"
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-xid",
]
[[package]]
name = "tempfile"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys 0.45.0",
"windows-sys 0.42.0",
]
[[package]]
@@ -1959,7 +1955,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
"syn 2.0.3",
]
[[package]]
@@ -2024,9 +2020,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.13"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
checksum = "7d502c968c6a838ead8e69b2ee18ec708802f99db92a0d156705ec9ef801993b"
[[package]]
name = "unicode-ident"
@@ -2043,6 +2039,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "universal-hash"
version = "0.5.0"
@@ -2168,22 +2170,50 @@ version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.0",
"windows-targets",
]
[[package]]
@@ -2192,28 +2222,13 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
@@ -2222,84 +2237,42 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "x25519-dalek"
version = "1.1.1"
@@ -2322,11 +2295,12 @@ dependencies = [
[[package]]
name = "zeroize_derive"
version = "1.4.2"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
"syn 1.0.109",
"synstructure",
]
-1
View File
@@ -4,7 +4,6 @@ members = [
"coconut-dkg",
"coconut-test",
"mixnet",
"mixnet-vesting-integration-tests",
"multisig/cw3-flex-multisig",
"multisig/cw4-group",
"service-provider-directory",
+2 -2
View File
@@ -86,9 +86,9 @@ mod tests {
assert!(res.is_none());
let mut spend_credential = SpendCredential::new(
funds,
funds.clone(),
blind_serial_number.to_string(),
gateway_cosmos_address,
gateway_cosmos_address.clone(),
);
spend_credential.mark_as_spent();

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